feat: Add shortcuts for stroke and background color picker (#3318)
* feat: Add shortcuts for opening stroke and background color picker * Use App.tsx keydown handler * only get selectedElements if applicable (perf) * fix tests and snaps * reuse `appState.openMenu` Co-authored-by: dwelle <luzar.david@gmail.com>
This commit is contained in:
parent
bc0b6e1888
commit
6c3e4417e1
@ -22,8 +22,8 @@ export const actionChangeViewBackgroundColor = register({
|
|||||||
name: "changeViewBackgroundColor",
|
name: "changeViewBackgroundColor",
|
||||||
perform: (_, appState, value) => {
|
perform: (_, appState, value) => {
|
||||||
return {
|
return {
|
||||||
appState: { ...appState, viewBackgroundColor: value },
|
appState: { ...appState, ...value },
|
||||||
commitToHistory: true,
|
commitToHistory: !!value.viewBackgroundColor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ appState, updateData }) => {
|
PanelComponent: ({ appState, updateData }) => {
|
||||||
@ -33,7 +33,11 @@ export const actionChangeViewBackgroundColor = register({
|
|||||||
label={t("labels.canvasBackground")}
|
label={t("labels.canvasBackground")}
|
||||||
type="canvasBackground"
|
type="canvasBackground"
|
||||||
color={appState.viewBackgroundColor}
|
color={appState.viewBackgroundColor}
|
||||||
onChange={(color) => updateData(color)}
|
onChange={(color) => updateData({ viewBackgroundColor: color })}
|
||||||
|
isActive={appState.openMenu === "canvasColorPicker"}
|
||||||
|
setActive={(active) =>
|
||||||
|
updateData({ openMenu: active ? "canvasColorPicker" : null })
|
||||||
|
}
|
||||||
data-testid="canvas-background-picker"
|
data-testid="canvas-background-picker"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
@ -99,13 +99,18 @@ export const actionChangeStrokeColor = register({
|
|||||||
name: "changeStrokeColor",
|
name: "changeStrokeColor",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
return {
|
return {
|
||||||
|
...(value.currentItemStrokeColor && {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
strokeColor: value,
|
strokeColor: value.currentItemStrokeColor,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
appState: { ...appState, currentItemStrokeColor: value },
|
}),
|
||||||
commitToHistory: true,
|
appState: {
|
||||||
|
...appState,
|
||||||
|
...value,
|
||||||
|
},
|
||||||
|
commitToHistory: !!value.currentItemStrokeColor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
@ -120,7 +125,11 @@ export const actionChangeStrokeColor = register({
|
|||||||
(element) => element.strokeColor,
|
(element) => element.strokeColor,
|
||||||
appState.currentItemStrokeColor,
|
appState.currentItemStrokeColor,
|
||||||
)}
|
)}
|
||||||
onChange={updateData}
|
onChange={(color) => updateData({ currentItemStrokeColor: color })}
|
||||||
|
isActive={appState.openMenu === "strokeColorPicker"}
|
||||||
|
setActive={(active) =>
|
||||||
|
updateData({ openMenu: active ? "strokeColorPicker" : null })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
@ -130,13 +139,18 @@ export const actionChangeBackgroundColor = register({
|
|||||||
name: "changeBackgroundColor",
|
name: "changeBackgroundColor",
|
||||||
perform: (elements, appState, value) => {
|
perform: (elements, appState, value) => {
|
||||||
return {
|
return {
|
||||||
|
...(value.currentItemBackgroundColor && {
|
||||||
elements: changeProperty(elements, appState, (el) =>
|
elements: changeProperty(elements, appState, (el) =>
|
||||||
newElementWith(el, {
|
newElementWith(el, {
|
||||||
backgroundColor: value,
|
backgroundColor: value.currentItemBackgroundColor,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
appState: { ...appState, currentItemBackgroundColor: value },
|
}),
|
||||||
commitToHistory: true,
|
appState: {
|
||||||
|
...appState,
|
||||||
|
...value,
|
||||||
|
},
|
||||||
|
commitToHistory: !!value.currentItemBackgroundColor,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData }) => (
|
PanelComponent: ({ elements, appState, updateData }) => (
|
||||||
@ -151,7 +165,11 @@ export const actionChangeBackgroundColor = register({
|
|||||||
(element) => element.backgroundColor,
|
(element) => element.backgroundColor,
|
||||||
appState.currentItemBackgroundColor,
|
appState.currentItemBackgroundColor,
|
||||||
)}
|
)}
|
||||||
onChange={updateData}
|
onChange={(color) => updateData({ currentItemBackgroundColor: color })}
|
||||||
|
isActive={appState.openMenu === "backgroundColorPicker"}
|
||||||
|
setActive={(active) =>
|
||||||
|
updateData({ openMenu: active ? "backgroundColorPicker" : null })
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
|
@ -1645,6 +1645,21 @@ class App extends React.Component<AppProps, AppState> {
|
|||||||
isHoldingSpace = true;
|
isHoldingSpace = true;
|
||||||
setCursor(this.canvas, CURSOR_TYPE.GRABBING);
|
setCursor(this.canvas, CURSOR_TYPE.GRABBING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (event.key === KEYS.G || event.key === KEYS.S) {
|
||||||
|
const selectedElements = getSelectedElements(
|
||||||
|
this.scene.getElements(),
|
||||||
|
this.state,
|
||||||
|
);
|
||||||
|
if (selectedElements.length) {
|
||||||
|
if (event.key === KEYS.G) {
|
||||||
|
this.setState({ openMenu: "backgroundColorPicker" });
|
||||||
|
}
|
||||||
|
if (event.key === KEYS.S) {
|
||||||
|
this.setState({ openMenu: "strokeColorPicker" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
@ -238,13 +238,16 @@ export const ColorPicker = ({
|
|||||||
color,
|
color,
|
||||||
onChange,
|
onChange,
|
||||||
label,
|
label,
|
||||||
|
isActive,
|
||||||
|
setActive,
|
||||||
}: {
|
}: {
|
||||||
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
||||||
color: string | null;
|
color: string | null;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
label: string;
|
label: string;
|
||||||
|
isActive: boolean;
|
||||||
|
setActive: (active: boolean) => void;
|
||||||
}) => {
|
}) => {
|
||||||
const [isActive, setActive] = React.useState(false);
|
|
||||||
const pickerButton = React.useRef<HTMLButtonElement>(null);
|
const pickerButton = React.useRef<HTMLButtonElement>(null);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -365,6 +365,14 @@ export const HelpDialog = ({ onClose }: { onClose?: () => void }) => {
|
|||||||
label={t("labels.flipVertical")}
|
label={t("labels.flipVertical")}
|
||||||
shortcuts={[getShortcutKey("Shift+V")]}
|
shortcuts={[getShortcutKey("Shift+V")]}
|
||||||
/>
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.showStroke")}
|
||||||
|
shortcuts={[getShortcutKey("S")]}
|
||||||
|
/>
|
||||||
|
<Shortcut
|
||||||
|
label={t("labels.showBackground")}
|
||||||
|
shortcuts={[getShortcutKey("G")]}
|
||||||
|
/>
|
||||||
</ShortcutIsland>
|
</ShortcutIsland>
|
||||||
</Column>
|
</Column>
|
||||||
</Columns>
|
</Columns>
|
||||||
|
@ -44,6 +44,7 @@ export const KEYS = {
|
|||||||
A: "a",
|
A: "a",
|
||||||
D: "d",
|
D: "d",
|
||||||
E: "e",
|
E: "e",
|
||||||
|
G: "g",
|
||||||
L: "l",
|
L: "l",
|
||||||
O: "o",
|
O: "o",
|
||||||
P: "p",
|
P: "p",
|
||||||
|
@ -101,6 +101,8 @@
|
|||||||
"viewMode": "View mode",
|
"viewMode": "View mode",
|
||||||
"toggleExportColorScheme": "Toggle export color scheme",
|
"toggleExportColorScheme": "Toggle export color scheme",
|
||||||
"share": "Share",
|
"share": "Share",
|
||||||
|
"showStroke": "Show stroke color picker",
|
||||||
|
"showBackground": "Show background color picker",
|
||||||
"toggleTheme": "Toggle theme"
|
"toggleTheme": "Toggle theme"
|
||||||
},
|
},
|
||||||
"buttons": {
|
"buttons": {
|
||||||
|
@ -1748,7 +1748,7 @@ Object {
|
|||||||
"name": "Untitled-201933152653",
|
"name": "Untitled-201933152653",
|
||||||
"offsetLeft": 20,
|
"offsetLeft": 20,
|
||||||
"offsetTop": 10,
|
"offsetTop": 10,
|
||||||
"openMenu": null,
|
"openMenu": "backgroundColorPicker",
|
||||||
"pasteDialog": Object {
|
"pasteDialog": Object {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
@ -2424,7 +2424,7 @@ Object {
|
|||||||
|
|
||||||
exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of elements 1`] = `2`;
|
exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of elements 1`] = `2`;
|
||||||
|
|
||||||
exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `25`;
|
exports[`contextMenu element selecting 'Paste styles' in context menu pastes styles: [end of test] number of renders 1`] = `27`;
|
||||||
|
|
||||||
exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] appState 1`] = `
|
exports[`contextMenu element selecting 'Send backward' in context menu sends element backward: [end of test] appState 1`] = `
|
||||||
Object {
|
Object {
|
||||||
|
@ -3260,7 +3260,7 @@ Object {
|
|||||||
"name": "Untitled-201933152653",
|
"name": "Untitled-201933152653",
|
||||||
"offsetLeft": 0,
|
"offsetLeft": 0,
|
||||||
"offsetTop": 0,
|
"offsetTop": 0,
|
||||||
"openMenu": null,
|
"openMenu": "strokeColorPicker",
|
||||||
"pasteDialog": Object {
|
"pasteDialog": Object {
|
||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
@ -3451,7 +3451,7 @@ Object {
|
|||||||
|
|
||||||
exports[`regression tests change the properties of a shape: [end of test] number of elements 1`] = `1`;
|
exports[`regression tests change the properties of a shape: [end of test] number of elements 1`] = `1`;
|
||||||
|
|
||||||
exports[`regression tests change the properties of a shape: [end of test] number of renders 1`] = `12`;
|
exports[`regression tests change the properties of a shape: [end of test] number of renders 1`] = `14`;
|
||||||
|
|
||||||
exports[`regression tests click on an element and drag it: [dragged] appState 1`] = `
|
exports[`regression tests click on an element and drag it: [dragged] appState 1`] = `
|
||||||
Object {
|
Object {
|
||||||
@ -8734,7 +8734,7 @@ Object {
|
|||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 1000,
|
"height": 0,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@ -8745,9 +8745,9 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 1278240551,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
"width": 0,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
}
|
}
|
||||||
@ -8765,14 +8765,14 @@ Object {
|
|||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#000000",
|
"strokeColor": "#000000",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "ellipse",
|
"type": "ellipse",
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"versionNonce": 453191,
|
"versionNonce": 449462985,
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"x": 500,
|
"x": 500,
|
||||||
"y": 500,
|
"y": 500,
|
||||||
@ -8799,9 +8799,7 @@ Object {
|
|||||||
"editingGroupId": null,
|
"editingGroupId": null,
|
||||||
"editingLinearElement": null,
|
"editingLinearElement": null,
|
||||||
"name": "Untitled-201933152653",
|
"name": "Untitled-201933152653",
|
||||||
"selectedElementIds": Object {
|
"selectedElementIds": Object {},
|
||||||
"id0": true,
|
|
||||||
},
|
|
||||||
"viewBackgroundColor": "#ffffff",
|
"viewBackgroundColor": "#ffffff",
|
||||||
},
|
},
|
||||||
"elements": Array [
|
"elements": Array [
|
||||||
@ -8811,7 +8809,7 @@ Object {
|
|||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 1000,
|
"height": 0,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@ -8822,9 +8820,9 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 1278240551,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
"width": 0,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
},
|
},
|
||||||
@ -8847,7 +8845,7 @@ Object {
|
|||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 1000,
|
"height": 0,
|
||||||
"id": "id0",
|
"id": "id0",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
@ -8858,9 +8856,9 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 1278240551,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
"width": 0,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
},
|
},
|
||||||
@ -8875,14 +8873,14 @@ Object {
|
|||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#000000",
|
"strokeColor": "#000000",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "ellipse",
|
"type": "ellipse",
|
||||||
"version": 2,
|
"version": 2,
|
||||||
"versionNonce": 453191,
|
"versionNonce": 449462985,
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"x": 500,
|
"x": 500,
|
||||||
"y": 500,
|
"y": 500,
|
||||||
@ -8901,7 +8899,7 @@ exports[`regression tests given selected element A with lower z-index than unsel
|
|||||||
Object {
|
Object {
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "#fa5252",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
"currentItemFillStyle": "hachure",
|
"currentItemFillStyle": "hachure",
|
||||||
"currentItemFontFamily": 1,
|
"currentItemFontFamily": 1,
|
||||||
@ -8982,7 +8980,7 @@ Object {
|
|||||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] element 0 1`] = `
|
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] element 0 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
@ -8997,8 +8995,8 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 1278240551,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
@ -9008,24 +9006,24 @@ Object {
|
|||||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] element 1 1`] = `
|
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] element 1 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 1000,
|
"height": 500,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#000000",
|
"strokeColor": "#000000",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "ellipse",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 453191,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
"width": 500,
|
||||||
"x": 500,
|
"x": 500,
|
||||||
"y": 500,
|
"y": 500,
|
||||||
}
|
}
|
||||||
@ -9053,13 +9051,14 @@ Object {
|
|||||||
"name": "Untitled-201933152653",
|
"name": "Untitled-201933152653",
|
||||||
"selectedElementIds": Object {
|
"selectedElementIds": Object {
|
||||||
"id0": true,
|
"id0": true,
|
||||||
|
"id2": true,
|
||||||
},
|
},
|
||||||
"viewBackgroundColor": "#ffffff",
|
"viewBackgroundColor": "#ffffff",
|
||||||
},
|
},
|
||||||
"elements": Array [
|
"elements": Array [
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
@ -9074,68 +9073,32 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 1278240551,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"appState": Object {
|
|
||||||
"editingGroupId": null,
|
|
||||||
"editingLinearElement": null,
|
|
||||||
"name": "Untitled-201933152653",
|
|
||||||
"selectedElementIds": Object {
|
|
||||||
"id1": true,
|
|
||||||
},
|
|
||||||
"viewBackgroundColor": "#ffffff",
|
|
||||||
},
|
|
||||||
"elements": Array [
|
|
||||||
Object {
|
|
||||||
"angle": 0,
|
|
||||||
"backgroundColor": "#fa5252",
|
|
||||||
"boundElementIds": null,
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"groupIds": Array [],
|
|
||||||
"height": 1000,
|
|
||||||
"id": "id0",
|
|
||||||
"isDeleted": false,
|
|
||||||
"opacity": 100,
|
|
||||||
"roughness": 1,
|
|
||||||
"seed": 337897,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 2,
|
|
||||||
"versionNonce": 1278240551,
|
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 1000,
|
"height": 500,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#000000",
|
"strokeColor": "#000000",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "ellipse",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 453191,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
"width": 500,
|
||||||
"x": 500,
|
"x": 500,
|
||||||
"y": 500,
|
"y": 500,
|
||||||
},
|
},
|
||||||
@ -9147,13 +9110,13 @@ Object {
|
|||||||
|
|
||||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of elements 1`] = `2`;
|
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of elements 1`] = `2`;
|
||||||
|
|
||||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of renders 1`] = `18`;
|
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when clicking intersection between A and B B should be selected on pointer up: [end of test] number of renders 1`] = `11`;
|
||||||
|
|
||||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] appState 1`] = `
|
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] appState 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"collaborators": Map {},
|
"collaborators": Map {},
|
||||||
"currentChartType": "bar",
|
"currentChartType": "bar",
|
||||||
"currentItemBackgroundColor": "#fa5252",
|
"currentItemBackgroundColor": "transparent",
|
||||||
"currentItemEndArrowhead": "arrow",
|
"currentItemEndArrowhead": "arrow",
|
||||||
"currentItemFillStyle": "hachure",
|
"currentItemFillStyle": "hachure",
|
||||||
"currentItemFontFamily": 1,
|
"currentItemFontFamily": 1,
|
||||||
@ -9235,7 +9198,7 @@ Object {
|
|||||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] element 0 1`] = `
|
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] element 0 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
@ -9250,8 +9213,8 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 3,
|
"version": 2,
|
||||||
"versionNonce": 1150084233,
|
"versionNonce": 401146281,
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"x": 100,
|
"x": 100,
|
||||||
"y": 100,
|
"y": 100,
|
||||||
@ -9261,24 +9224,24 @@ Object {
|
|||||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] element 1 1`] = `
|
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] element 1 1`] = `
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 1000,
|
"height": 500,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#000000",
|
"strokeColor": "#000000",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "ellipse",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 453191,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
"width": 500,
|
||||||
"x": 500,
|
"x": 500,
|
||||||
"y": 500,
|
"y": 500,
|
||||||
}
|
}
|
||||||
@ -9306,13 +9269,14 @@ Object {
|
|||||||
"name": "Untitled-201933152653",
|
"name": "Untitled-201933152653",
|
||||||
"selectedElementIds": Object {
|
"selectedElementIds": Object {
|
||||||
"id0": true,
|
"id0": true,
|
||||||
|
"id2": true,
|
||||||
},
|
},
|
||||||
"viewBackgroundColor": "#ffffff",
|
"viewBackgroundColor": "#ffffff",
|
||||||
},
|
},
|
||||||
"elements": Array [
|
"elements": Array [
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
@ -9327,68 +9291,32 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 1278240551,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
|
||||||
"x": 0,
|
|
||||||
"y": 0,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
Object {
|
|
||||||
"appState": Object {
|
|
||||||
"editingGroupId": null,
|
|
||||||
"editingLinearElement": null,
|
|
||||||
"name": "Untitled-201933152653",
|
|
||||||
"selectedElementIds": Object {
|
|
||||||
"id1": true,
|
|
||||||
},
|
|
||||||
"viewBackgroundColor": "#ffffff",
|
|
||||||
},
|
|
||||||
"elements": Array [
|
|
||||||
Object {
|
|
||||||
"angle": 0,
|
|
||||||
"backgroundColor": "#fa5252",
|
|
||||||
"boundElementIds": null,
|
|
||||||
"fillStyle": "hachure",
|
|
||||||
"groupIds": Array [],
|
|
||||||
"height": 1000,
|
|
||||||
"id": "id0",
|
|
||||||
"isDeleted": false,
|
|
||||||
"opacity": 100,
|
|
||||||
"roughness": 1,
|
|
||||||
"seed": 337897,
|
|
||||||
"strokeColor": "#000000",
|
|
||||||
"strokeSharpness": "sharp",
|
|
||||||
"strokeStyle": "solid",
|
|
||||||
"strokeWidth": 1,
|
|
||||||
"type": "rectangle",
|
|
||||||
"version": 2,
|
|
||||||
"versionNonce": 1278240551,
|
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"x": 0,
|
"x": 0,
|
||||||
"y": 0,
|
"y": 0,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 1000,
|
"height": 500,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#000000",
|
"strokeColor": "#000000",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "ellipse",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 453191,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
"width": 500,
|
||||||
"x": 500,
|
"x": 500,
|
||||||
"y": 500,
|
"y": 500,
|
||||||
},
|
},
|
||||||
@ -9409,7 +9337,7 @@ Object {
|
|||||||
"elements": Array [
|
"elements": Array [
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
@ -9424,32 +9352,32 @@ Object {
|
|||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "rectangle",
|
"type": "rectangle",
|
||||||
"version": 3,
|
"version": 2,
|
||||||
"versionNonce": 1150084233,
|
"versionNonce": 401146281,
|
||||||
"width": 1000,
|
"width": 1000,
|
||||||
"x": 100,
|
"x": 100,
|
||||||
"y": 100,
|
"y": 100,
|
||||||
},
|
},
|
||||||
Object {
|
Object {
|
||||||
"angle": 0,
|
"angle": 0,
|
||||||
"backgroundColor": "#fa5252",
|
"backgroundColor": "red",
|
||||||
"boundElementIds": null,
|
"boundElementIds": null,
|
||||||
"fillStyle": "hachure",
|
"fillStyle": "hachure",
|
||||||
"groupIds": Array [],
|
"groupIds": Array [],
|
||||||
"height": 1000,
|
"height": 500,
|
||||||
"id": "id1",
|
"id": "id1",
|
||||||
"isDeleted": false,
|
"isDeleted": false,
|
||||||
"opacity": 100,
|
"opacity": 100,
|
||||||
"roughness": 1,
|
"roughness": 1,
|
||||||
"seed": 449462985,
|
"seed": 1278240551,
|
||||||
"strokeColor": "#000000",
|
"strokeColor": "#000000",
|
||||||
"strokeSharpness": "sharp",
|
"strokeSharpness": "sharp",
|
||||||
"strokeStyle": "solid",
|
"strokeStyle": "solid",
|
||||||
"strokeWidth": 1,
|
"strokeWidth": 1,
|
||||||
"type": "ellipse",
|
"type": "rectangle",
|
||||||
"version": 2,
|
"version": 1,
|
||||||
"versionNonce": 453191,
|
"versionNonce": 0,
|
||||||
"width": 1000,
|
"width": 500,
|
||||||
"x": 500,
|
"x": 500,
|
||||||
"y": 500,
|
"y": 500,
|
||||||
},
|
},
|
||||||
@ -9461,7 +9389,7 @@ Object {
|
|||||||
|
|
||||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of elements 1`] = `2`;
|
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of elements 1`] = `2`;
|
||||||
|
|
||||||
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of renders 1`] = `19`;
|
exports[`regression tests given selected element A with lower z-index than unselected element B and given B is partially over A when dragging on intersection between A and B A should be dragged and keep being selected: [end of test] number of renders 1`] = `12`;
|
||||||
|
|
||||||
exports[`regression tests key 2 selects rectangle tool: [end of test] appState 1`] = `
|
exports[`regression tests key 2 selects rectangle tool: [end of test] appState 1`] = `
|
||||||
Object {
|
Object {
|
||||||
@ -13323,16 +13251,13 @@ Object {
|
|||||||
"data": null,
|
"data": null,
|
||||||
"shown": false,
|
"shown": false,
|
||||||
},
|
},
|
||||||
"previousSelectedElementIds": Object {
|
"previousSelectedElementIds": Object {},
|
||||||
"id0": true,
|
|
||||||
},
|
|
||||||
"resizingElement": null,
|
"resizingElement": null,
|
||||||
"scrollX": 0,
|
"scrollX": 0,
|
||||||
"scrollY": 0,
|
"scrollY": 0,
|
||||||
"scrolledOutside": false,
|
"scrolledOutside": false,
|
||||||
"selectedElementIds": Object {
|
"selectedElementIds": Object {
|
||||||
"id0": true,
|
"id0": true,
|
||||||
"id1": true,
|
|
||||||
},
|
},
|
||||||
"selectedGroupIds": Object {},
|
"selectedGroupIds": Object {},
|
||||||
"selectionElement": null,
|
"selectionElement": null,
|
||||||
@ -13476,7 +13401,7 @@ Object {
|
|||||||
|
|
||||||
exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of elements 1`] = `1`;
|
exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of elements 1`] = `1`;
|
||||||
|
|
||||||
exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of renders 1`] = `12`;
|
exports[`regression tests should show fill icons when element has non transparent background: [end of test] number of renders 1`] = `13`;
|
||||||
|
|
||||||
exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] appState 1`] = `
|
exports[`regression tests single-clicking on a subgroup of a selected group should not alter selection: [end of test] appState 1`] = `
|
||||||
Object {
|
Object {
|
||||||
|
@ -702,33 +702,36 @@ describe("regression tests", () => {
|
|||||||
"when clicking intersection between A and B " +
|
"when clicking intersection between A and B " +
|
||||||
"B should be selected on pointer up",
|
"B should be selected on pointer up",
|
||||||
() => {
|
() => {
|
||||||
UI.clickTool("rectangle");
|
// set background color since default is transparent
|
||||||
// change background color since default is transparent
|
|
||||||
// and transparent elements can't be selected by clicking inside of them
|
// and transparent elements can't be selected by clicking inside of them
|
||||||
clickLabeledElement("Background");
|
const rect1 = API.createElement({
|
||||||
clickLabeledElement("#fa5252");
|
type: "rectangle",
|
||||||
mouse.down();
|
backgroundColor: "red",
|
||||||
mouse.up(1000, 1000);
|
x: 0,
|
||||||
|
y: 0,
|
||||||
|
width: 1000,
|
||||||
|
height: 1000,
|
||||||
|
});
|
||||||
|
const rect2 = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
backgroundColor: "red",
|
||||||
|
x: 500,
|
||||||
|
y: 500,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
});
|
||||||
|
h.elements = [rect1, rect2];
|
||||||
|
|
||||||
// draw ellipse partially over rectangle.
|
mouse.select(rect1);
|
||||||
// since ellipse was created after rectangle it has an higher z-index.
|
|
||||||
// we don't need to change background color again since change above
|
|
||||||
// affects next drawn elements.
|
|
||||||
UI.clickTool("ellipse");
|
|
||||||
mouse.reset();
|
|
||||||
mouse.down(500, 500);
|
|
||||||
mouse.up(1000, 1000);
|
|
||||||
|
|
||||||
// select rectangle
|
// pointerdown on rect2 covering rect1 while rect1 is selected should
|
||||||
mouse.reset();
|
// retain rect1 selection
|
||||||
mouse.click();
|
|
||||||
|
|
||||||
// pointer down on intersection between ellipse and rectangle
|
|
||||||
mouse.down(900, 900);
|
mouse.down(900, 900);
|
||||||
expect(API.getSelectedElement().type).toBe("rectangle");
|
expect(API.getSelectedElement().id).toBe(rect1.id);
|
||||||
|
|
||||||
|
// pointerup should select rect2
|
||||||
mouse.up();
|
mouse.up();
|
||||||
expect(API.getSelectedElement().type).toBe("ellipse");
|
expect(API.getSelectedElement().id).toBe(rect2.id);
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -737,26 +740,27 @@ describe("regression tests", () => {
|
|||||||
"when dragging on intersection between A and B " +
|
"when dragging on intersection between A and B " +
|
||||||
"A should be dragged and keep being selected",
|
"A should be dragged and keep being selected",
|
||||||
() => {
|
() => {
|
||||||
UI.clickTool("rectangle");
|
const rect1 = API.createElement({
|
||||||
// change background color since default is transparent
|
type: "rectangle",
|
||||||
// and transparent elements can't be selected by clicking inside of them
|
backgroundColor: "red",
|
||||||
clickLabeledElement("Background");
|
x: 0,
|
||||||
clickLabeledElement("#fa5252");
|
y: 0,
|
||||||
mouse.down();
|
width: 1000,
|
||||||
mouse.up(1000, 1000);
|
height: 1000,
|
||||||
|
});
|
||||||
|
const rect2 = API.createElement({
|
||||||
|
type: "rectangle",
|
||||||
|
backgroundColor: "red",
|
||||||
|
x: 500,
|
||||||
|
y: 500,
|
||||||
|
width: 500,
|
||||||
|
height: 500,
|
||||||
|
});
|
||||||
|
h.elements = [rect1, rect2];
|
||||||
|
|
||||||
// draw ellipse partially over rectangle.
|
mouse.select(rect1);
|
||||||
// since ellipse was created after rectangle it has an higher z-index.
|
|
||||||
// we don't need to change background color again since change above
|
|
||||||
// affects next drawn elements.
|
|
||||||
UI.clickTool("ellipse");
|
|
||||||
mouse.reset();
|
|
||||||
mouse.down(500, 500);
|
|
||||||
mouse.up(1000, 1000);
|
|
||||||
|
|
||||||
// select rectangle
|
expect(API.getSelectedElement().id).toBe(rect1.id);
|
||||||
mouse.reset();
|
|
||||||
mouse.click();
|
|
||||||
|
|
||||||
const { x: prevX, y: prevY } = API.getSelectedElement();
|
const { x: prevX, y: prevY } = API.getSelectedElement();
|
||||||
|
|
||||||
@ -764,7 +768,7 @@ describe("regression tests", () => {
|
|||||||
mouse.down(900, 900);
|
mouse.down(900, 900);
|
||||||
mouse.up(100, 100);
|
mouse.up(100, 100);
|
||||||
|
|
||||||
expect(API.getSelectedElement().type).toBe("rectangle");
|
expect(API.getSelectedElement().id).toBe(rect1.id);
|
||||||
expect(API.getSelectedElement().x).toEqual(prevX + 100);
|
expect(API.getSelectedElement().x).toEqual(prevX + 100);
|
||||||
expect(API.getSelectedElement().y).toEqual(prevY + 100);
|
expect(API.getSelectedElement().y).toEqual(prevY + 100);
|
||||||
},
|
},
|
||||||
|
@ -81,7 +81,13 @@ export type AppState = {
|
|||||||
isResizing: boolean;
|
isResizing: boolean;
|
||||||
isRotating: boolean;
|
isRotating: boolean;
|
||||||
zoom: Zoom;
|
zoom: Zoom;
|
||||||
openMenu: "canvas" | "shape" | null;
|
openMenu:
|
||||||
|
| "canvas"
|
||||||
|
| "shape"
|
||||||
|
| "canvasColorPicker"
|
||||||
|
| "backgroundColorPicker"
|
||||||
|
| "strokeColorPicker"
|
||||||
|
| null;
|
||||||
lastPointerDownWith: PointerType;
|
lastPointerDownWith: PointerType;
|
||||||
selectedElementIds: { [id: string]: boolean };
|
selectedElementIds: { [id: string]: boolean };
|
||||||
previousSelectedElementIds: { [id: string]: boolean };
|
previousSelectedElementIds: { [id: string]: boolean };
|
||||||
|
Loading…
x
Reference in New Issue
Block a user