Set Trailing Cmma to (#525)
This commit is contained in:
parent
25202aec11
commit
ee68af0fd3
@ -11,5 +11,5 @@ module.exports = {
|
|||||||
files.filter(file => !cli.isPathIgnored(file)).join(" ")
|
files.filter(file => !cli.isPathIgnored(file)).join(" ")
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
"*.{js,css,scss,json,md,ts,tsx,html,yml}": ["prettier --write"]
|
"*.{js,css,scss,json,md,ts,tsx,html,yml}": ["prettier --write"],
|
||||||
};
|
};
|
||||||
|
@ -1 +1,3 @@
|
|||||||
{}
|
{
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
||||||
|
@ -17,8 +17,8 @@ var config = defaults.__get__("config");
|
|||||||
config.optimization.runtimeChunk = false;
|
config.optimization.runtimeChunk = false;
|
||||||
config.optimization.splitChunks = {
|
config.optimization.splitChunks = {
|
||||||
cacheGroups: {
|
cacheGroups: {
|
||||||
default: false
|
default: false,
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
// Set the filename to be deterministic
|
// Set the filename to be deterministic
|
||||||
config.output.filename = "static/js/build-node.js";
|
config.output.filename = "static/js/build-node.js";
|
||||||
@ -33,7 +33,7 @@ config.externals = function(context, request, callback) {
|
|||||||
if (/\.node$/.test(request)) {
|
if (/\.node$/.test(request)) {
|
||||||
return callback(
|
return callback(
|
||||||
null,
|
null,
|
||||||
"commonjs ../../../node_modules/canvas/build/Release/canvas.node"
|
"commonjs ../../../node_modules/canvas/build/Release/canvas.node",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
callback();
|
callback();
|
||||||
|
@ -20,7 +20,7 @@ export const actionChangeViewBackgroundColor: Action = {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionClearCanvas: Action = {
|
export const actionClearCanvas: Action = {
|
||||||
@ -28,7 +28,7 @@ export const actionClearCanvas: Action = {
|
|||||||
perform: () => {
|
perform: () => {
|
||||||
return {
|
return {
|
||||||
elements: [],
|
elements: [],
|
||||||
appState: getDefaultAppState()
|
appState: getDefaultAppState(),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ updateData, t }) => (
|
PanelComponent: ({ updateData, t }) => (
|
||||||
@ -47,5 +47,5 @@ export const actionClearCanvas: Action = {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
@ -6,10 +6,10 @@ export const actionDeleteSelected: Action = {
|
|||||||
name: "deleteSelectedElements",
|
name: "deleteSelectedElements",
|
||||||
perform: elements => {
|
perform: elements => {
|
||||||
return {
|
return {
|
||||||
elements: deleteSelectedElements(elements)
|
elements: deleteSelectedElements(elements),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.delete",
|
contextItemLabel: "labels.delete",
|
||||||
contextMenuOrder: 3,
|
contextMenuOrder: 3,
|
||||||
keyTest: event => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE
|
keyTest: event => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE,
|
||||||
};
|
};
|
||||||
|
@ -15,7 +15,7 @@ export const actionChangeProjectName: Action = {
|
|||||||
value={appState.name || "Unnamed"}
|
value={appState.name || "Unnamed"}
|
||||||
onChange={(name: string) => updateData(name)}
|
onChange={(name: string) => updateData(name)}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionChangeExportBackground: Action = {
|
export const actionChangeExportBackground: Action = {
|
||||||
@ -34,7 +34,7 @@ export const actionChangeExportBackground: Action = {
|
|||||||
/>{" "}
|
/>{" "}
|
||||||
{t("labels.withBackground")}
|
{t("labels.withBackground")}
|
||||||
</label>
|
</label>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionSaveScene: Action = {
|
export const actionSaveScene: Action = {
|
||||||
@ -51,7 +51,7 @@ export const actionSaveScene: Action = {
|
|||||||
aria-label={t("buttons.save")}
|
aria-label={t("buttons.save")}
|
||||||
onClick={() => updateData(null)}
|
onClick={() => updateData(null)}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionLoadScene: Action = {
|
export const actionLoadScene: Action = {
|
||||||
@ -59,7 +59,7 @@ export const actionLoadScene: Action = {
|
|||||||
perform: (
|
perform: (
|
||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
{ elements: loadedElements, appState: loadedAppState }
|
{ elements: loadedElements, appState: loadedAppState },
|
||||||
) => {
|
) => {
|
||||||
return { elements: loadedElements, appState: loadedAppState };
|
return { elements: loadedElements, appState: loadedAppState };
|
||||||
},
|
},
|
||||||
@ -77,5 +77,5 @@ export const actionLoadScene: Action = {
|
|||||||
.catch(err => console.error(err));
|
.catch(err => console.error(err));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
@ -9,7 +9,7 @@ import { AppState } from "../../src/types";
|
|||||||
|
|
||||||
const changeProperty = (
|
const changeProperty = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
callback: (element: ExcalidrawElement) => ExcalidrawElement
|
callback: (element: ExcalidrawElement) => ExcalidrawElement,
|
||||||
) => {
|
) => {
|
||||||
return elements.map(element => {
|
return elements.map(element => {
|
||||||
if (element.isSelected) {
|
if (element.isSelected) {
|
||||||
@ -23,7 +23,7 @@ const getFormValue = function<T>(
|
|||||||
editingElement: AppState["editingElement"],
|
editingElement: AppState["editingElement"],
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
getAttribute: (element: ExcalidrawElement) => T,
|
getAttribute: (element: ExcalidrawElement) => T,
|
||||||
defaultValue?: T
|
defaultValue?: T,
|
||||||
): T | null {
|
): T | null {
|
||||||
return (
|
return (
|
||||||
(editingElement && getAttribute(editingElement)) ??
|
(editingElement && getAttribute(editingElement)) ??
|
||||||
@ -40,9 +40,9 @@ export const actionChangeStrokeColor: Action = {
|
|||||||
elements: changeProperty(elements, el => ({
|
elements: changeProperty(elements, el => ({
|
||||||
...el,
|
...el,
|
||||||
shape: null,
|
shape: null,
|
||||||
strokeColor: value
|
strokeColor: value,
|
||||||
})),
|
})),
|
||||||
appState: { ...appState, currentItemStrokeColor: value }
|
appState: { ...appState, currentItemStrokeColor: value },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
@ -54,12 +54,12 @@ export const actionChangeStrokeColor: Action = {
|
|||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => element.strokeColor,
|
element => element.strokeColor,
|
||||||
appState.currentItemStrokeColor
|
appState.currentItemStrokeColor,
|
||||||
)}
|
)}
|
||||||
onChange={updateData}
|
onChange={updateData}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionChangeBackgroundColor: Action = {
|
export const actionChangeBackgroundColor: Action = {
|
||||||
@ -69,9 +69,9 @@ export const actionChangeBackgroundColor: Action = {
|
|||||||
elements: changeProperty(elements, el => ({
|
elements: changeProperty(elements, el => ({
|
||||||
...el,
|
...el,
|
||||||
shape: null,
|
shape: null,
|
||||||
backgroundColor: value
|
backgroundColor: value,
|
||||||
})),
|
})),
|
||||||
appState: { ...appState, currentItemBackgroundColor: value }
|
appState: { ...appState, currentItemBackgroundColor: value },
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
@ -83,12 +83,12 @@ export const actionChangeBackgroundColor: Action = {
|
|||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => element.backgroundColor,
|
element => element.backgroundColor,
|
||||||
appState.currentItemBackgroundColor
|
appState.currentItemBackgroundColor,
|
||||||
)}
|
)}
|
||||||
onChange={updateData}
|
onChange={updateData}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionChangeFillStyle: Action = {
|
export const actionChangeFillStyle: Action = {
|
||||||
@ -98,8 +98,8 @@ export const actionChangeFillStyle: Action = {
|
|||||||
elements: changeProperty(elements, el => ({
|
elements: changeProperty(elements, el => ({
|
||||||
...el,
|
...el,
|
||||||
shape: null,
|
shape: null,
|
||||||
fillStyle: value
|
fillStyle: value,
|
||||||
}))
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
@ -109,19 +109,19 @@ export const actionChangeFillStyle: Action = {
|
|||||||
options={[
|
options={[
|
||||||
{ value: "solid", text: t("labels.solid") },
|
{ value: "solid", text: t("labels.solid") },
|
||||||
{ value: "hachure", text: t("labels.hachure") },
|
{ value: "hachure", text: t("labels.hachure") },
|
||||||
{ value: "cross-hatch", text: t("labels.crossHatch") }
|
{ value: "cross-hatch", text: t("labels.crossHatch") },
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => element.fillStyle
|
element => element.fillStyle,
|
||||||
)}
|
)}
|
||||||
onChange={value => {
|
onChange={value => {
|
||||||
updateData(value);
|
updateData(value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionChangeStrokeWidth: Action = {
|
export const actionChangeStrokeWidth: Action = {
|
||||||
@ -131,8 +131,8 @@ export const actionChangeStrokeWidth: Action = {
|
|||||||
elements: changeProperty(elements, el => ({
|
elements: changeProperty(elements, el => ({
|
||||||
...el,
|
...el,
|
||||||
shape: null,
|
shape: null,
|
||||||
strokeWidth: value
|
strokeWidth: value,
|
||||||
}))
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
@ -142,17 +142,17 @@ export const actionChangeStrokeWidth: Action = {
|
|||||||
options={[
|
options={[
|
||||||
{ value: 1, text: t("labels.thin") },
|
{ value: 1, text: t("labels.thin") },
|
||||||
{ value: 2, text: t("labels.bold") },
|
{ value: 2, text: t("labels.bold") },
|
||||||
{ value: 4, text: t("labels.extraBold") }
|
{ value: 4, text: t("labels.extraBold") },
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => element.strokeWidth
|
element => element.strokeWidth,
|
||||||
)}
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionChangeSloppiness: Action = {
|
export const actionChangeSloppiness: Action = {
|
||||||
@ -162,8 +162,8 @@ export const actionChangeSloppiness: Action = {
|
|||||||
elements: changeProperty(elements, el => ({
|
elements: changeProperty(elements, el => ({
|
||||||
...el,
|
...el,
|
||||||
shape: null,
|
shape: null,
|
||||||
roughness: value
|
roughness: value,
|
||||||
}))
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
@ -173,17 +173,17 @@ export const actionChangeSloppiness: Action = {
|
|||||||
options={[
|
options={[
|
||||||
{ value: 0, text: t("labels.architect") },
|
{ value: 0, text: t("labels.architect") },
|
||||||
{ value: 1, text: t("labels.artist") },
|
{ value: 1, text: t("labels.artist") },
|
||||||
{ value: 3, text: t("labels.cartoonist") }
|
{ value: 3, text: t("labels.cartoonist") },
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => element.roughness
|
element => element.roughness,
|
||||||
)}
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionChangeOpacity: Action = {
|
export const actionChangeOpacity: Action = {
|
||||||
@ -193,8 +193,8 @@ export const actionChangeOpacity: Action = {
|
|||||||
elements: changeProperty(elements, el => ({
|
elements: changeProperty(elements, el => ({
|
||||||
...el,
|
...el,
|
||||||
shape: null,
|
shape: null,
|
||||||
opacity: value
|
opacity: value,
|
||||||
}))
|
})),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
@ -210,12 +210,12 @@ export const actionChangeOpacity: Action = {
|
|||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => element.opacity,
|
element => element.opacity,
|
||||||
100 /* default opacity */
|
100 /* default opacity */,
|
||||||
) ?? undefined
|
) ?? undefined
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionChangeFontSize: Action = {
|
export const actionChangeFontSize: Action = {
|
||||||
@ -227,14 +227,14 @@ export const actionChangeFontSize: Action = {
|
|||||||
const element: ExcalidrawTextElement = {
|
const element: ExcalidrawTextElement = {
|
||||||
...el,
|
...el,
|
||||||
shape: null,
|
shape: null,
|
||||||
font: `${value}px ${el.font.split("px ")[1]}`
|
font: `${value}px ${el.font.split("px ")[1]}`,
|
||||||
};
|
};
|
||||||
redrawTextBoundingBox(element);
|
redrawTextBoundingBox(element);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
return el;
|
return el;
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
@ -245,17 +245,17 @@ export const actionChangeFontSize: Action = {
|
|||||||
{ value: 16, text: t("labels.small") },
|
{ value: 16, text: t("labels.small") },
|
||||||
{ value: 20, text: t("labels.medium") },
|
{ value: 20, text: t("labels.medium") },
|
||||||
{ value: 28, text: t("labels.large") },
|
{ value: 28, text: t("labels.large") },
|
||||||
{ value: 36, text: t("labels.veryLarge") }
|
{ value: 36, text: t("labels.veryLarge") },
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => isTextElement(element) && +element.font.split("px ")[0]
|
element => isTextElement(element) && +element.font.split("px ")[0],
|
||||||
)}
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionChangeFontFamily: Action = {
|
export const actionChangeFontFamily: Action = {
|
||||||
@ -267,14 +267,14 @@ export const actionChangeFontFamily: Action = {
|
|||||||
const element: ExcalidrawTextElement = {
|
const element: ExcalidrawTextElement = {
|
||||||
...el,
|
...el,
|
||||||
shape: null,
|
shape: null,
|
||||||
font: `${el.font.split("px ")[0]}px ${value}`
|
font: `${el.font.split("px ")[0]}px ${value}`,
|
||||||
};
|
};
|
||||||
redrawTextBoundingBox(element);
|
redrawTextBoundingBox(element);
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
|
|
||||||
return el;
|
return el;
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
PanelComponent: ({ elements, appState, updateData, t }) => (
|
PanelComponent: ({ elements, appState, updateData, t }) => (
|
||||||
@ -284,15 +284,15 @@ export const actionChangeFontFamily: Action = {
|
|||||||
options={[
|
options={[
|
||||||
{ value: "Virgil", text: t("labels.handDrawn") },
|
{ value: "Virgil", text: t("labels.handDrawn") },
|
||||||
{ value: "Helvetica", text: t("labels.normal") },
|
{ value: "Helvetica", text: t("labels.normal") },
|
||||||
{ value: "Cascadia", text: t("labels.code") }
|
{ value: "Cascadia", text: t("labels.code") },
|
||||||
]}
|
]}
|
||||||
value={getFormValue(
|
value={getFormValue(
|
||||||
appState.editingElement,
|
appState.editingElement,
|
||||||
elements,
|
elements,
|
||||||
element => isTextElement(element) && element.font.split("px ")[1]
|
element => isTextElement(element) && element.font.split("px ")[1],
|
||||||
)}
|
)}
|
||||||
onChange={value => updateData(value)}
|
onChange={value => updateData(value)}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
@ -5,9 +5,9 @@ export const actionSelectAll: Action = {
|
|||||||
name: "selectAll",
|
name: "selectAll",
|
||||||
perform: elements => {
|
perform: elements => {
|
||||||
return {
|
return {
|
||||||
elements: elements.map(elem => ({ ...elem, isSelected: true }))
|
elements: elements.map(elem => ({ ...elem, isSelected: true })),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.selectAll",
|
contextItemLabel: "labels.selectAll",
|
||||||
keyTest: event => event[KEYS.META] && event.code === "KeyA"
|
keyTest: event => event[KEYS.META] && event.code === "KeyA",
|
||||||
};
|
};
|
||||||
|
@ -15,7 +15,7 @@ export const actionCopyStyles: Action = {
|
|||||||
},
|
},
|
||||||
contextItemLabel: "labels.copyStyles",
|
contextItemLabel: "labels.copyStyles",
|
||||||
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyC",
|
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyC",
|
||||||
contextMenuOrder: 0
|
contextMenuOrder: 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionPasteStyles: Action = {
|
export const actionPasteStyles: Action = {
|
||||||
@ -33,7 +33,7 @@ export const actionPasteStyles: Action = {
|
|||||||
strokeColor: pastedElement?.strokeColor,
|
strokeColor: pastedElement?.strokeColor,
|
||||||
fillStyle: pastedElement?.fillStyle,
|
fillStyle: pastedElement?.fillStyle,
|
||||||
opacity: pastedElement?.opacity,
|
opacity: pastedElement?.opacity,
|
||||||
roughness: pastedElement?.roughness
|
roughness: pastedElement?.roughness,
|
||||||
};
|
};
|
||||||
if (isTextElement(newElement)) {
|
if (isTextElement(newElement)) {
|
||||||
newElement.font = pastedElement?.font;
|
newElement.font = pastedElement?.font;
|
||||||
@ -42,10 +42,10 @@ export const actionPasteStyles: Action = {
|
|||||||
return newElement;
|
return newElement;
|
||||||
}
|
}
|
||||||
return element;
|
return element;
|
||||||
})
|
}),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.pasteStyles",
|
contextItemLabel: "labels.pasteStyles",
|
||||||
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyV",
|
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyV",
|
||||||
contextMenuOrder: 1
|
contextMenuOrder: 1,
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
moveOneLeft,
|
moveOneLeft,
|
||||||
moveOneRight,
|
moveOneRight,
|
||||||
moveAllLeft,
|
moveAllLeft,
|
||||||
moveAllRight
|
moveAllRight,
|
||||||
} from "../zindex";
|
} from "../zindex";
|
||||||
import { getSelectedIndices } from "../scene";
|
import { getSelectedIndices } from "../scene";
|
||||||
import { KEYS } from "../keys";
|
import { KEYS } from "../keys";
|
||||||
@ -13,13 +13,13 @@ export const actionSendBackward: Action = {
|
|||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
return {
|
return {
|
||||||
elements: moveOneLeft([...elements], getSelectedIndices(elements)),
|
elements: moveOneLeft([...elements], getSelectedIndices(elements)),
|
||||||
appState
|
appState,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.sendBackward",
|
contextItemLabel: "labels.sendBackward",
|
||||||
keyPriority: 40,
|
keyPriority: 40,
|
||||||
keyTest: event =>
|
keyTest: event =>
|
||||||
event[KEYS.META] && event.shiftKey && event.altKey && event.code === "KeyB"
|
event[KEYS.META] && event.shiftKey && event.altKey && event.code === "KeyB",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionBringForward: Action = {
|
export const actionBringForward: Action = {
|
||||||
@ -27,13 +27,13 @@ export const actionBringForward: Action = {
|
|||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
return {
|
return {
|
||||||
elements: moveOneRight([...elements], getSelectedIndices(elements)),
|
elements: moveOneRight([...elements], getSelectedIndices(elements)),
|
||||||
appState
|
appState,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.bringForward",
|
contextItemLabel: "labels.bringForward",
|
||||||
keyPriority: 40,
|
keyPriority: 40,
|
||||||
keyTest: event =>
|
keyTest: event =>
|
||||||
event[KEYS.META] && event.shiftKey && event.altKey && event.code === "KeyF"
|
event[KEYS.META] && event.shiftKey && event.altKey && event.code === "KeyF",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionSendToBack: Action = {
|
export const actionSendToBack: Action = {
|
||||||
@ -41,11 +41,11 @@ export const actionSendToBack: Action = {
|
|||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
return {
|
return {
|
||||||
elements: moveAllLeft([...elements], getSelectedIndices(elements)),
|
elements: moveAllLeft([...elements], getSelectedIndices(elements)),
|
||||||
appState
|
appState,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.sendToBack",
|
contextItemLabel: "labels.sendToBack",
|
||||||
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyB"
|
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyB",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const actionBringToFront: Action = {
|
export const actionBringToFront: Action = {
|
||||||
@ -53,9 +53,9 @@ export const actionBringToFront: Action = {
|
|||||||
perform: (elements, appState) => {
|
perform: (elements, appState) => {
|
||||||
return {
|
return {
|
||||||
elements: moveAllRight([...elements], getSelectedIndices(elements)),
|
elements: moveAllRight([...elements], getSelectedIndices(elements)),
|
||||||
appState
|
appState,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
contextItemLabel: "labels.bringToFront",
|
contextItemLabel: "labels.bringToFront",
|
||||||
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyF"
|
keyTest: event => event[KEYS.META] && event.shiftKey && event.code === "KeyF",
|
||||||
};
|
};
|
||||||
|
@ -4,7 +4,7 @@ export {
|
|||||||
actionBringForward,
|
actionBringForward,
|
||||||
actionBringToFront,
|
actionBringToFront,
|
||||||
actionSendBackward,
|
actionSendBackward,
|
||||||
actionSendToBack
|
actionSendToBack,
|
||||||
} from "./actionZindex";
|
} from "./actionZindex";
|
||||||
export { actionSelectAll } from "./actionSelectAll";
|
export { actionSelectAll } from "./actionSelectAll";
|
||||||
export {
|
export {
|
||||||
@ -15,19 +15,19 @@ export {
|
|||||||
actionChangeSloppiness,
|
actionChangeSloppiness,
|
||||||
actionChangeOpacity,
|
actionChangeOpacity,
|
||||||
actionChangeFontSize,
|
actionChangeFontSize,
|
||||||
actionChangeFontFamily
|
actionChangeFontFamily,
|
||||||
} from "./actionProperties";
|
} from "./actionProperties";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
actionChangeViewBackgroundColor,
|
actionChangeViewBackgroundColor,
|
||||||
actionClearCanvas
|
actionClearCanvas,
|
||||||
} from "./actionCanvas";
|
} from "./actionCanvas";
|
||||||
|
|
||||||
export {
|
export {
|
||||||
actionChangeProjectName,
|
actionChangeProjectName,
|
||||||
actionChangeExportBackground,
|
actionChangeExportBackground,
|
||||||
actionSaveScene,
|
actionSaveScene,
|
||||||
actionLoadScene
|
actionLoadScene,
|
||||||
} from "./actionExport";
|
} from "./actionExport";
|
||||||
|
|
||||||
export { actionCopyStyles, actionPasteStyles } from "./actionStyles";
|
export { actionCopyStyles, actionPasteStyles } from "./actionStyles";
|
||||||
|
@ -3,7 +3,7 @@ import {
|
|||||||
Action,
|
Action,
|
||||||
ActionsManagerInterface,
|
ActionsManagerInterface,
|
||||||
UpdaterFn,
|
UpdaterFn,
|
||||||
ActionFilterFn
|
ActionFilterFn,
|
||||||
} from "./types";
|
} from "./types";
|
||||||
import { ExcalidrawElement } from "../element/types";
|
import { ExcalidrawElement } from "../element/types";
|
||||||
import { AppState } from "../types";
|
import { AppState } from "../types";
|
||||||
@ -17,7 +17,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
| null = null;
|
| null = null;
|
||||||
|
|
||||||
setUpdater(
|
setUpdater(
|
||||||
updater: (elements: ExcalidrawElement[], appState: AppState) => void
|
updater: (elements: ExcalidrawElement[], appState: AppState) => void,
|
||||||
) {
|
) {
|
||||||
this.updater = updater;
|
this.updater = updater;
|
||||||
}
|
}
|
||||||
@ -29,12 +29,12 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
handleKeyDown(
|
handleKeyDown(
|
||||||
event: KeyboardEvent,
|
event: KeyboardEvent,
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState
|
appState: AppState,
|
||||||
) {
|
) {
|
||||||
const data = Object.values(this.actions)
|
const data = Object.values(this.actions)
|
||||||
.sort((a, b) => (b.keyPriority || 0) - (a.keyPriority || 0))
|
.sort((a, b) => (b.keyPriority || 0) - (a.keyPriority || 0))
|
||||||
.filter(
|
.filter(
|
||||||
action => action.keyTest && action.keyTest(event, elements, appState)
|
action => action.keyTest && action.keyTest(event, elements, appState),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (data.length === 0) return {};
|
if (data.length === 0) return {};
|
||||||
@ -48,7 +48,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
appState: AppState,
|
appState: AppState,
|
||||||
updater: UpdaterFn,
|
updater: UpdaterFn,
|
||||||
actionFilter: ActionFilterFn = action => action,
|
actionFilter: ActionFilterFn = action => action,
|
||||||
t?: TFunction
|
t?: TFunction,
|
||||||
) {
|
) {
|
||||||
return Object.values(this.actions)
|
return Object.values(this.actions)
|
||||||
.filter(actionFilter)
|
.filter(actionFilter)
|
||||||
@ -56,7 +56,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
.sort(
|
.sort(
|
||||||
(a, b) =>
|
(a, b) =>
|
||||||
(a.contextMenuOrder !== undefined ? a.contextMenuOrder : 999) -
|
(a.contextMenuOrder !== undefined ? a.contextMenuOrder : 999) -
|
||||||
(b.contextMenuOrder !== undefined ? b.contextMenuOrder : 999)
|
(b.contextMenuOrder !== undefined ? b.contextMenuOrder : 999),
|
||||||
)
|
)
|
||||||
.map(action => ({
|
.map(action => ({
|
||||||
label:
|
label:
|
||||||
@ -65,7 +65,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
: action.contextItemLabel!,
|
: action.contextItemLabel!,
|
||||||
action: () => {
|
action: () => {
|
||||||
updater(action.perform(elements, appState, null));
|
updater(action.perform(elements, appState, null));
|
||||||
}
|
},
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,7 +74,7 @@ export class ActionManager implements ActionsManagerInterface {
|
|||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
updater: UpdaterFn,
|
updater: UpdaterFn,
|
||||||
t: TFunction
|
t: TFunction,
|
||||||
) {
|
) {
|
||||||
if (this.actions[name] && "PanelComponent" in this.actions[name]) {
|
if (this.actions[name] && "PanelComponent" in this.actions[name]) {
|
||||||
const action = this.actions[name];
|
const action = this.actions[name];
|
||||||
|
@ -11,7 +11,7 @@ export type ActionResult = {
|
|||||||
type ActionFn = (
|
type ActionFn = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
formData: any
|
formData: any,
|
||||||
) => ActionResult;
|
) => ActionResult;
|
||||||
|
|
||||||
export type UpdaterFn = (res: ActionResult) => void;
|
export type UpdaterFn = (res: ActionResult) => void;
|
||||||
@ -30,7 +30,7 @@ export interface Action {
|
|||||||
keyTest?: (
|
keyTest?: (
|
||||||
event: KeyboardEvent,
|
event: KeyboardEvent,
|
||||||
elements?: readonly ExcalidrawElement[],
|
elements?: readonly ExcalidrawElement[],
|
||||||
appState?: AppState
|
appState?: AppState,
|
||||||
) => boolean;
|
) => boolean;
|
||||||
contextItemLabel?: string;
|
contextItemLabel?: string;
|
||||||
contextMenuOrder?: number;
|
contextMenuOrder?: number;
|
||||||
@ -44,19 +44,19 @@ export interface ActionsManagerInterface {
|
|||||||
handleKeyDown: (
|
handleKeyDown: (
|
||||||
event: KeyboardEvent,
|
event: KeyboardEvent,
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState
|
appState: AppState,
|
||||||
) => ActionResult | {};
|
) => ActionResult | {};
|
||||||
getContextMenuItems: (
|
getContextMenuItems: (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
updater: UpdaterFn,
|
updater: UpdaterFn,
|
||||||
actionFilter: ActionFilterFn
|
actionFilter: ActionFilterFn,
|
||||||
) => { label: string; action: () => void }[];
|
) => { label: string; action: () => void }[];
|
||||||
renderAction: (
|
renderAction: (
|
||||||
name: string,
|
name: string,
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState,
|
appState: AppState,
|
||||||
updater: UpdaterFn,
|
updater: UpdaterFn,
|
||||||
t: TFunction
|
t: TFunction,
|
||||||
) => React.ReactElement | null;
|
) => React.ReactElement | null;
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,6 @@ export function getDefaultAppState(): AppState {
|
|||||||
scrollY: 0,
|
scrollY: 0,
|
||||||
cursorX: 0,
|
cursorX: 0,
|
||||||
cursorY: 0,
|
cursorY: 0,
|
||||||
name: DEFAULT_PROJECT_NAME
|
name: DEFAULT_PROJECT_NAME,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
export function ButtonSelect<T>({
|
export function ButtonSelect<T>({
|
||||||
options,
|
options,
|
||||||
value,
|
value,
|
||||||
onChange
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
options: { value: T; text: string }[];
|
options: { value: T; text: string }[];
|
||||||
value: T | null;
|
value: T | null;
|
||||||
|
@ -9,7 +9,7 @@ import "./ColorPicker.css";
|
|||||||
const Picker = function({
|
const Picker = function({
|
||||||
colors,
|
colors,
|
||||||
color,
|
color,
|
||||||
onChange
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
colors: string[];
|
colors: string[];
|
||||||
color: string | null;
|
color: string | null;
|
||||||
@ -53,7 +53,7 @@ const Picker = function({
|
|||||||
|
|
||||||
function ColorInput({
|
function ColorInput({
|
||||||
color,
|
color,
|
||||||
onChange
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
color: string | null;
|
color: string | null;
|
||||||
onChange: (color: string) => void;
|
onChange: (color: string) => void;
|
||||||
@ -90,7 +90,7 @@ function ColorInput({
|
|||||||
export function ColorPicker({
|
export function ColorPicker({
|
||||||
type,
|
type,
|
||||||
color,
|
color,
|
||||||
onChange
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
type: "canvasBackground" | "elementBackground" | "elementStroke";
|
||||||
color: string | null;
|
color: string | null;
|
||||||
@ -149,7 +149,7 @@ const colors = {
|
|||||||
"#ebfbee",
|
"#ebfbee",
|
||||||
"#f4fce3",
|
"#f4fce3",
|
||||||
"#fff9db",
|
"#fff9db",
|
||||||
"#fff4e6"
|
"#fff4e6",
|
||||||
],
|
],
|
||||||
// Shade 6
|
// Shade 6
|
||||||
elementBackground: [
|
elementBackground: [
|
||||||
@ -167,7 +167,7 @@ const colors = {
|
|||||||
"#40c057",
|
"#40c057",
|
||||||
"#82c91e",
|
"#82c91e",
|
||||||
"#fab005",
|
"#fab005",
|
||||||
"#fd7e14"
|
"#fd7e14",
|
||||||
],
|
],
|
||||||
// Shade 9
|
// Shade 9
|
||||||
elementStroke: [
|
elementStroke: [
|
||||||
@ -185,6 +185,6 @@ const colors = {
|
|||||||
"#2b8a3e",
|
"#2b8a3e",
|
||||||
"#5c940d",
|
"#5c940d",
|
||||||
"#e67700",
|
"#e67700",
|
||||||
"#d9480f"
|
"#d9480f",
|
||||||
]
|
],
|
||||||
};
|
};
|
||||||
|
@ -83,8 +83,8 @@ export default {
|
|||||||
options={options}
|
options={options}
|
||||||
onCloseRequest={handleClose}
|
onCloseRequest={handleClose}
|
||||||
/>,
|
/>,
|
||||||
getContextMenuNode()
|
getContextMenuNode(),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
@ -25,7 +25,7 @@ const defaultScale = scales.includes(devicePixelRatio) ? devicePixelRatio : 1;
|
|||||||
|
|
||||||
type ExportCB = (
|
type ExportCB = (
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
scale?: number
|
scale?: number,
|
||||||
) => void;
|
) => void;
|
||||||
|
|
||||||
export function ExportDialog({
|
export function ExportDialog({
|
||||||
@ -36,7 +36,7 @@ export function ExportDialog({
|
|||||||
syncActionResult,
|
syncActionResult,
|
||||||
onExportToPng,
|
onExportToPng,
|
||||||
onExportToClipboard,
|
onExportToClipboard,
|
||||||
onExportToBackend
|
onExportToBackend,
|
||||||
}: {
|
}: {
|
||||||
appState: AppState;
|
appState: AppState;
|
||||||
elements: readonly ExcalidrawElement[];
|
elements: readonly ExcalidrawElement[];
|
||||||
@ -69,7 +69,7 @@ export function ExportDialog({
|
|||||||
exportBackground,
|
exportBackground,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
scale
|
scale,
|
||||||
});
|
});
|
||||||
previewNode?.appendChild(canvas);
|
previewNode?.appendChild(canvas);
|
||||||
return () => {
|
return () => {
|
||||||
@ -81,7 +81,7 @@ export function ExportDialog({
|
|||||||
exportBackground,
|
exportBackground,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
scale
|
scale,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
function handleClose() {
|
function handleClose() {
|
||||||
@ -141,7 +141,7 @@ export function ExportDialog({
|
|||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
syncActionResult,
|
syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
<Stack.Col gap={1}>
|
<Stack.Col gap={1}>
|
||||||
<div className="ExportDialog__scales">
|
<div className="ExportDialog__scales">
|
||||||
@ -165,7 +165,7 @@ export function ExportDialog({
|
|||||||
elements,
|
elements,
|
||||||
appState,
|
appState,
|
||||||
syncActionResult,
|
syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
{someElementIsSelected && (
|
{someElementIsSelected && (
|
||||||
<div>
|
<div>
|
||||||
|
@ -9,7 +9,7 @@ type FixedSideContainerProps = {
|
|||||||
|
|
||||||
export function FixedSideContainer({
|
export function FixedSideContainer({
|
||||||
children,
|
children,
|
||||||
side
|
side,
|
||||||
}: FixedSideContainerProps) {
|
}: FixedSideContainerProps) {
|
||||||
return (
|
return (
|
||||||
<div className={"FixedSideContainer FixedSideContainer_side_" + side}>
|
<div className={"FixedSideContainer FixedSideContainer_side_" + side}>
|
||||||
|
@ -3,7 +3,7 @@ import React from "react";
|
|||||||
export function LanguageList<T>({
|
export function LanguageList<T>({
|
||||||
onClick,
|
onClick,
|
||||||
languages,
|
languages,
|
||||||
currentLanguage
|
currentLanguage,
|
||||||
}: {
|
}: {
|
||||||
languages: { lng: string; label: string }[];
|
languages: { lng: string; label: string }[];
|
||||||
onClick: (value: string) => void;
|
onClick: (value: string) => void;
|
||||||
|
@ -35,7 +35,7 @@ const ICONS = {
|
|||||||
>
|
>
|
||||||
<path d="M1728 576v256q0 26-19 45t-45 19h-64q-26 0-45-19t-19-45v-256q0-106-75-181t-181-75-181 75-75 181v192h96q40 0 68 28t28 68v576q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-576q0-40 28-68t68-28h672v-192q0-185 131.5-316.5t316.5-131.5 316.5 131.5 131.5 316.5z" />
|
<path d="M1728 576v256q0 26-19 45t-45 19h-64q-26 0-45-19t-19-45v-256q0-106-75-181t-181-75-181 75-75 181v192h96q40 0 68 28t28 68v576q0 40-28 68t-68 28h-960q-40 0-68-28t-28-68v-576q0-40 28-68t68-28h672v-192q0-185 131.5-316.5t316.5-131.5 316.5 131.5 131.5 316.5z" />
|
||||||
</svg>
|
</svg>
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
|
|
||||||
export function LockIcon(props: LockIconProps) {
|
export function LockIcon(props: LockIconProps) {
|
||||||
|
@ -16,7 +16,7 @@ export function Modal(props: {
|
|||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
</div>,
|
</div>,
|
||||||
modalRoot
|
modalRoot,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -14,7 +14,7 @@ export function Popover({
|
|||||||
left,
|
left,
|
||||||
top,
|
top,
|
||||||
onCloseRequest,
|
onCloseRequest,
|
||||||
fitInViewport = false
|
fitInViewport = false,
|
||||||
}: Props) {
|
}: Props) {
|
||||||
const popoverRef = useRef<HTMLDivElement>(null);
|
const popoverRef = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ function RowStack({ children, gap, align, justifyContent }: StackProps) {
|
|||||||
{
|
{
|
||||||
"--gap": gap,
|
"--gap": gap,
|
||||||
alignItems: align,
|
alignItems: align,
|
||||||
justifyContent
|
justifyContent,
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -34,7 +34,7 @@ function ColStack({ children, gap, align, justifyContent }: StackProps) {
|
|||||||
{
|
{
|
||||||
"--gap": gap,
|
"--gap": gap,
|
||||||
justifyItems: align,
|
justifyItems: align,
|
||||||
justifyContent
|
justifyContent,
|
||||||
} as React.CSSProperties
|
} as React.CSSProperties
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
@ -45,5 +45,5 @@ function ColStack({ children, gap, align, justifyContent }: StackProps) {
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
Row: RowStack,
|
Row: RowStack,
|
||||||
Col: ColStack
|
Col: ColStack,
|
||||||
};
|
};
|
||||||
|
@ -13,7 +13,7 @@ const _ce = ({ x, y, w, h }: { x: number; y: number; w: number; h: number }) =>
|
|||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
width: w,
|
width: w,
|
||||||
height: h
|
height: h,
|
||||||
} as ExcalidrawElement);
|
} as ExcalidrawElement);
|
||||||
|
|
||||||
describe("getElementAbsoluteCoords", () => {
|
describe("getElementAbsoluteCoords", () => {
|
||||||
@ -29,14 +29,14 @@ describe("getElementAbsoluteCoords", () => {
|
|||||||
|
|
||||||
it("test x2 coordinate if width is positive or zero", () => {
|
it("test x2 coordinate if width is positive or zero", () => {
|
||||||
const [, , x2] = getElementAbsoluteCoords(
|
const [, , x2] = getElementAbsoluteCoords(
|
||||||
_ce({ x: 10, y: 0, w: 10, h: 0 })
|
_ce({ x: 10, y: 0, w: 10, h: 0 }),
|
||||||
);
|
);
|
||||||
expect(x2).toEqual(20);
|
expect(x2).toEqual(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test x2 coordinate if width is negative", () => {
|
it("test x2 coordinate if width is negative", () => {
|
||||||
const [, , x2] = getElementAbsoluteCoords(
|
const [, , x2] = getElementAbsoluteCoords(
|
||||||
_ce({ x: 10, y: 0, w: -10, h: 0 })
|
_ce({ x: 10, y: 0, w: -10, h: 0 }),
|
||||||
);
|
);
|
||||||
expect(x2).toEqual(10);
|
expect(x2).toEqual(10);
|
||||||
});
|
});
|
||||||
@ -53,14 +53,14 @@ describe("getElementAbsoluteCoords", () => {
|
|||||||
|
|
||||||
it("test y2 coordinate if height is positive or zero", () => {
|
it("test y2 coordinate if height is positive or zero", () => {
|
||||||
const [, , , y2] = getElementAbsoluteCoords(
|
const [, , , y2] = getElementAbsoluteCoords(
|
||||||
_ce({ x: 0, y: 10, w: 0, h: 10 })
|
_ce({ x: 0, y: 10, w: 0, h: 10 }),
|
||||||
);
|
);
|
||||||
expect(y2).toEqual(20);
|
expect(y2).toEqual(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("test y2 coordinate if height is negative", () => {
|
it("test y2 coordinate if height is negative", () => {
|
||||||
const [, , , y2] = getElementAbsoluteCoords(
|
const [, , , y2] = getElementAbsoluteCoords(
|
||||||
_ce({ x: 0, y: 10, w: 0, h: -10 })
|
_ce({ x: 0, y: 10, w: 0, h: -10 }),
|
||||||
);
|
);
|
||||||
expect(y2).toEqual(10);
|
expect(y2).toEqual(10);
|
||||||
});
|
});
|
||||||
|
@ -10,7 +10,7 @@ export function getElementAbsoluteCoords(element: ExcalidrawElement) {
|
|||||||
element.width >= 0 ? element.x : element.x + element.width, // x1
|
element.width >= 0 ? element.x : element.x + element.width, // x1
|
||||||
element.height >= 0 ? element.y : element.y + element.height, // y1
|
element.height >= 0 ? element.y : element.y + element.height, // y1
|
||||||
element.width >= 0 ? element.x + element.width : element.x, // x2
|
element.width >= 0 ? element.x + element.width : element.x, // x2
|
||||||
element.height >= 0 ? element.y + element.height : element.y // y2
|
element.height >= 0 ? element.y + element.height : element.y, // y2
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ import {
|
|||||||
getArrowPoints,
|
getArrowPoints,
|
||||||
getDiamondPoints,
|
getDiamondPoints,
|
||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getLinePoints
|
getLinePoints,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
|
|
||||||
function isElementDraggableFromInside(element: ExcalidrawElement): boolean {
|
function isElementDraggableFromInside(element: ExcalidrawElement): boolean {
|
||||||
@ -15,7 +15,7 @@ function isElementDraggableFromInside(element: ExcalidrawElement): boolean {
|
|||||||
export function hitTest(
|
export function hitTest(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number,
|
||||||
): boolean {
|
): boolean {
|
||||||
// For shapes that are composed of lines, we only enable point-selection when the distance
|
// For shapes that are composed of lines, we only enable point-selection when the distance
|
||||||
// of the click is less than x pixels of any of the lines that the shape is composed of
|
// of the click is less than x pixels of any of the lines that the shape is composed of
|
||||||
@ -95,7 +95,7 @@ export function hitTest(
|
|||||||
bottomX,
|
bottomX,
|
||||||
bottomY,
|
bottomY,
|
||||||
leftX,
|
leftX,
|
||||||
leftY
|
leftY,
|
||||||
] = getDiamondPoints(element);
|
] = getDiamondPoints(element);
|
||||||
|
|
||||||
if (isElementDraggableFromInside(element)) {
|
if (isElementDraggableFromInside(element)) {
|
||||||
|
@ -5,7 +5,7 @@ type Sides = "n" | "s" | "w" | "e" | "nw" | "ne" | "sw" | "se";
|
|||||||
|
|
||||||
export function handlerRectangles(
|
export function handlerRectangles(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
{ scrollX, scrollY }: SceneScroll
|
{ scrollX, scrollY }: SceneScroll,
|
||||||
) {
|
) {
|
||||||
const elementX1 = element.x;
|
const elementX1 = element.x;
|
||||||
const elementX2 = element.x + element.width;
|
const elementX2 = element.x + element.width;
|
||||||
@ -24,14 +24,14 @@ export function handlerRectangles(
|
|||||||
elementX1 + (elementX2 - elementX1) / 2 + scrollX - 4,
|
elementX1 + (elementX2 - elementX1) / 2 + scrollX - 4,
|
||||||
elementY1 - margin + scrollY + marginY,
|
elementY1 - margin + scrollY + marginY,
|
||||||
8,
|
8,
|
||||||
8
|
8,
|
||||||
];
|
];
|
||||||
|
|
||||||
handlers["s"] = [
|
handlers["s"] = [
|
||||||
elementX1 + (elementX2 - elementX1) / 2 + scrollX - 4,
|
elementX1 + (elementX2 - elementX1) / 2 + scrollX - 4,
|
||||||
elementY2 - margin + scrollY - marginY,
|
elementY2 - margin + scrollY - marginY,
|
||||||
8,
|
8,
|
||||||
8
|
8,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -40,14 +40,14 @@ export function handlerRectangles(
|
|||||||
elementX1 - margin + scrollX + marginX,
|
elementX1 - margin + scrollX + marginX,
|
||||||
elementY1 + (elementY2 - elementY1) / 2 + scrollY - 4,
|
elementY1 + (elementY2 - elementY1) / 2 + scrollY - 4,
|
||||||
8,
|
8,
|
||||||
8
|
8,
|
||||||
];
|
];
|
||||||
|
|
||||||
handlers["e"] = [
|
handlers["e"] = [
|
||||||
elementX2 - margin + scrollX - marginX,
|
elementX2 - margin + scrollX - marginX,
|
||||||
elementY1 + (elementY2 - elementY1) / 2 + scrollY - 4,
|
elementY1 + (elementY2 - elementY1) / 2 + scrollY - 4,
|
||||||
8,
|
8,
|
||||||
8
|
8,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -55,31 +55,31 @@ export function handlerRectangles(
|
|||||||
elementX1 - margin + scrollX + marginX,
|
elementX1 - margin + scrollX + marginX,
|
||||||
elementY1 - margin + scrollY + marginY,
|
elementY1 - margin + scrollY + marginY,
|
||||||
8,
|
8,
|
||||||
8
|
8,
|
||||||
]; // nw
|
]; // nw
|
||||||
handlers["ne"] = [
|
handlers["ne"] = [
|
||||||
elementX2 - margin + scrollX - marginX,
|
elementX2 - margin + scrollX - marginX,
|
||||||
elementY1 - margin + scrollY + marginY,
|
elementY1 - margin + scrollY + marginY,
|
||||||
8,
|
8,
|
||||||
8
|
8,
|
||||||
]; // ne
|
]; // ne
|
||||||
handlers["sw"] = [
|
handlers["sw"] = [
|
||||||
elementX1 - margin + scrollX + marginX,
|
elementX1 - margin + scrollX + marginX,
|
||||||
elementY2 - margin + scrollY - marginY,
|
elementY2 - margin + scrollY - marginY,
|
||||||
8,
|
8,
|
||||||
8
|
8,
|
||||||
]; // sw
|
]; // sw
|
||||||
handlers["se"] = [
|
handlers["se"] = [
|
||||||
elementX2 - margin + scrollX - marginX,
|
elementX2 - margin + scrollX - marginX,
|
||||||
elementY2 - margin + scrollY - marginY,
|
elementY2 - margin + scrollY - marginY,
|
||||||
8,
|
8,
|
||||||
8
|
8,
|
||||||
]; // se
|
]; // se
|
||||||
|
|
||||||
if (element.type === "arrow" || element.type === "line") {
|
if (element.type === "arrow" || element.type === "line") {
|
||||||
return {
|
return {
|
||||||
nw: handlers.nw,
|
nw: handlers.nw,
|
||||||
se: handlers.se
|
se: handlers.se,
|
||||||
} as typeof handlers;
|
} as typeof handlers;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -3,7 +3,7 @@ export {
|
|||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getDiamondPoints,
|
getDiamondPoints,
|
||||||
getArrowPoints,
|
getArrowPoints,
|
||||||
getLinePoints
|
getLinePoints,
|
||||||
} from "./bounds";
|
} from "./bounds";
|
||||||
|
|
||||||
export { handlerRectangles } from "./handlerRectangles";
|
export { handlerRectangles } from "./handlerRectangles";
|
||||||
@ -15,5 +15,5 @@ export { redrawTextBoundingBox } from "./textElement";
|
|||||||
export {
|
export {
|
||||||
getPerfectElementSize,
|
getPerfectElementSize,
|
||||||
isInvisiblySmallElement,
|
isInvisiblySmallElement,
|
||||||
resizePerfectLineForNWHandler
|
resizePerfectLineForNWHandler,
|
||||||
} from "./sizeHelpers";
|
} from "./sizeHelpers";
|
||||||
|
@ -16,7 +16,7 @@ export function newElement(
|
|||||||
roughness: number,
|
roughness: number,
|
||||||
opacity: number,
|
opacity: number,
|
||||||
width = 0,
|
width = 0,
|
||||||
height = 0
|
height = 0,
|
||||||
) {
|
) {
|
||||||
const element = {
|
const element = {
|
||||||
id: nanoid(),
|
id: nanoid(),
|
||||||
@ -33,7 +33,7 @@ export function newElement(
|
|||||||
opacity,
|
opacity,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
seed: randomSeed(),
|
seed: randomSeed(),
|
||||||
shape: null as Drawable | Drawable[] | null
|
shape: null as Drawable | Drawable[] | null,
|
||||||
};
|
};
|
||||||
return element;
|
return element;
|
||||||
}
|
}
|
||||||
@ -41,7 +41,7 @@ export function newElement(
|
|||||||
export function newTextElement(
|
export function newTextElement(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
text: string,
|
text: string,
|
||||||
font: string
|
font: string,
|
||||||
) {
|
) {
|
||||||
const metrics = measureText(text, font);
|
const metrics = measureText(text, font);
|
||||||
const textElement: ExcalidrawTextElement = {
|
const textElement: ExcalidrawTextElement = {
|
||||||
@ -54,7 +54,7 @@ export function newTextElement(
|
|||||||
y: element.y - metrics.height / 2,
|
y: element.y - metrics.height / 2,
|
||||||
width: metrics.width,
|
width: metrics.width,
|
||||||
height: metrics.height,
|
height: metrics.height,
|
||||||
baseline: metrics.baseline
|
baseline: metrics.baseline,
|
||||||
};
|
};
|
||||||
|
|
||||||
return textElement;
|
return textElement;
|
||||||
|
@ -9,7 +9,7 @@ export function resizeTest(
|
|||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
x: number,
|
x: number,
|
||||||
y: number,
|
y: number,
|
||||||
{ scrollX, scrollY }: SceneScroll
|
{ scrollX, scrollY }: SceneScroll,
|
||||||
): HandlerRectanglesRet | false {
|
): HandlerRectanglesRet | false {
|
||||||
if (!element.isSelected || element.type === "text") return false;
|
if (!element.isSelected || element.type === "text") return false;
|
||||||
|
|
||||||
@ -36,7 +36,7 @@ export function resizeTest(
|
|||||||
export function getElementWithResizeHandler(
|
export function getElementWithResizeHandler(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
{ x, y }: { x: number; y: number },
|
{ x, y }: { x: number; y: number },
|
||||||
{ scrollX, scrollY }: SceneScroll
|
{ scrollX, scrollY }: SceneScroll,
|
||||||
) {
|
) {
|
||||||
return elements.reduce((result, element) => {
|
return elements.reduce((result, element) => {
|
||||||
if (result) {
|
if (result) {
|
||||||
@ -44,7 +44,7 @@ export function getElementWithResizeHandler(
|
|||||||
}
|
}
|
||||||
const resizeHandle = resizeTest(element, x, y, {
|
const resizeHandle = resizeTest(element, x, y, {
|
||||||
scrollX,
|
scrollX,
|
||||||
scrollY
|
scrollY,
|
||||||
});
|
});
|
||||||
return resizeHandle ? { element, resizeHandle } : null;
|
return resizeHandle ? { element, resizeHandle } : null;
|
||||||
}, null as { element: ExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null);
|
}, null as { element: ExcalidrawElement; resizeHandle: ReturnType<typeof resizeTest> } | null);
|
||||||
|
@ -10,7 +10,7 @@ export function isInvisiblySmallElement(element: ExcalidrawElement): boolean {
|
|||||||
export function getPerfectElementSize(
|
export function getPerfectElementSize(
|
||||||
elementType: string,
|
elementType: string,
|
||||||
width: number,
|
width: number,
|
||||||
height: number
|
height: number,
|
||||||
): { width: number; height: number } {
|
): { width: number; height: number } {
|
||||||
const absWidth = Math.abs(width);
|
const absWidth = Math.abs(width);
|
||||||
const absHeight = Math.abs(height);
|
const absHeight = Math.abs(height);
|
||||||
@ -33,7 +33,7 @@ export function getPerfectElementSize(
|
|||||||
export function resizePerfectLineForNWHandler(
|
export function resizePerfectLineForNWHandler(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number,
|
||||||
) {
|
) {
|
||||||
const anchorX = element.x + element.width;
|
const anchorX = element.x + element.width;
|
||||||
const anchorY = element.y + element.height;
|
const anchorY = element.y + element.height;
|
||||||
|
@ -22,7 +22,7 @@ export function textWysiwyg({
|
|||||||
y,
|
y,
|
||||||
strokeColor,
|
strokeColor,
|
||||||
font,
|
font,
|
||||||
onSubmit
|
onSubmit,
|
||||||
}: TextWysiwygParams) {
|
}: TextWysiwygParams) {
|
||||||
// Using contenteditable here as it has dynamic width.
|
// Using contenteditable here as it has dynamic width.
|
||||||
// But this solution has an issue — it allows to paste
|
// But this solution has an issue — it allows to paste
|
||||||
@ -45,7 +45,7 @@ export function textWysiwyg({
|
|||||||
padding: "4px",
|
padding: "4px",
|
||||||
outline: "transparent",
|
outline: "transparent",
|
||||||
whiteSpace: "nowrap",
|
whiteSpace: "nowrap",
|
||||||
minHeight: "1em"
|
minHeight: "1em",
|
||||||
});
|
});
|
||||||
|
|
||||||
editable.onkeydown = ev => {
|
editable.onkeydown = ev => {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { ExcalidrawElement, ExcalidrawTextElement } from "./types";
|
import { ExcalidrawElement, ExcalidrawTextElement } from "./types";
|
||||||
|
|
||||||
export function isTextElement(
|
export function isTextElement(
|
||||||
element: ExcalidrawElement
|
element: ExcalidrawElement,
|
||||||
): element is ExcalidrawTextElement {
|
): element is ExcalidrawTextElement {
|
||||||
return element.type === "text";
|
return element.type === "text";
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,14 @@ class SceneHistory {
|
|||||||
|
|
||||||
generateCurrentEntry(
|
generateCurrentEntry(
|
||||||
appState: Partial<AppState>,
|
appState: Partial<AppState>,
|
||||||
elements: readonly ExcalidrawElement[]
|
elements: readonly ExcalidrawElement[],
|
||||||
) {
|
) {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
appState,
|
appState,
|
||||||
elements: elements.map(({ shape, ...element }) => ({
|
elements: elements.map(({ shape, ...element }) => ({
|
||||||
...element,
|
...element,
|
||||||
isSelected: false
|
isSelected: false,
|
||||||
}))
|
})),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,7 +18,7 @@ export const languages = [
|
|||||||
{ lng: "en", label: "English" },
|
{ lng: "en", label: "English" },
|
||||||
{ lng: "es", label: "Español" },
|
{ lng: "es", label: "Español" },
|
||||||
{ lng: "fr", label: "Français" },
|
{ lng: "fr", label: "Français" },
|
||||||
{ lng: "pt", label: "Português" }
|
{ lng: "pt", label: "Português" },
|
||||||
];
|
];
|
||||||
|
|
||||||
i18n
|
i18n
|
||||||
@ -28,7 +28,7 @@ i18n
|
|||||||
.init({
|
.init({
|
||||||
fallbackLng,
|
fallbackLng,
|
||||||
react: { useSuspense: false },
|
react: { useSuspense: false },
|
||||||
load: "languageOnly"
|
load: "languageOnly",
|
||||||
});
|
});
|
||||||
|
|
||||||
export default i18n;
|
export default i18n;
|
||||||
|
@ -17,7 +17,7 @@ const elements = [
|
|||||||
roughness: 1,
|
roughness: 1,
|
||||||
opacity: 100,
|
opacity: 100,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
seed: 749612521
|
seed: 749612521,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "7W-iw5pEBPTU3eaCaLtFo",
|
id: "7W-iw5pEBPTU3eaCaLtFo",
|
||||||
@ -33,7 +33,7 @@ const elements = [
|
|||||||
roughness: 1,
|
roughness: 1,
|
||||||
opacity: 100,
|
opacity: 100,
|
||||||
isSelected: false,
|
isSelected: false,
|
||||||
seed: 952056308
|
seed: 952056308,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "kqKI231mvTrcsYo2DkUsR",
|
id: "kqKI231mvTrcsYo2DkUsR",
|
||||||
@ -52,8 +52,8 @@ const elements = [
|
|||||||
seed: 1683771448,
|
seed: 1683771448,
|
||||||
text: "test",
|
text: "test",
|
||||||
font: "20px Virgil",
|
font: "20px Virgil",
|
||||||
baseline: 22
|
baseline: 22,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
registerFont("./public/FG_Virgil.ttf", { family: "Virgil" });
|
registerFont("./public/FG_Virgil.ttf", { family: "Virgil" });
|
||||||
@ -62,9 +62,9 @@ const canvas = getExportCanvasPreview(
|
|||||||
{
|
{
|
||||||
exportBackground: true,
|
exportBackground: true,
|
||||||
viewBackgroundColor: "#ffffff",
|
viewBackgroundColor: "#ffffff",
|
||||||
scale: 1
|
scale: 1,
|
||||||
},
|
},
|
||||||
createCanvas
|
createCanvas,
|
||||||
);
|
);
|
||||||
|
|
||||||
const fs = require("fs");
|
const fs = require("fs");
|
||||||
|
170
src/index.tsx
170
src/index.tsx
@ -15,7 +15,7 @@ import {
|
|||||||
getElementAbsoluteCoords,
|
getElementAbsoluteCoords,
|
||||||
getCursorForResizingElement,
|
getCursorForResizingElement,
|
||||||
getPerfectElementSize,
|
getPerfectElementSize,
|
||||||
resizePerfectLineForNWHandler
|
resizePerfectLineForNWHandler,
|
||||||
} from "./element";
|
} from "./element";
|
||||||
import {
|
import {
|
||||||
clearSelection,
|
clearSelection,
|
||||||
@ -31,7 +31,7 @@ import {
|
|||||||
hasStroke,
|
hasStroke,
|
||||||
hasText,
|
hasText,
|
||||||
exportCanvas,
|
exportCanvas,
|
||||||
importFromBackend
|
importFromBackend,
|
||||||
} from "./scene";
|
} from "./scene";
|
||||||
|
|
||||||
import { renderScene } from "./renderer";
|
import { renderScene } from "./renderer";
|
||||||
@ -71,7 +71,7 @@ import {
|
|||||||
actionLoadScene,
|
actionLoadScene,
|
||||||
actionSaveScene,
|
actionSaveScene,
|
||||||
actionCopyStyles,
|
actionCopyStyles,
|
||||||
actionPasteStyles
|
actionPasteStyles,
|
||||||
} from "./actions";
|
} from "./actions";
|
||||||
import { Action, ActionResult } from "./actions/types";
|
import { Action, ActionResult } from "./actions/types";
|
||||||
import { getDefaultAppState } from "./appState";
|
import { getDefaultAppState } from "./appState";
|
||||||
@ -100,7 +100,7 @@ const ELEMENT_TRANSLATE_AMOUNT = 1;
|
|||||||
const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
|
const TEXT_TO_CENTER_SNAP_THRESHOLD = 30;
|
||||||
const CURSOR_TYPE = {
|
const CURSOR_TYPE = {
|
||||||
TEXT: "text",
|
TEXT: "text",
|
||||||
CROSSHAIR: "crosshair"
|
CROSSHAIR: "crosshair",
|
||||||
};
|
};
|
||||||
|
|
||||||
let lastCanvasWidth = -1;
|
let lastCanvasWidth = -1;
|
||||||
@ -110,7 +110,7 @@ let lastMouseUp: ((e: any) => void) | null = null;
|
|||||||
|
|
||||||
export function viewportCoordsToSceneCoords(
|
export function viewportCoordsToSceneCoords(
|
||||||
{ clientX, clientY }: { clientX: number; clientY: number },
|
{ clientX, clientY }: { clientX: number; clientY: number },
|
||||||
{ scrollX, scrollY }: { scrollX: number; scrollY: number }
|
{ scrollX, scrollY }: { scrollX: number; scrollY: number },
|
||||||
) {
|
) {
|
||||||
const x = clientX - CANVAS_WINDOW_OFFSET_LEFT - scrollX;
|
const x = clientX - CANVAS_WINDOW_OFFSET_LEFT - scrollX;
|
||||||
const y = clientY - CANVAS_WINDOW_OFFSET_TOP - scrollY;
|
const y = clientY - CANVAS_WINDOW_OFFSET_TOP - scrollY;
|
||||||
@ -118,7 +118,7 @@ export function viewportCoordsToSceneCoords(
|
|||||||
}
|
}
|
||||||
|
|
||||||
function pickAppStatePropertiesForHistory(
|
function pickAppStatePropertiesForHistory(
|
||||||
appState: AppState
|
appState: AppState,
|
||||||
): Partial<AppState> {
|
): Partial<AppState> {
|
||||||
return {
|
return {
|
||||||
exportBackground: appState.exportBackground,
|
exportBackground: appState.exportBackground,
|
||||||
@ -126,7 +126,7 @@ function pickAppStatePropertiesForHistory(
|
|||||||
currentItemBackgroundColor: appState.currentItemBackgroundColor,
|
currentItemBackgroundColor: appState.currentItemBackgroundColor,
|
||||||
currentItemFont: appState.currentItemFont,
|
currentItemFont: appState.currentItemFont,
|
||||||
viewBackgroundColor: appState.viewBackgroundColor,
|
viewBackgroundColor: appState.viewBackgroundColor,
|
||||||
name: appState.name
|
name: appState.name,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -186,8 +186,8 @@ export class App extends React.Component<any, AppState> {
|
|||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
elements
|
elements
|
||||||
.filter(element => element.isSelected)
|
.filter(element => element.isSelected)
|
||||||
.map(({ shape, ...el }) => el)
|
.map(({ shape, ...el }) => el),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
elements = deleteSelectedElements(elements);
|
elements = deleteSelectedElements(elements);
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
@ -200,8 +200,8 @@ export class App extends React.Component<any, AppState> {
|
|||||||
JSON.stringify(
|
JSON.stringify(
|
||||||
elements
|
elements
|
||||||
.filter(element => element.isSelected)
|
.filter(element => element.isSelected)
|
||||||
.map(({ shape, ...el }) => el)
|
.map(({ shape, ...el }) => el),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
};
|
};
|
||||||
@ -257,7 +257,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
document.removeEventListener(
|
document.removeEventListener(
|
||||||
"mousemove",
|
"mousemove",
|
||||||
this.getCurrentCursorPosition,
|
this.getCurrentCursorPosition,
|
||||||
false
|
false,
|
||||||
);
|
);
|
||||||
window.removeEventListener("resize", this.onResize, false);
|
window.removeEventListener("resize", this.onResize, false);
|
||||||
window.removeEventListener("unload", this.onUnload, false);
|
window.removeEventListener("unload", this.onUnload, false);
|
||||||
@ -354,7 +354,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const text = JSON.stringify(
|
const text = JSON.stringify(
|
||||||
elements
|
elements
|
||||||
.filter(element => element.isSelected)
|
.filter(element => element.isSelected)
|
||||||
.map(({ shape, ...el }) => el)
|
.map(({ shape, ...el }) => el),
|
||||||
);
|
);
|
||||||
navigator.clipboard.writeText(text);
|
navigator.clipboard.writeText(text);
|
||||||
}
|
}
|
||||||
@ -393,7 +393,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(hasBackground(elements) ||
|
{(hasBackground(elements) ||
|
||||||
@ -404,7 +404,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -412,7 +412,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
<hr />
|
<hr />
|
||||||
</>
|
</>
|
||||||
@ -426,7 +426,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -434,7 +434,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
<hr />
|
<hr />
|
||||||
</>
|
</>
|
||||||
@ -447,7 +447,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -455,7 +455,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
<hr />
|
<hr />
|
||||||
</>
|
</>
|
||||||
@ -466,7 +466,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -474,7 +474,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</Island>
|
</Island>
|
||||||
@ -533,14 +533,14 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
"saveScene",
|
"saveScene",
|
||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
<ExportDialog
|
<ExportDialog
|
||||||
elements={elements}
|
elements={elements}
|
||||||
@ -553,7 +553,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
exportBackground: this.state.exportBackground,
|
exportBackground: this.state.exportBackground,
|
||||||
name: this.state.name,
|
name: this.state.name,
|
||||||
viewBackgroundColor: this.state.viewBackgroundColor,
|
viewBackgroundColor: this.state.viewBackgroundColor,
|
||||||
scale
|
scale,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onExportToClipboard={(exportedElements, scale) => {
|
onExportToClipboard={(exportedElements, scale) => {
|
||||||
@ -562,7 +562,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
exportBackground: this.state.exportBackground,
|
exportBackground: this.state.exportBackground,
|
||||||
name: this.state.name,
|
name: this.state.name,
|
||||||
viewBackgroundColor: this.state.viewBackgroundColor,
|
viewBackgroundColor: this.state.viewBackgroundColor,
|
||||||
scale
|
scale,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onExportToBackend={exportedElements => {
|
onExportToBackend={exportedElements => {
|
||||||
@ -571,10 +571,10 @@ export class App extends React.Component<any, AppState> {
|
|||||||
"backend",
|
"backend",
|
||||||
exportedElements.map(element => ({
|
exportedElements.map(element => ({
|
||||||
...element,
|
...element,
|
||||||
isSelected: false
|
isSelected: false,
|
||||||
})),
|
})),
|
||||||
this.canvas,
|
this.canvas,
|
||||||
this.state
|
this.state,
|
||||||
);
|
);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -583,7 +583,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
</Stack.Row>
|
</Stack.Row>
|
||||||
{this.actionManager.renderAction(
|
{this.actionManager.renderAction(
|
||||||
@ -591,7 +591,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
t
|
t,
|
||||||
)}
|
)}
|
||||||
</Stack.Col>
|
</Stack.Col>
|
||||||
);
|
);
|
||||||
@ -626,7 +626,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
id="canvas"
|
id="canvas"
|
||||||
style={{
|
style={{
|
||||||
width: canvasWidth,
|
width: canvasWidth,
|
||||||
height: canvasHeight
|
height: canvasHeight,
|
||||||
}}
|
}}
|
||||||
width={canvasWidth * window.devicePixelRatio}
|
width={canvasWidth * window.devicePixelRatio}
|
||||||
height={canvasHeight * window.devicePixelRatio}
|
height={canvasHeight * window.devicePixelRatio}
|
||||||
@ -641,7 +641,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
if (canvas) {
|
if (canvas) {
|
||||||
canvas.addEventListener("wheel", this.handleWheel, {
|
canvas.addEventListener("wheel", this.handleWheel, {
|
||||||
passive: false
|
passive: false,
|
||||||
});
|
});
|
||||||
this.removeWheelEventListener = () =>
|
this.removeWheelEventListener = () =>
|
||||||
canvas.removeEventListener("wheel", this.handleWheel);
|
canvas.removeEventListener("wheel", this.handleWheel);
|
||||||
@ -670,18 +670,18 @@ export class App extends React.Component<any, AppState> {
|
|||||||
options: [
|
options: [
|
||||||
navigator.clipboard && {
|
navigator.clipboard && {
|
||||||
label: t("labels.paste"),
|
label: t("labels.paste"),
|
||||||
action: () => this.pasteFromClipboard()
|
action: () => this.pasteFromClipboard(),
|
||||||
},
|
},
|
||||||
...this.actionManager.getContextMenuItems(
|
...this.actionManager.getContextMenuItems(
|
||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
action => this.canvasOnlyActions.includes(action),
|
action => this.canvasOnlyActions.includes(action),
|
||||||
t
|
t,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
top: e.clientY,
|
top: e.clientY,
|
||||||
left: e.clientX
|
left: e.clientX,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -696,22 +696,22 @@ export class App extends React.Component<any, AppState> {
|
|||||||
options: [
|
options: [
|
||||||
navigator.clipboard && {
|
navigator.clipboard && {
|
||||||
label: t("labels.copy"),
|
label: t("labels.copy"),
|
||||||
action: this.copyToClipboard
|
action: this.copyToClipboard,
|
||||||
},
|
},
|
||||||
navigator.clipboard && {
|
navigator.clipboard && {
|
||||||
label: t("labels.paste"),
|
label: t("labels.paste"),
|
||||||
action: () => this.pasteFromClipboard()
|
action: () => this.pasteFromClipboard(),
|
||||||
},
|
},
|
||||||
...this.actionManager.getContextMenuItems(
|
...this.actionManager.getContextMenuItems(
|
||||||
elements,
|
elements,
|
||||||
this.state,
|
this.state,
|
||||||
this.syncActionResult,
|
this.syncActionResult,
|
||||||
action => !this.canvasOnlyActions.includes(action),
|
action => !this.canvasOnlyActions.includes(action),
|
||||||
t
|
t,
|
||||||
)
|
),
|
||||||
],
|
],
|
||||||
top: e.clientY,
|
top: e.clientY,
|
||||||
left: e.clientX
|
left: e.clientX,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onMouseDown={e => {
|
onMouseDown={e => {
|
||||||
@ -733,7 +733,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
lastY = e.clientY;
|
lastY = e.clientY;
|
||||||
this.setState(state => ({
|
this.setState(state => ({
|
||||||
scrollX: state.scrollX - deltaX,
|
scrollX: state.scrollX - deltaX,
|
||||||
scrollY: state.scrollY - deltaY
|
scrollY: state.scrollY - deltaY,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
const onMouseUp = (lastMouseUp = (e: MouseEvent) => {
|
const onMouseUp = (lastMouseUp = (e: MouseEvent) => {
|
||||||
@ -743,7 +743,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
window.removeEventListener("mouseup", onMouseUp);
|
window.removeEventListener("mouseup", onMouseUp);
|
||||||
});
|
});
|
||||||
window.addEventListener("mousemove", onMouseMove, {
|
window.addEventListener("mousemove", onMouseMove, {
|
||||||
passive: true
|
passive: true,
|
||||||
});
|
});
|
||||||
window.addEventListener("mouseup", onMouseUp);
|
window.addEventListener("mouseup", onMouseUp);
|
||||||
return;
|
return;
|
||||||
@ -763,7 +763,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
// Handle scrollbars dragging
|
// Handle scrollbars dragging
|
||||||
const {
|
const {
|
||||||
isOverHorizontalScrollBar,
|
isOverHorizontalScrollBar,
|
||||||
isOverVerticalScrollBar
|
isOverVerticalScrollBar,
|
||||||
} = isOverScrollBars(
|
} = isOverScrollBars(
|
||||||
elements,
|
elements,
|
||||||
e.clientX - CANVAS_WINDOW_OFFSET_LEFT,
|
e.clientX - CANVAS_WINDOW_OFFSET_LEFT,
|
||||||
@ -771,7 +771,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
canvasWidth,
|
canvasWidth,
|
||||||
canvasHeight,
|
canvasHeight,
|
||||||
this.state.scrollX,
|
this.state.scrollX,
|
||||||
this.state.scrollY
|
this.state.scrollY,
|
||||||
);
|
);
|
||||||
|
|
||||||
const { x, y } = viewportCoordsToSceneCoords(e, this.state);
|
const { x, y } = viewportCoordsToSceneCoords(e, this.state);
|
||||||
@ -785,7 +785,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
"hachure",
|
"hachure",
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
100
|
100,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (isTextElement(element)) {
|
if (isTextElement(element)) {
|
||||||
@ -802,16 +802,16 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const resizeElement = getElementWithResizeHandler(
|
const resizeElement = getElementWithResizeHandler(
|
||||||
elements,
|
elements,
|
||||||
{ x, y },
|
{ x, y },
|
||||||
this.state
|
this.state,
|
||||||
);
|
);
|
||||||
this.setState({
|
this.setState({
|
||||||
resizingElement: resizeElement ? resizeElement.element : null
|
resizingElement: resizeElement ? resizeElement.element : null,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (resizeElement) {
|
if (resizeElement) {
|
||||||
resizeHandle = resizeElement.resizeHandle;
|
resizeHandle = resizeElement.resizeHandle;
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
document.documentElement.style.cursor = getCursorForResizingElement(
|
||||||
resizeElement
|
resizeElement,
|
||||||
);
|
);
|
||||||
isResizingElements = true;
|
isResizingElements = true;
|
||||||
} else {
|
} else {
|
||||||
@ -837,7 +837,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elements = [
|
elements = [
|
||||||
...elements.map(element => ({
|
...elements.map(element => ({
|
||||||
...element,
|
...element,
|
||||||
isSelected: false
|
isSelected: false,
|
||||||
})),
|
})),
|
||||||
...elements
|
...elements
|
||||||
.filter(element => element.isSelected)
|
.filter(element => element.isSelected)
|
||||||
@ -845,7 +845,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const newElement = duplicateElement(element);
|
const newElement = duplicateElement(element);
|
||||||
newElement.isSelected = true;
|
newElement.isSelected = true;
|
||||||
return newElement;
|
return newElement;
|
||||||
})
|
}),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -860,7 +860,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
if (!e.altKey) {
|
if (!e.altKey) {
|
||||||
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
|
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
|
||||||
x,
|
x,
|
||||||
y
|
y,
|
||||||
);
|
);
|
||||||
if (snappedToCenterPosition) {
|
if (snappedToCenterPosition) {
|
||||||
element.x = snappedToCenterPosition.elementCenterX;
|
element.x = snappedToCenterPosition.elementCenterX;
|
||||||
@ -884,22 +884,22 @@ export class App extends React.Component<any, AppState> {
|
|||||||
...newTextElement(
|
...newTextElement(
|
||||||
element,
|
element,
|
||||||
text,
|
text,
|
||||||
this.state.currentItemFont
|
this.state.currentItemFont,
|
||||||
),
|
),
|
||||||
isSelected: true
|
isSelected: true,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
editingElement: null,
|
editingElement: null,
|
||||||
elementType: "selection"
|
elementType: "selection",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
this.setState({
|
this.setState({
|
||||||
elementType: "selection",
|
elementType: "selection",
|
||||||
editingElement: element
|
editingElement: element,
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -993,7 +993,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const { width, height } = getPerfectElementSize(
|
const { width, height } = getPerfectElementSize(
|
||||||
element.type,
|
element.type,
|
||||||
x - element.x,
|
x - element.x,
|
||||||
y - element.y
|
y - element.y,
|
||||||
);
|
);
|
||||||
element.width = width;
|
element.width = width;
|
||||||
element.height = height;
|
element.height = height;
|
||||||
@ -1023,7 +1023,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
document.documentElement.style.cursor = getCursorForResizingElement(
|
||||||
{ element, resizeHandle }
|
{ element, resizeHandle },
|
||||||
);
|
);
|
||||||
el.x = element.x;
|
el.x = element.x;
|
||||||
el.y = element.y;
|
el.y = element.y;
|
||||||
@ -1078,11 +1078,11 @@ export class App extends React.Component<any, AppState> {
|
|||||||
if (e.shiftKey) {
|
if (e.shiftKey) {
|
||||||
let {
|
let {
|
||||||
width: newWidth,
|
width: newWidth,
|
||||||
height: newHeight
|
height: newHeight,
|
||||||
} = getPerfectElementSize(
|
} = getPerfectElementSize(
|
||||||
this.state.elementType,
|
this.state.elementType,
|
||||||
width,
|
width,
|
||||||
height
|
height,
|
||||||
);
|
);
|
||||||
draggingElement.width = newWidth;
|
draggingElement.width = newWidth;
|
||||||
draggingElement.height = newHeight;
|
draggingElement.height = newHeight;
|
||||||
@ -1099,7 +1099,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
}
|
}
|
||||||
const elementsWithinSelection = getElementsWithinSelection(
|
const elementsWithinSelection = getElementsWithinSelection(
|
||||||
elements,
|
elements,
|
||||||
draggingElement
|
draggingElement,
|
||||||
);
|
);
|
||||||
elementsWithinSelection.forEach(element => {
|
elementsWithinSelection.forEach(element => {
|
||||||
element.isSelected = true;
|
element.isSelected = true;
|
||||||
@ -1115,7 +1115,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
draggingElement,
|
draggingElement,
|
||||||
resizingElement,
|
resizingElement,
|
||||||
elementType,
|
elementType,
|
||||||
elementLocked
|
elementLocked,
|
||||||
} = this.state;
|
} = this.state;
|
||||||
|
|
||||||
lastMouseUp = null;
|
lastMouseUp = null;
|
||||||
@ -1130,7 +1130,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
// remove invisible element which was added in onMouseDown
|
// remove invisible element which was added in onMouseDown
|
||||||
elements = elements.slice(0, -1);
|
elements = elements.slice(0, -1);
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null
|
draggingElement: null,
|
||||||
});
|
});
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
return;
|
return;
|
||||||
@ -1179,7 +1179,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
elementType: "selection"
|
elementType: "selection",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1214,10 +1214,10 @@ export class App extends React.Component<any, AppState> {
|
|||||||
"hachure",
|
"hachure",
|
||||||
1,
|
1,
|
||||||
1,
|
1,
|
||||||
100
|
100,
|
||||||
),
|
),
|
||||||
"", // default text
|
"", // default text
|
||||||
this.state.currentItemFont // default font
|
this.state.currentItemFont, // default font
|
||||||
);
|
);
|
||||||
|
|
||||||
this.setState({ editingElement: element });
|
this.setState({ editingElement: element });
|
||||||
@ -1227,7 +1227,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
|
|
||||||
if (elementAtPosition && isTextElement(elementAtPosition)) {
|
if (elementAtPosition && isTextElement(elementAtPosition)) {
|
||||||
elements = elements.filter(
|
elements = elements.filter(
|
||||||
element => element.id !== elementAtPosition.id
|
element => element.id !== elementAtPosition.id,
|
||||||
);
|
);
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
|
|
||||||
@ -1248,7 +1248,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
} else if (!e.altKey) {
|
} else if (!e.altKey) {
|
||||||
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
|
const snappedToCenterPosition = this.getTextWysiwygSnappedToCenterPosition(
|
||||||
x,
|
x,
|
||||||
y
|
y,
|
||||||
);
|
);
|
||||||
|
|
||||||
if (snappedToCenterPosition) {
|
if (snappedToCenterPosition) {
|
||||||
@ -1273,16 +1273,16 @@ export class App extends React.Component<any, AppState> {
|
|||||||
// we need to recreate the element to update dimensions &
|
// we need to recreate the element to update dimensions &
|
||||||
// position
|
// position
|
||||||
...newTextElement(element, text, element.font),
|
...newTextElement(element, text, element.font),
|
||||||
isSelected: true
|
isSelected: true,
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
this.setState({
|
this.setState({
|
||||||
draggingElement: null,
|
draggingElement: null,
|
||||||
editingElement: null,
|
editingElement: null,
|
||||||
elementType: "selection"
|
elementType: "selection",
|
||||||
});
|
});
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
onMouseMove={e => {
|
onMouseMove={e => {
|
||||||
@ -1296,11 +1296,11 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const resizeElement = getElementWithResizeHandler(
|
const resizeElement = getElementWithResizeHandler(
|
||||||
elements,
|
elements,
|
||||||
{ x, y },
|
{ x, y },
|
||||||
this.state
|
this.state,
|
||||||
);
|
);
|
||||||
if (resizeElement && resizeElement.resizeHandle) {
|
if (resizeElement && resizeElement.resizeHandle) {
|
||||||
document.documentElement.style.cursor = getCursorForResizingElement(
|
document.documentElement.style.cursor = getCursorForResizingElement(
|
||||||
resizeElement
|
resizeElement,
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -1327,7 +1327,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
const { deltaX, deltaY } = e;
|
const { deltaX, deltaY } = e;
|
||||||
this.setState(state => ({
|
this.setState(state => ({
|
||||||
scrollX: state.scrollX - deltaX,
|
scrollX: state.scrollX - deltaX,
|
||||||
scrollY: state.scrollY - deltaY
|
scrollY: state.scrollY - deltaY,
|
||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1384,7 +1384,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
duplicate.x += dx - minX;
|
duplicate.x += dx - minX;
|
||||||
duplicate.y += dy - minY;
|
duplicate.y += dy - minY;
|
||||||
return duplicate;
|
return duplicate;
|
||||||
})
|
}),
|
||||||
];
|
];
|
||||||
this.forceUpdate();
|
this.forceUpdate();
|
||||||
}
|
}
|
||||||
@ -1399,7 +1399,7 @@ export class App extends React.Component<any, AppState> {
|
|||||||
elementClickedInside.y + elementClickedInside.height / 2;
|
elementClickedInside.y + elementClickedInside.height / 2;
|
||||||
const distanceToCenter = Math.hypot(
|
const distanceToCenter = Math.hypot(
|
||||||
x - elementCenterX,
|
x - elementCenterX,
|
||||||
y - elementCenterY
|
y - elementCenterY,
|
||||||
);
|
);
|
||||||
const isSnappedToCenter =
|
const isSnappedToCenter =
|
||||||
distanceToCenter < TEXT_TO_CENTER_SNAP_THRESHOLD;
|
distanceToCenter < TEXT_TO_CENTER_SNAP_THRESHOLD;
|
||||||
@ -1427,15 +1427,15 @@ export class App extends React.Component<any, AppState> {
|
|||||||
renderScene(elements, this.rc!, this.canvas!, {
|
renderScene(elements, this.rc!, this.canvas!, {
|
||||||
scrollX: this.state.scrollX,
|
scrollX: this.state.scrollX,
|
||||||
scrollY: this.state.scrollY,
|
scrollY: this.state.scrollY,
|
||||||
viewBackgroundColor: this.state.viewBackgroundColor
|
viewBackgroundColor: this.state.viewBackgroundColor,
|
||||||
});
|
});
|
||||||
this.saveDebounced();
|
this.saveDebounced();
|
||||||
if (history.isRecording()) {
|
if (history.isRecording()) {
|
||||||
history.pushEntry(
|
history.pushEntry(
|
||||||
history.generateCurrentEntry(
|
history.generateCurrentEntry(
|
||||||
pickAppStatePropertiesForHistory(this.state),
|
pickAppStatePropertiesForHistory(this.state),
|
||||||
elements
|
elements,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1453,7 +1453,7 @@ class TopErrorBoundary extends React.Component {
|
|||||||
return {
|
return {
|
||||||
hasError: true,
|
hasError: true,
|
||||||
localStorage: JSON.stringify({ ...localStorage }),
|
localStorage: JSON.stringify({ ...localStorage }),
|
||||||
stack: error.stack
|
stack: error.stack,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1471,7 +1471,7 @@ class TopErrorBoundary extends React.Component {
|
|||||||
} catch {}
|
} catch {}
|
||||||
|
|
||||||
window.open(
|
window.open(
|
||||||
`https://github.com/excalidraw/excalidraw/issues/new?body=${body}`
|
`https://github.com/excalidraw/excalidraw/issues/new?body=${body}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1539,5 +1539,5 @@ ReactDOM.render(
|
|||||||
<TopErrorBoundary>
|
<TopErrorBoundary>
|
||||||
<AppWithTrans />
|
<AppWithTrans />
|
||||||
</TopErrorBoundary>,
|
</TopErrorBoundary>,
|
||||||
rootElement
|
rootElement,
|
||||||
);
|
);
|
||||||
|
@ -11,7 +11,7 @@ export const KEYS = {
|
|||||||
return /Mac|iPod|iPhone|iPad/.test(window.navigator.platform)
|
return /Mac|iPod|iPhone|iPad/.test(window.navigator.platform)
|
||||||
? "metaKey"
|
? "metaKey"
|
||||||
: "ctrlKey";
|
: "ctrlKey";
|
||||||
}
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export function isArrowKey(keyCode: string) {
|
export function isArrowKey(keyCode: string) {
|
||||||
|
@ -5,7 +5,7 @@ export function distanceBetweenPointAndSegment(
|
|||||||
x1: number,
|
x1: number,
|
||||||
y1: number,
|
y1: number,
|
||||||
x2: number,
|
x2: number,
|
||||||
y2: number
|
y2: number,
|
||||||
) {
|
) {
|
||||||
const A = x - x1;
|
const A = x - x1;
|
||||||
const B = y - y1;
|
const B = y - y1;
|
||||||
@ -42,13 +42,13 @@ export function rotate(
|
|||||||
y1: number,
|
y1: number,
|
||||||
x2: number,
|
x2: number,
|
||||||
y2: number,
|
y2: number,
|
||||||
angle: number
|
angle: number,
|
||||||
) {
|
) {
|
||||||
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
// 𝑎′𝑥=(𝑎𝑥−𝑐𝑥)cos𝜃−(𝑎𝑦−𝑐𝑦)sin𝜃+𝑐𝑥
|
||||||
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
// 𝑎′𝑦=(𝑎𝑥−𝑐𝑥)sin𝜃+(𝑎𝑦−𝑐𝑦)cos𝜃+𝑐𝑦.
|
||||||
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
|
// https://math.stackexchange.com/questions/2204520/how-do-i-rotate-a-line-segment-in-a-specific-point-on-the-line
|
||||||
return [
|
return [
|
||||||
(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2,
|
(x1 - x2) * Math.cos(angle) - (y1 - y2) * Math.sin(angle) + x2,
|
||||||
(x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2
|
(x1 - x2) * Math.sin(angle) + (y1 - y2) * Math.cos(angle) + y2,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import { isTextElement } from "../element/typeChecks";
|
|||||||
import {
|
import {
|
||||||
getDiamondPoints,
|
getDiamondPoints,
|
||||||
getArrowPoints,
|
getArrowPoints,
|
||||||
getLinePoints
|
getLinePoints,
|
||||||
} from "../element/bounds";
|
} from "../element/bounds";
|
||||||
import { RoughCanvas } from "roughjs/bin/canvas";
|
import { RoughCanvas } from "roughjs/bin/canvas";
|
||||||
import { Drawable } from "roughjs/bin/core";
|
import { Drawable } from "roughjs/bin/core";
|
||||||
@ -11,7 +11,7 @@ import { Drawable } from "roughjs/bin/core";
|
|||||||
export function renderElement(
|
export function renderElement(
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
rc: RoughCanvas,
|
rc: RoughCanvas,
|
||||||
context: CanvasRenderingContext2D
|
context: CanvasRenderingContext2D,
|
||||||
) {
|
) {
|
||||||
const generator = rc.generator;
|
const generator = rc.generator;
|
||||||
if (element.type === "selection") {
|
if (element.type === "selection") {
|
||||||
@ -30,7 +30,7 @@ export function renderElement(
|
|||||||
fillStyle: element.fillStyle,
|
fillStyle: element.fillStyle,
|
||||||
strokeWidth: element.strokeWidth,
|
strokeWidth: element.strokeWidth,
|
||||||
roughness: element.roughness,
|
roughness: element.roughness,
|
||||||
seed: element.seed
|
seed: element.seed,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -47,14 +47,14 @@ export function renderElement(
|
|||||||
bottomX,
|
bottomX,
|
||||||
bottomY,
|
bottomY,
|
||||||
leftX,
|
leftX,
|
||||||
leftY
|
leftY,
|
||||||
] = getDiamondPoints(element);
|
] = getDiamondPoints(element);
|
||||||
element.shape = generator.polygon(
|
element.shape = generator.polygon(
|
||||||
[
|
[
|
||||||
[topX, topY],
|
[topX, topY],
|
||||||
[rightX, rightY],
|
[rightX, rightY],
|
||||||
[bottomX, bottomY],
|
[bottomX, bottomY],
|
||||||
[leftX, leftY]
|
[leftX, leftY],
|
||||||
],
|
],
|
||||||
{
|
{
|
||||||
stroke: element.strokeColor,
|
stroke: element.strokeColor,
|
||||||
@ -65,8 +65,8 @@ export function renderElement(
|
|||||||
fillStyle: element.fillStyle,
|
fillStyle: element.fillStyle,
|
||||||
strokeWidth: element.strokeWidth,
|
strokeWidth: element.strokeWidth,
|
||||||
roughness: element.roughness,
|
roughness: element.roughness,
|
||||||
seed: element.seed
|
seed: element.seed,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -90,8 +90,8 @@ export function renderElement(
|
|||||||
strokeWidth: element.strokeWidth,
|
strokeWidth: element.strokeWidth,
|
||||||
roughness: element.roughness,
|
roughness: element.roughness,
|
||||||
seed: element.seed,
|
seed: element.seed,
|
||||||
curveFitting: 1
|
curveFitting: 1,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +104,7 @@ export function renderElement(
|
|||||||
stroke: element.strokeColor,
|
stroke: element.strokeColor,
|
||||||
strokeWidth: element.strokeWidth,
|
strokeWidth: element.strokeWidth,
|
||||||
roughness: element.roughness,
|
roughness: element.roughness,
|
||||||
seed: element.seed
|
seed: element.seed,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!element.shape) {
|
if (!element.shape) {
|
||||||
@ -114,7 +114,7 @@ export function renderElement(
|
|||||||
// -----
|
// -----
|
||||||
generator.line(x1, y1, x2, y2, options),
|
generator.line(x1, y1, x2, y2, options),
|
||||||
// /
|
// /
|
||||||
generator.line(x4, y4, x2, y2, options)
|
generator.line(x4, y4, x2, y2, options),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,7 +128,7 @@ export function renderElement(
|
|||||||
stroke: element.strokeColor,
|
stroke: element.strokeColor,
|
||||||
strokeWidth: element.strokeWidth,
|
strokeWidth: element.strokeWidth,
|
||||||
roughness: element.roughness,
|
roughness: element.roughness,
|
||||||
seed: element.seed
|
seed: element.seed,
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!element.shape) {
|
if (!element.shape) {
|
||||||
@ -147,7 +147,7 @@ export function renderElement(
|
|||||||
context.fillText(
|
context.fillText(
|
||||||
element.text,
|
element.text,
|
||||||
0,
|
0,
|
||||||
element.baseline || element.actualBoundingBoxAscent || 0
|
element.baseline || element.actualBoundingBoxAscent || 0,
|
||||||
);
|
);
|
||||||
context.fillStyle = fillStyle;
|
context.fillStyle = fillStyle;
|
||||||
context.font = font;
|
context.font = font;
|
||||||
|
@ -8,7 +8,7 @@ import { SceneState } from "../scene/types";
|
|||||||
import {
|
import {
|
||||||
getScrollBars,
|
getScrollBars,
|
||||||
SCROLLBAR_COLOR,
|
SCROLLBAR_COLOR,
|
||||||
SCROLLBAR_WIDTH
|
SCROLLBAR_WIDTH,
|
||||||
} from "../scene/scrollbars";
|
} from "../scene/scrollbars";
|
||||||
|
|
||||||
import { renderElement } from "./renderElement";
|
import { renderElement } from "./renderElement";
|
||||||
@ -23,13 +23,13 @@ export function renderScene(
|
|||||||
offsetX,
|
offsetX,
|
||||||
offsetY,
|
offsetY,
|
||||||
renderScrollbars = true,
|
renderScrollbars = true,
|
||||||
renderSelection = true
|
renderSelection = true,
|
||||||
}: {
|
}: {
|
||||||
offsetX?: number;
|
offsetX?: number;
|
||||||
offsetY?: number;
|
offsetY?: number;
|
||||||
renderScrollbars?: boolean;
|
renderScrollbars?: boolean;
|
||||||
renderSelection?: boolean;
|
renderSelection?: boolean;
|
||||||
} = {}
|
} = {},
|
||||||
) {
|
) {
|
||||||
if (!canvas) return;
|
if (!canvas) return;
|
||||||
const context = canvas.getContext("2d")!;
|
const context = canvas.getContext("2d")!;
|
||||||
@ -53,7 +53,7 @@ export function renderScene(
|
|||||||
sceneState = {
|
sceneState = {
|
||||||
...sceneState,
|
...sceneState,
|
||||||
scrollX: typeof offsetX === "number" ? offsetX : sceneState.scrollX,
|
scrollX: typeof offsetX === "number" ? offsetX : sceneState.scrollX,
|
||||||
scrollY: typeof offsetY === "number" ? offsetY : sceneState.scrollY
|
scrollY: typeof offsetY === "number" ? offsetY : sceneState.scrollY,
|
||||||
};
|
};
|
||||||
|
|
||||||
elements.forEach(element => {
|
elements.forEach(element => {
|
||||||
@ -65,19 +65,19 @@ export function renderScene(
|
|||||||
// If canvas is scaled for high pixelDeviceRatio width and height
|
// If canvas is scaled for high pixelDeviceRatio width and height
|
||||||
// setted in the `style` attribute
|
// setted in the `style` attribute
|
||||||
parseInt(canvas.style.width) || canvas.width,
|
parseInt(canvas.style.width) || canvas.width,
|
||||||
parseInt(canvas.style.height) || canvas.height
|
parseInt(canvas.style.height) || canvas.height,
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
context.translate(
|
context.translate(
|
||||||
element.x + sceneState.scrollX,
|
element.x + sceneState.scrollX,
|
||||||
element.y + sceneState.scrollY
|
element.y + sceneState.scrollY,
|
||||||
);
|
);
|
||||||
renderElement(element, rc, context);
|
renderElement(element, rc, context);
|
||||||
context.translate(
|
context.translate(
|
||||||
-element.x - sceneState.scrollX,
|
-element.x - sceneState.scrollX,
|
||||||
-element.y - sceneState.scrollY
|
-element.y - sceneState.scrollY,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ export function renderScene(
|
|||||||
elementX1,
|
elementX1,
|
||||||
elementY1,
|
elementY1,
|
||||||
elementX2,
|
elementX2,
|
||||||
elementY2
|
elementY2,
|
||||||
] = getElementAbsoluteCoords(element);
|
] = getElementAbsoluteCoords(element);
|
||||||
const lineDash = context.getLineDash();
|
const lineDash = context.getLineDash();
|
||||||
context.setLineDash([8, 4]);
|
context.setLineDash([8, 4]);
|
||||||
@ -99,7 +99,7 @@ export function renderScene(
|
|||||||
elementX1 - margin + sceneState.scrollX,
|
elementX1 - margin + sceneState.scrollX,
|
||||||
elementY1 - margin + sceneState.scrollY,
|
elementY1 - margin + sceneState.scrollY,
|
||||||
elementX2 - elementX1 + margin * 2,
|
elementX2 - elementX1 + margin * 2,
|
||||||
elementY2 - elementY1 + margin * 2
|
elementY2 - elementY1 + margin * 2,
|
||||||
);
|
);
|
||||||
context.setLineDash(lineDash);
|
context.setLineDash(lineDash);
|
||||||
});
|
});
|
||||||
@ -118,7 +118,7 @@ export function renderScene(
|
|||||||
context.canvas.width / window.devicePixelRatio,
|
context.canvas.width / window.devicePixelRatio,
|
||||||
context.canvas.height / window.devicePixelRatio,
|
context.canvas.height / window.devicePixelRatio,
|
||||||
sceneState.scrollX,
|
sceneState.scrollX,
|
||||||
sceneState.scrollY
|
sceneState.scrollY,
|
||||||
);
|
);
|
||||||
|
|
||||||
const strokeStyle = context.strokeStyle;
|
const strokeStyle = context.strokeStyle;
|
||||||
@ -132,7 +132,7 @@ export function renderScene(
|
|||||||
scrollBar.y,
|
scrollBar.y,
|
||||||
scrollBar.width,
|
scrollBar.width,
|
||||||
scrollBar.height,
|
scrollBar.height,
|
||||||
SCROLLBAR_WIDTH / 2
|
SCROLLBAR_WIDTH / 2,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
context.strokeStyle = strokeStyle;
|
context.strokeStyle = strokeStyle;
|
||||||
@ -145,7 +145,7 @@ function isVisibleElement(
|
|||||||
scrollX: number,
|
scrollX: number,
|
||||||
scrollY: number,
|
scrollY: number,
|
||||||
canvasWidth: number,
|
canvasWidth: number,
|
||||||
canvasHeight: number
|
canvasHeight: number,
|
||||||
) {
|
) {
|
||||||
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
let [x1, y1, x2, y2] = getElementAbsoluteCoords(element);
|
||||||
x1 += scrollX;
|
x1 += scrollX;
|
||||||
|
@ -14,7 +14,7 @@ export function roundRect(
|
|||||||
y: number,
|
y: number,
|
||||||
width: number,
|
width: number,
|
||||||
height: number,
|
height: number,
|
||||||
radius: number
|
radius: number,
|
||||||
) {
|
) {
|
||||||
context.beginPath();
|
context.beginPath();
|
||||||
context.moveTo(x + radius, y);
|
context.moveTo(x + radius, y);
|
||||||
@ -25,7 +25,7 @@ export function roundRect(
|
|||||||
x + width,
|
x + width,
|
||||||
y + height,
|
y + height,
|
||||||
x + width - radius,
|
x + width - radius,
|
||||||
y + height
|
y + height,
|
||||||
);
|
);
|
||||||
context.lineTo(x + radius, y + height);
|
context.lineTo(x + radius, y + height);
|
||||||
context.quadraticCurveTo(x, y + height, x, y + height - radius);
|
context.quadraticCurveTo(x, y + height, x, y + height - radius);
|
||||||
|
@ -8,7 +8,7 @@ export const hasBackground = (elements: readonly ExcalidrawElement[]) =>
|
|||||||
element.isSelected &&
|
element.isSelected &&
|
||||||
(element.type === "rectangle" ||
|
(element.type === "rectangle" ||
|
||||||
element.type === "ellipse" ||
|
element.type === "ellipse" ||
|
||||||
element.type === "diamond")
|
element.type === "diamond"),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const hasStroke = (elements: readonly ExcalidrawElement[]) =>
|
export const hasStroke = (elements: readonly ExcalidrawElement[]) =>
|
||||||
@ -19,7 +19,7 @@ export const hasStroke = (elements: readonly ExcalidrawElement[]) =>
|
|||||||
element.type === "ellipse" ||
|
element.type === "ellipse" ||
|
||||||
element.type === "diamond" ||
|
element.type === "diamond" ||
|
||||||
element.type === "arrow" ||
|
element.type === "arrow" ||
|
||||||
element.type === "line")
|
element.type === "line"),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const hasText = (elements: readonly ExcalidrawElement[]) =>
|
export const hasText = (elements: readonly ExcalidrawElement[]) =>
|
||||||
@ -28,7 +28,7 @@ export const hasText = (elements: readonly ExcalidrawElement[]) =>
|
|||||||
export function getElementAtPosition(
|
export function getElementAtPosition(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number,
|
||||||
) {
|
) {
|
||||||
let hitElement = null;
|
let hitElement = null;
|
||||||
// We need to to hit testing from front (end of the array) to back (beginning of the array)
|
// We need to to hit testing from front (end of the array) to back (beginning of the array)
|
||||||
@ -45,7 +45,7 @@ export function getElementAtPosition(
|
|||||||
export function getElementContainingPosition(
|
export function getElementContainingPosition(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
x: number,
|
x: number,
|
||||||
y: number
|
y: number,
|
||||||
) {
|
) {
|
||||||
let hitElement = null;
|
let hitElement = null;
|
||||||
// We need to to hit testing from front (end of the array) to back (beginning of the array)
|
// We need to to hit testing from front (end of the array) to back (beginning of the array)
|
||||||
|
@ -27,19 +27,19 @@ interface DataState {
|
|||||||
|
|
||||||
export function serializeAsJSON(
|
export function serializeAsJSON(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState?: AppState
|
appState?: AppState,
|
||||||
): string {
|
): string {
|
||||||
return JSON.stringify({
|
return JSON.stringify({
|
||||||
version: 1,
|
version: 1,
|
||||||
source: window.location.origin,
|
source: window.location.origin,
|
||||||
elements: elements.map(({ shape, ...el }) => el),
|
elements: elements.map(({ shape, ...el }) => el),
|
||||||
appState: appState || getDefaultAppState()
|
appState: appState || getDefaultAppState(),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function saveAsJSON(
|
export async function saveAsJSON(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState
|
appState: AppState,
|
||||||
) {
|
) {
|
||||||
const serialized = serializeAsJSON(elements, appState);
|
const serialized = serializeAsJSON(elements, appState);
|
||||||
|
|
||||||
@ -48,9 +48,9 @@ export async function saveAsJSON(
|
|||||||
new Blob([serialized], { type: "application/json" }),
|
new Blob([serialized], { type: "application/json" }),
|
||||||
{
|
{
|
||||||
fileName: name,
|
fileName: name,
|
||||||
description: "Excalidraw file"
|
description: "Excalidraw file",
|
||||||
},
|
},
|
||||||
(window as any).handle
|
(window as any).handle,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ export async function loadFromJSON() {
|
|||||||
const blob = await fileOpen({
|
const blob = await fileOpen({
|
||||||
description: "Excalidraw files",
|
description: "Excalidraw files",
|
||||||
extensions: ["json"],
|
extensions: ["json"],
|
||||||
mimeTypes: ["application/json"]
|
mimeTypes: ["application/json"],
|
||||||
});
|
});
|
||||||
if (blob.handle) {
|
if (blob.handle) {
|
||||||
(window as any).handle = blob.handle;
|
(window as any).handle = blob.handle;
|
||||||
@ -101,12 +101,12 @@ export async function loadFromJSON() {
|
|||||||
|
|
||||||
export async function exportToBackend(
|
export async function exportToBackend(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
appState: AppState
|
appState: AppState,
|
||||||
) {
|
) {
|
||||||
const response = await fetch(BACKEND_POST, {
|
const response = await fetch(BACKEND_POST, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: serializeAsJSON(elements, appState)
|
body: serializeAsJSON(elements, appState),
|
||||||
});
|
});
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
if (json.id) {
|
if (json.id) {
|
||||||
@ -117,8 +117,8 @@ export async function exportToBackend(
|
|||||||
window.alert(
|
window.alert(
|
||||||
i18n.t("alerts.copiedToClipboard", {
|
i18n.t("alerts.copiedToClipboard", {
|
||||||
url: url.toString(),
|
url: url.toString(),
|
||||||
interpolation: { escapeValue: false }
|
interpolation: { escapeValue: false },
|
||||||
})
|
}),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
window.alert(i18n.t("alerts.couldNotCreateShareableLink"));
|
window.alert(i18n.t("alerts.couldNotCreateShareableLink"));
|
||||||
@ -129,7 +129,7 @@ export async function importFromBackend(id: string | null) {
|
|||||||
let elements: readonly ExcalidrawElement[] = [];
|
let elements: readonly ExcalidrawElement[] = [];
|
||||||
let appState: AppState = getDefaultAppState();
|
let appState: AppState = getDefaultAppState();
|
||||||
const response = await fetch(`${BACKEND_GET}${id}.json`).then(data =>
|
const response = await fetch(`${BACKEND_GET}${id}.json`).then(data =>
|
||||||
data.clone().json()
|
data.clone().json(),
|
||||||
);
|
);
|
||||||
if (response != null) {
|
if (response != null) {
|
||||||
try {
|
try {
|
||||||
@ -152,14 +152,14 @@ export async function exportCanvas(
|
|||||||
exportPadding = 10,
|
exportPadding = 10,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
name,
|
name,
|
||||||
scale = 1
|
scale = 1,
|
||||||
}: {
|
}: {
|
||||||
exportBackground: boolean;
|
exportBackground: boolean;
|
||||||
exportPadding?: number;
|
exportPadding?: number;
|
||||||
viewBackgroundColor: string;
|
viewBackgroundColor: string;
|
||||||
name: string;
|
name: string;
|
||||||
scale?: number;
|
scale?: number;
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
if (!elements.length)
|
if (!elements.length)
|
||||||
return window.alert(i18n.t("alerts.cannotExportEmptyCanvas"));
|
return window.alert(i18n.t("alerts.cannotExportEmptyCanvas"));
|
||||||
@ -169,7 +169,7 @@ export async function exportCanvas(
|
|||||||
exportBackground,
|
exportBackground,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
exportPadding,
|
exportPadding,
|
||||||
scale
|
scale,
|
||||||
});
|
});
|
||||||
tempCanvas.style.display = "none";
|
tempCanvas.style.display = "none";
|
||||||
document.body.appendChild(tempCanvas);
|
document.body.appendChild(tempCanvas);
|
||||||
@ -180,7 +180,7 @@ export async function exportCanvas(
|
|||||||
if (blob) {
|
if (blob) {
|
||||||
await fileSave(blob, {
|
await fileSave(blob, {
|
||||||
fileName: fileName,
|
fileName: fileName,
|
||||||
description: "Excalidraw image"
|
description: "Excalidraw image",
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -190,7 +190,7 @@ export async function exportCanvas(
|
|||||||
tempCanvas.toBlob(async function(blob: any) {
|
tempCanvas.toBlob(async function(blob: any) {
|
||||||
try {
|
try {
|
||||||
await navigator.clipboard.write([
|
await navigator.clipboard.write([
|
||||||
new window.ClipboardItem({ "image/png": blob })
|
new window.ClipboardItem({ "image/png": blob }),
|
||||||
]);
|
]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
window.alert(errorMsg);
|
window.alert(errorMsg);
|
||||||
@ -213,7 +213,7 @@ export async function exportCanvas(
|
|||||||
|
|
||||||
function restore(
|
function restore(
|
||||||
savedElements: readonly ExcalidrawElement[],
|
savedElements: readonly ExcalidrawElement[],
|
||||||
savedState: AppState
|
savedState: AppState,
|
||||||
): DataState {
|
): DataState {
|
||||||
return {
|
return {
|
||||||
elements: savedElements.map(element => ({
|
elements: savedElements.map(element => ({
|
||||||
@ -225,9 +225,9 @@ function restore(
|
|||||||
opacity:
|
opacity:
|
||||||
element.opacity === null || element.opacity === undefined
|
element.opacity === null || element.opacity === undefined
|
||||||
? 100
|
? 100
|
||||||
: element.opacity
|
: element.opacity,
|
||||||
})),
|
})),
|
||||||
appState: savedState
|
appState: savedState,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -239,7 +239,7 @@ export function restoreFromLocalStorage() {
|
|||||||
if (savedElements) {
|
if (savedElements) {
|
||||||
try {
|
try {
|
||||||
elements = JSON.parse(savedElements).map(
|
elements = JSON.parse(savedElements).map(
|
||||||
({ shape, ...element }: ExcalidrawElement) => element
|
({ shape, ...element }: ExcalidrawElement) => element,
|
||||||
);
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// Do nothing because elements array is already empty
|
// Do nothing because elements array is already empty
|
||||||
@ -260,7 +260,7 @@ export function restoreFromLocalStorage() {
|
|||||||
|
|
||||||
export function saveToLocalStorage(
|
export function saveToLocalStorage(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
state: AppState
|
state: AppState,
|
||||||
) {
|
) {
|
||||||
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(elements));
|
localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(elements));
|
||||||
localStorage.setItem(LOCAL_STORAGE_KEY_STATE, JSON.stringify(state));
|
localStorage.setItem(LOCAL_STORAGE_KEY_STATE, JSON.stringify(state));
|
||||||
|
@ -9,7 +9,7 @@ export function getExportCanvasPreview(
|
|||||||
exportBackground,
|
exportBackground,
|
||||||
exportPadding = 10,
|
exportPadding = 10,
|
||||||
viewBackgroundColor,
|
viewBackgroundColor,
|
||||||
scale = 1
|
scale = 1,
|
||||||
}: {
|
}: {
|
||||||
exportBackground: boolean;
|
exportBackground: boolean;
|
||||||
exportPadding?: number;
|
exportPadding?: number;
|
||||||
@ -18,13 +18,13 @@ export function getExportCanvasPreview(
|
|||||||
},
|
},
|
||||||
createCanvas: (width: number, height: number) => any = function(
|
createCanvas: (width: number, height: number) => any = function(
|
||||||
width,
|
width,
|
||||||
height
|
height,
|
||||||
) {
|
) {
|
||||||
const tempCanvas = document.createElement("canvas");
|
const tempCanvas = document.createElement("canvas");
|
||||||
tempCanvas.width = width * scale;
|
tempCanvas.width = width * scale;
|
||||||
tempCanvas.height = height * scale;
|
tempCanvas.height = height * scale;
|
||||||
return tempCanvas;
|
return tempCanvas;
|
||||||
}
|
},
|
||||||
) {
|
) {
|
||||||
// calculate smallest area to fit the contents in
|
// calculate smallest area to fit the contents in
|
||||||
let subCanvasX1 = Infinity;
|
let subCanvasX1 = Infinity;
|
||||||
@ -56,14 +56,14 @@ export function getExportCanvasPreview(
|
|||||||
{
|
{
|
||||||
viewBackgroundColor: exportBackground ? viewBackgroundColor : null,
|
viewBackgroundColor: exportBackground ? viewBackgroundColor : null,
|
||||||
scrollX: 0,
|
scrollX: 0,
|
||||||
scrollY: 0
|
scrollY: 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
offsetX: -subCanvasX1 + exportPadding,
|
offsetX: -subCanvasX1 + exportPadding,
|
||||||
offsetY: -subCanvasY1 + exportPadding,
|
offsetY: -subCanvasY1 + exportPadding,
|
||||||
renderScrollbars: false,
|
renderScrollbars: false,
|
||||||
renderSelection: false
|
renderSelection: false,
|
||||||
}
|
},
|
||||||
);
|
);
|
||||||
return tempCanvas;
|
return tempCanvas;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@ export {
|
|||||||
deleteSelectedElements,
|
deleteSelectedElements,
|
||||||
someElementIsSelected,
|
someElementIsSelected,
|
||||||
getElementsWithinSelection,
|
getElementsWithinSelection,
|
||||||
getCommonAttributeOfSelectedElements
|
getCommonAttributeOfSelectedElements,
|
||||||
} from "./selection";
|
} from "./selection";
|
||||||
export {
|
export {
|
||||||
exportCanvas,
|
exportCanvas,
|
||||||
@ -14,13 +14,13 @@ export {
|
|||||||
restoreFromLocalStorage,
|
restoreFromLocalStorage,
|
||||||
saveToLocalStorage,
|
saveToLocalStorage,
|
||||||
exportToBackend,
|
exportToBackend,
|
||||||
importFromBackend
|
importFromBackend,
|
||||||
} from "./data";
|
} from "./data";
|
||||||
export {
|
export {
|
||||||
hasBackground,
|
hasBackground,
|
||||||
hasStroke,
|
hasStroke,
|
||||||
getElementAtPosition,
|
getElementAtPosition,
|
||||||
getElementContainingPosition,
|
getElementContainingPosition,
|
||||||
hasText
|
hasText,
|
||||||
} from "./comparisons";
|
} from "./comparisons";
|
||||||
export { createScene } from "./createScene";
|
export { createScene } from "./createScene";
|
||||||
|
@ -11,7 +11,7 @@ export function getScrollBars(
|
|||||||
canvasWidth: number,
|
canvasWidth: number,
|
||||||
canvasHeight: number,
|
canvasHeight: number,
|
||||||
scrollX: number,
|
scrollX: number,
|
||||||
scrollY: number
|
scrollY: number,
|
||||||
) {
|
) {
|
||||||
let minX = Infinity;
|
let minX = Infinity;
|
||||||
let maxX = 0;
|
let maxX = 0;
|
||||||
@ -41,14 +41,14 @@ export function getScrollBars(
|
|||||||
horizontalScrollBar = {
|
horizontalScrollBar = {
|
||||||
x: Math.min(
|
x: Math.min(
|
||||||
leftOverflow + SCROLLBAR_MARGIN,
|
leftOverflow + SCROLLBAR_MARGIN,
|
||||||
canvasWidth - SCROLLBAR_MIN_SIZE - SCROLLBAR_MARGIN
|
canvasWidth - SCROLLBAR_MIN_SIZE - SCROLLBAR_MARGIN,
|
||||||
),
|
),
|
||||||
y: canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
|
y: canvasHeight - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
|
||||||
width: Math.max(
|
width: Math.max(
|
||||||
canvasWidth - rightOverflow - leftOverflow - SCROLLBAR_MARGIN * 2,
|
canvasWidth - rightOverflow - leftOverflow - SCROLLBAR_MARGIN * 2,
|
||||||
SCROLLBAR_MIN_SIZE
|
SCROLLBAR_MIN_SIZE,
|
||||||
),
|
),
|
||||||
height: SCROLLBAR_WIDTH
|
height: SCROLLBAR_WIDTH,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,19 +59,19 @@ export function getScrollBars(
|
|||||||
x: canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
|
x: canvasWidth - SCROLLBAR_WIDTH - SCROLLBAR_MARGIN,
|
||||||
y: Math.min(
|
y: Math.min(
|
||||||
topOverflow + SCROLLBAR_MARGIN,
|
topOverflow + SCROLLBAR_MARGIN,
|
||||||
canvasHeight - SCROLLBAR_MIN_SIZE - SCROLLBAR_MARGIN
|
canvasHeight - SCROLLBAR_MIN_SIZE - SCROLLBAR_MARGIN,
|
||||||
),
|
),
|
||||||
width: SCROLLBAR_WIDTH,
|
width: SCROLLBAR_WIDTH,
|
||||||
height: Math.max(
|
height: Math.max(
|
||||||
canvasHeight - bottomOverflow - topOverflow - SCROLLBAR_WIDTH * 2,
|
canvasHeight - bottomOverflow - topOverflow - SCROLLBAR_WIDTH * 2,
|
||||||
SCROLLBAR_MIN_SIZE
|
SCROLLBAR_MIN_SIZE,
|
||||||
)
|
),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
horizontal: horizontalScrollBar,
|
horizontal: horizontalScrollBar,
|
||||||
vertical: verticalScrollBar
|
vertical: verticalScrollBar,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -82,30 +82,30 @@ export function isOverScrollBars(
|
|||||||
canvasWidth: number,
|
canvasWidth: number,
|
||||||
canvasHeight: number,
|
canvasHeight: number,
|
||||||
scrollX: number,
|
scrollX: number,
|
||||||
scrollY: number
|
scrollY: number,
|
||||||
) {
|
) {
|
||||||
const scrollBars = getScrollBars(
|
const scrollBars = getScrollBars(
|
||||||
elements,
|
elements,
|
||||||
canvasWidth,
|
canvasWidth,
|
||||||
canvasHeight,
|
canvasHeight,
|
||||||
scrollX,
|
scrollX,
|
||||||
scrollY
|
scrollY,
|
||||||
);
|
);
|
||||||
|
|
||||||
const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [
|
const [isOverHorizontalScrollBar, isOverVerticalScrollBar] = [
|
||||||
scrollBars.horizontal,
|
scrollBars.horizontal,
|
||||||
scrollBars.vertical
|
scrollBars.vertical,
|
||||||
].map(
|
].map(
|
||||||
scrollBar =>
|
scrollBar =>
|
||||||
scrollBar &&
|
scrollBar &&
|
||||||
scrollBar.x <= x &&
|
scrollBar.x <= x &&
|
||||||
x <= scrollBar.x + scrollBar.width &&
|
x <= scrollBar.x + scrollBar.width &&
|
||||||
scrollBar.y <= y &&
|
scrollBar.y <= y &&
|
||||||
y <= scrollBar.y + scrollBar.height
|
y <= scrollBar.y + scrollBar.height,
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
isOverHorizontalScrollBar,
|
isOverHorizontalScrollBar,
|
||||||
isOverVerticalScrollBar
|
isOverVerticalScrollBar,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,20 @@ import { getElementAbsoluteCoords } from "../element";
|
|||||||
|
|
||||||
export function getElementsWithinSelection(
|
export function getElementsWithinSelection(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
selection: ExcalidrawElement
|
selection: ExcalidrawElement,
|
||||||
) {
|
) {
|
||||||
const [
|
const [
|
||||||
selectionX1,
|
selectionX1,
|
||||||
selectionY1,
|
selectionY1,
|
||||||
selectionX2,
|
selectionX2,
|
||||||
selectionY2
|
selectionY2,
|
||||||
] = getElementAbsoluteCoords(selection);
|
] = getElementAbsoluteCoords(selection);
|
||||||
return elements.filter(element => {
|
return elements.filter(element => {
|
||||||
const [
|
const [
|
||||||
elementX1,
|
elementX1,
|
||||||
elementY1,
|
elementY1,
|
||||||
elementX2,
|
elementX2,
|
||||||
elementY2
|
elementY2,
|
||||||
] = getElementAbsoluteCoords(element);
|
] = getElementAbsoluteCoords(element);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -62,14 +62,14 @@ export const someElementIsSelected = (elements: readonly ExcalidrawElement[]) =>
|
|||||||
*/
|
*/
|
||||||
export function getCommonAttributeOfSelectedElements<T>(
|
export function getCommonAttributeOfSelectedElements<T>(
|
||||||
elements: readonly ExcalidrawElement[],
|
elements: readonly ExcalidrawElement[],
|
||||||
getAttribute: (element: ExcalidrawElement) => T
|
getAttribute: (element: ExcalidrawElement) => T,
|
||||||
): T | null {
|
): T | null {
|
||||||
const attributes = Array.from(
|
const attributes = Array.from(
|
||||||
new Set(
|
new Set(
|
||||||
elements
|
elements
|
||||||
.filter(element => element.isSelected)
|
.filter(element => element.isSelected)
|
||||||
.map(element => getAttribute(element))
|
.map(element => getAttribute(element)),
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
return attributes.length === 1 ? attributes[0] : null;
|
return attributes.length === 1 ? attributes[0] : null;
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ export const SHAPES = [
|
|||||||
<path d="M302.189 329.126H196.105l55.831 135.993c3.889 9.428-.555 19.999-9.444 23.999l-49.165 21.427c-9.165 4-19.443-.571-23.332-9.714l-53.053-129.136-86.664 89.138C18.729 472.71 0 463.554 0 447.977V18.299C0 1.899 19.921-6.096 30.277 5.443l284.412 292.542c11.472 11.179 3.007 31.141-12.5 31.141z" />
|
<path d="M302.189 329.126H196.105l55.831 135.993c3.889 9.428-.555 19.999-9.444 23.999l-49.165 21.427c-9.165 4-19.443-.571-23.332-9.714l-53.053-129.136-86.664 89.138C18.729 472.71 0 463.554 0 447.977V18.299C0 1.899 19.921-6.096 30.277 5.443l284.412 292.542c11.472 11.179 3.007 31.141-12.5 31.141z" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
value: "selection"
|
value: "selection",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
@ -18,7 +18,7 @@ export const SHAPES = [
|
|||||||
<path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z" />
|
<path d="M400 32H48C21.5 32 0 53.5 0 80v352c0 26.5 21.5 48 48 48h352c26.5 0 48-21.5 48-48V80c0-26.5-21.5-48-48-48z" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
value: "rectangle"
|
value: "rectangle",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
@ -27,7 +27,7 @@ export const SHAPES = [
|
|||||||
<path d="M111.823 0L16.622 111.823 111.823 223.646 207.025 111.823z" />
|
<path d="M111.823 0L16.622 111.823 111.823 223.646 207.025 111.823z" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
value: "diamond"
|
value: "diamond",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
@ -36,7 +36,7 @@ export const SHAPES = [
|
|||||||
<path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z" />
|
<path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
value: "ellipse"
|
value: "ellipse",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
@ -45,7 +45,7 @@ export const SHAPES = [
|
|||||||
<path d="M313.941 216H12c-6.627 0-12 5.373-12 12v56c0 6.627 5.373 12 12 12h301.941v46.059c0 21.382 25.851 32.09 40.971 16.971l86.059-86.059c9.373-9.373 9.373-24.569 0-33.941l-86.059-86.059c-15.119-15.119-40.971-4.411-40.971 16.971V216z" />
|
<path d="M313.941 216H12c-6.627 0-12 5.373-12 12v56c0 6.627 5.373 12 12 12h301.941v46.059c0 21.382 25.851 32.09 40.971 16.971l86.059-86.059c9.373-9.373 9.373-24.569 0-33.941l-86.059-86.059c-15.119-15.119-40.971-4.411-40.971 16.971V216z" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
value: "arrow"
|
value: "arrow",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
@ -54,7 +54,7 @@ export const SHAPES = [
|
|||||||
<line x1="0" y1="3" x2="6" y2="3" stroke="#000" strokeLinecap="round" />
|
<line x1="0" y1="3" x2="6" y2="3" stroke="#000" strokeLinecap="round" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
value: "line"
|
value: "line",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
icon: (
|
icon: (
|
||||||
@ -63,13 +63,13 @@ export const SHAPES = [
|
|||||||
<path d="M432 416h-23.41L277.88 53.69A32 32 0 0 0 247.58 32h-47.16a32 32 0 0 0-30.3 21.69L39.41 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16h-19.58l23.3-64h152.56l23.3 64H304a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM176.85 272L224 142.51 271.15 272z" />
|
<path d="M432 416h-23.41L277.88 53.69A32 32 0 0 0 247.58 32h-47.16a32 32 0 0 0-30.3 21.69L39.41 416H16a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16h-19.58l23.3-64h152.56l23.3 64H304a16 16 0 0 0-16 16v32a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16v-32a16 16 0 0 0-16-16zM176.85 272L224 142.51 271.15 272z" />
|
||||||
</svg>
|
</svg>
|
||||||
),
|
),
|
||||||
value: "text"
|
value: "text",
|
||||||
}
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const shapesShortcutKeys = SHAPES.map((shape, index) => [
|
export const shapesShortcutKeys = SHAPES.map((shape, index) => [
|
||||||
shape.value[0],
|
shape.value[0],
|
||||||
(index + 1).toString()
|
(index + 1).toString(),
|
||||||
]).flat(1);
|
]).flat(1);
|
||||||
|
|
||||||
export function findShapeByKey(key: string) {
|
export function findShapeByKey(key: string) {
|
||||||
|
@ -15,7 +15,7 @@ export function capitalizeString(str: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isInputLike(
|
export function isInputLike(
|
||||||
target: Element | EventTarget | null
|
target: Element | EventTarget | null,
|
||||||
): target is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement {
|
): target is HTMLInputElement | HTMLTextAreaElement | HTMLSelectElement {
|
||||||
return (
|
return (
|
||||||
(target instanceof HTMLElement && target.dataset.type === "wysiwyg") ||
|
(target instanceof HTMLElement && target.dataset.type === "wysiwyg") ||
|
||||||
@ -54,7 +54,7 @@ export function measureText(text: string, font: string) {
|
|||||||
|
|
||||||
export function debounce<T extends any[]>(
|
export function debounce<T extends any[]>(
|
||||||
fn: (...args: T) => void,
|
fn: (...args: T) => void,
|
||||||
timeout: number
|
timeout: number,
|
||||||
) {
|
) {
|
||||||
let handle = 0;
|
let handle = 0;
|
||||||
let lastArgs: T;
|
let lastArgs: T;
|
||||||
|
@ -4,7 +4,7 @@ function expectMove<T>(
|
|||||||
fn: (elements: T[], indicesToMove: number[]) => void,
|
fn: (elements: T[], indicesToMove: number[]) => void,
|
||||||
elems: T[],
|
elems: T[],
|
||||||
indices: number[],
|
indices: number[],
|
||||||
equal: T[]
|
equal: T[],
|
||||||
) {
|
) {
|
||||||
fn(elems, indices);
|
fn(elems, indices);
|
||||||
expect(elems).toEqual(equal);
|
expect(elems).toEqual(equal);
|
||||||
@ -17,7 +17,7 @@ it("should moveOneLeft", () => {
|
|||||||
moveOneLeft,
|
moveOneLeft,
|
||||||
["a", "b", "c", "d"],
|
["a", "b", "c", "d"],
|
||||||
[0, 1, 2, 3],
|
[0, 1, 2, 3],
|
||||||
["a", "b", "c", "d"]
|
["a", "b", "c", "d"],
|
||||||
);
|
);
|
||||||
expectMove(moveOneLeft, ["a", "b", "c", "d"], [1, 3], ["b", "a", "d", "c"]);
|
expectMove(moveOneLeft, ["a", "b", "c", "d"], [1, 3], ["b", "a", "d", "c"]);
|
||||||
});
|
});
|
||||||
@ -29,7 +29,7 @@ it("should moveOneRight", () => {
|
|||||||
moveOneRight,
|
moveOneRight,
|
||||||
["a", "b", "c", "d"],
|
["a", "b", "c", "d"],
|
||||||
[0, 1, 2, 3],
|
[0, 1, 2, 3],
|
||||||
["a", "b", "c", "d"]
|
["a", "b", "c", "d"],
|
||||||
);
|
);
|
||||||
expectMove(moveOneRight, ["a", "b", "c", "d"], [0, 2], ["b", "a", "d", "c"]);
|
expectMove(moveOneRight, ["a", "b", "c", "d"], [0, 2], ["b", "a", "d", "c"]);
|
||||||
});
|
});
|
||||||
@ -39,31 +39,31 @@ it("should moveAllLeft", () => {
|
|||||||
moveAllLeft,
|
moveAllLeft,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[2, 5],
|
[2, 5],
|
||||||
["c", "f", "a", "b", "d", "e", "g"]
|
["c", "f", "a", "b", "d", "e", "g"],
|
||||||
);
|
);
|
||||||
expectMove(
|
expectMove(
|
||||||
moveAllLeft,
|
moveAllLeft,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[5],
|
[5],
|
||||||
["f", "a", "b", "c", "d", "e", "g"]
|
["f", "a", "b", "c", "d", "e", "g"],
|
||||||
);
|
);
|
||||||
expectMove(
|
expectMove(
|
||||||
moveAllLeft,
|
moveAllLeft,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[0, 1, 2, 3, 4, 5, 6],
|
[0, 1, 2, 3, 4, 5, 6],
|
||||||
["a", "b", "c", "d", "e", "f", "g"]
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
);
|
);
|
||||||
expectMove(
|
expectMove(
|
||||||
moveAllLeft,
|
moveAllLeft,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[0, 1, 2],
|
[0, 1, 2],
|
||||||
["a", "b", "c", "d", "e", "f", "g"]
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
);
|
);
|
||||||
expectMove(
|
expectMove(
|
||||||
moveAllLeft,
|
moveAllLeft,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[4, 5, 6],
|
[4, 5, 6],
|
||||||
["e", "f", "g", "a", "b", "c", "d"]
|
["e", "f", "g", "a", "b", "c", "d"],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -72,30 +72,30 @@ it("should moveAllRight", () => {
|
|||||||
moveAllRight,
|
moveAllRight,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[2, 5],
|
[2, 5],
|
||||||
["a", "b", "d", "e", "g", "c", "f"]
|
["a", "b", "d", "e", "g", "c", "f"],
|
||||||
);
|
);
|
||||||
expectMove(
|
expectMove(
|
||||||
moveAllRight,
|
moveAllRight,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[5],
|
[5],
|
||||||
["a", "b", "c", "d", "e", "g", "f"]
|
["a", "b", "c", "d", "e", "g", "f"],
|
||||||
);
|
);
|
||||||
expectMove(
|
expectMove(
|
||||||
moveAllRight,
|
moveAllRight,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[0, 1, 2, 3, 4, 5, 6],
|
[0, 1, 2, 3, 4, 5, 6],
|
||||||
["a", "b", "c", "d", "e", "f", "g"]
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
);
|
);
|
||||||
expectMove(
|
expectMove(
|
||||||
moveAllRight,
|
moveAllRight,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[0, 1, 2],
|
[0, 1, 2],
|
||||||
["d", "e", "f", "g", "a", "b", "c"]
|
["d", "e", "f", "g", "a", "b", "c"],
|
||||||
);
|
);
|
||||||
expectMove(
|
expectMove(
|
||||||
moveAllRight,
|
moveAllRight,
|
||||||
["a", "b", "c", "d", "e", "f", "g"],
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
[4, 5, 6],
|
[4, 5, 6],
|
||||||
["a", "b", "c", "d", "e", "f", "g"]
|
["a", "b", "c", "d", "e", "f", "g"],
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -23,7 +23,7 @@ export function moveOneLeft<T>(elements: T[], indicesToMove: number[]) {
|
|||||||
|
|
||||||
export function moveOneRight<T>(elements: T[], indicesToMove: number[]) {
|
export function moveOneRight<T>(elements: T[], indicesToMove: number[]) {
|
||||||
const reversedIndicesToMove = indicesToMove.sort(
|
const reversedIndicesToMove = indicesToMove.sort(
|
||||||
(a: number, b: number) => b - a
|
(a: number, b: number) => b - a,
|
||||||
);
|
);
|
||||||
let isSorted = true;
|
let isSorted = true;
|
||||||
|
|
||||||
@ -166,7 +166,7 @@ export function moveAllLeft<T>(elements: T[], indicesToMove: number[]) {
|
|||||||
// And we are done!
|
// And we are done!
|
||||||
export function moveAllRight<T>(elements: T[], indicesToMove: number[]) {
|
export function moveAllRight<T>(elements: T[], indicesToMove: number[]) {
|
||||||
const reversedIndicesToMove = indicesToMove.sort(
|
const reversedIndicesToMove = indicesToMove.sort(
|
||||||
(a: number, b: number) => b - a
|
(a: number, b: number) => b - a,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Copy the elements to move
|
// Copy the elements to move
|
||||||
|
Loading…
x
Reference in New Issue
Block a user