Refactor ExcalidrawElement (#874)
* Get rid of isSelected, canvas, canvasZoom, canvasOffsetX and canvasOffsetY on ExcalidrawElement. * Fix most unit tests. Fix cmd a. Fix alt drag * Focus on paste * shift select should include previously selected items * Fix last test * Move this.shape out of ExcalidrawElement and into a WeakMap
This commit is contained in:
@ -1,7 +1,7 @@
|
||||
import React from "react";
|
||||
import { ExcalidrawElement } from "../element/types";
|
||||
import { ActionManager } from "../actions/manager";
|
||||
import { hasBackground, hasStroke, hasText, clearSelection } from "../scene";
|
||||
import { hasBackground, hasStroke, hasText } from "../scene";
|
||||
import { t } from "../i18n";
|
||||
import { SHAPES } from "../shapes";
|
||||
import { ToolButton } from "./ToolButton";
|
||||
@ -92,8 +92,11 @@ export function ShapesSwitcher({
|
||||
aria-label={capitalizeString(label)}
|
||||
aria-keyshortcuts={`${label[0]} ${index + 1}`}
|
||||
onChange={() => {
|
||||
setAppState({ elementType: value, multiElement: null });
|
||||
setElements(clearSelection(elements));
|
||||
setAppState({
|
||||
elementType: value,
|
||||
multiElement: null,
|
||||
selectedElementIds: {},
|
||||
});
|
||||
document.documentElement.style.cursor =
|
||||
value === "text" ? CURSOR_TYPE.TEXT : CURSOR_TYPE.CROSSHAIR;
|
||||
setAppState({});
|
||||
|
@ -19,7 +19,6 @@ import {
|
||||
normalizeDimensions,
|
||||
} from "../element";
|
||||
import {
|
||||
clearSelection,
|
||||
deleteSelectedElements,
|
||||
getElementsWithinSelection,
|
||||
isOverScrollBars,
|
||||
@ -77,6 +76,7 @@ import {
|
||||
} from "../constants";
|
||||
import { LayerUI } from "./LayerUI";
|
||||
import { ScrollBars } from "../scene/types";
|
||||
import { invalidateShapeForElement } from "../renderer/renderElement";
|
||||
|
||||
// -----------------------------------------------------------------------------
|
||||
// TEST HOOKS
|
||||
@ -179,8 +179,8 @@ export class App extends React.Component<any, AppState> {
|
||||
if (isWritableElement(event.target)) {
|
||||
return;
|
||||
}
|
||||
copyToAppClipboard(elements);
|
||||
elements = deleteSelectedElements(elements);
|
||||
copyToAppClipboard(elements, this.state);
|
||||
elements = deleteSelectedElements(elements, this.state);
|
||||
history.resumeRecording();
|
||||
this.setState({});
|
||||
event.preventDefault();
|
||||
@ -189,7 +189,7 @@ export class App extends React.Component<any, AppState> {
|
||||
if (isWritableElement(event.target)) {
|
||||
return;
|
||||
}
|
||||
copyToAppClipboard(elements);
|
||||
copyToAppClipboard(elements, this.state);
|
||||
event.preventDefault();
|
||||
};
|
||||
|
||||
@ -296,7 +296,7 @@ export class App extends React.Component<any, AppState> {
|
||||
public state: AppState = getDefaultAppState();
|
||||
|
||||
private onResize = () => {
|
||||
elements = elements.map(el => ({ ...el, shape: null }));
|
||||
elements.forEach(element => invalidateShapeForElement(element));
|
||||
this.setState({});
|
||||
};
|
||||
|
||||
@ -325,7 +325,7 @@ export class App extends React.Component<any, AppState> {
|
||||
? ELEMENT_SHIFT_TRANSLATE_AMOUNT
|
||||
: ELEMENT_TRANSLATE_AMOUNT;
|
||||
elements = elements.map(el => {
|
||||
if (el.isSelected) {
|
||||
if (this.state.selectedElementIds[el.id]) {
|
||||
const element = { ...el };
|
||||
if (event.key === KEYS.ARROW_LEFT) {
|
||||
element.x -= step;
|
||||
@ -361,19 +361,18 @@ export class App extends React.Component<any, AppState> {
|
||||
if (this.state.elementType === "selection") {
|
||||
resetCursor();
|
||||
} else {
|
||||
elements = clearSelection(elements);
|
||||
document.documentElement.style.cursor =
|
||||
this.state.elementType === "text"
|
||||
? CURSOR_TYPE.TEXT
|
||||
: CURSOR_TYPE.CROSSHAIR;
|
||||
this.setState({});
|
||||
this.setState({ selectedElementIds: {} });
|
||||
}
|
||||
isHoldingSpace = false;
|
||||
}
|
||||
};
|
||||
|
||||
private copyToAppClipboard = () => {
|
||||
copyToAppClipboard(elements);
|
||||
copyToAppClipboard(elements, this.state);
|
||||
};
|
||||
|
||||
private pasteFromClipboard = async (event: ClipboardEvent | null) => {
|
||||
@ -413,9 +412,8 @@ export class App extends React.Component<any, AppState> {
|
||||
this.state.currentItemFont,
|
||||
);
|
||||
|
||||
element.isSelected = true;
|
||||
|
||||
elements = [...clearSelection(elements), element];
|
||||
elements = [...elements, element];
|
||||
this.setState({ selectedElementIds: { [element.id]: true } });
|
||||
history.resumeRecording();
|
||||
}
|
||||
this.selectShapeTool("selection");
|
||||
@ -431,9 +429,10 @@ export class App extends React.Component<any, AppState> {
|
||||
document.activeElement.blur();
|
||||
}
|
||||
if (elementType !== "selection") {
|
||||
elements = clearSelection(elements);
|
||||
this.setState({ elementType, selectedElementIds: {} });
|
||||
} else {
|
||||
this.setState({ elementType });
|
||||
}
|
||||
this.setState({ elementType });
|
||||
}
|
||||
|
||||
private onGestureStart = (event: GestureEvent) => {
|
||||
@ -524,6 +523,7 @@ export class App extends React.Component<any, AppState> {
|
||||
|
||||
const element = getElementAtPosition(
|
||||
elements,
|
||||
this.state,
|
||||
x,
|
||||
y,
|
||||
this.state.zoom,
|
||||
@ -545,10 +545,8 @@ export class App extends React.Component<any, AppState> {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!element.isSelected) {
|
||||
elements = clearSelection(elements);
|
||||
element.isSelected = true;
|
||||
this.setState({});
|
||||
if (!this.state.selectedElementIds[element.id]) {
|
||||
this.setState({ selectedElementIds: { [element.id]: true } });
|
||||
}
|
||||
|
||||
ContextMenu.push({
|
||||
@ -760,12 +758,16 @@ export class App extends React.Component<any, AppState> {
|
||||
if (this.state.elementType === "selection") {
|
||||
const resizeElement = getElementWithResizeHandler(
|
||||
elements,
|
||||
this.state,
|
||||
{ x, y },
|
||||
this.state.zoom,
|
||||
event.pointerType,
|
||||
);
|
||||
|
||||
const selectedElements = getSelectedElements(elements);
|
||||
const selectedElements = getSelectedElements(
|
||||
elements,
|
||||
this.state,
|
||||
);
|
||||
if (selectedElements.length === 1 && resizeElement) {
|
||||
this.setState({
|
||||
resizingElement: resizeElement
|
||||
@ -781,13 +783,19 @@ export class App extends React.Component<any, AppState> {
|
||||
} else {
|
||||
hitElement = getElementAtPosition(
|
||||
elements,
|
||||
this.state,
|
||||
x,
|
||||
y,
|
||||
this.state.zoom,
|
||||
);
|
||||
// clear selection if shift is not clicked
|
||||
if (!hitElement?.isSelected && !event.shiftKey) {
|
||||
elements = clearSelection(elements);
|
||||
if (
|
||||
!(
|
||||
hitElement && this.state.selectedElementIds[hitElement.id]
|
||||
) &&
|
||||
!event.shiftKey
|
||||
) {
|
||||
this.setState({ selectedElementIds: {} });
|
||||
}
|
||||
|
||||
// If we click on something
|
||||
@ -796,30 +804,37 @@ export class App extends React.Component<any, AppState> {
|
||||
// if shift is not clicked, this will always return true
|
||||
// otherwise, it will trigger selection based on current
|
||||
// state of the box
|
||||
if (!hitElement.isSelected) {
|
||||
hitElement.isSelected = true;
|
||||
if (!this.state.selectedElementIds[hitElement.id]) {
|
||||
this.setState(prevState => ({
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
[hitElement!.id]: true,
|
||||
},
|
||||
}));
|
||||
elements = elements.slice();
|
||||
elementIsAddedToSelection = true;
|
||||
}
|
||||
|
||||
// We duplicate the selected element if alt is pressed on pointer down
|
||||
if (event.altKey) {
|
||||
elements = [
|
||||
...elements.map(element => ({
|
||||
...element,
|
||||
isSelected: false,
|
||||
})),
|
||||
...getSelectedElements(elements).map(element => {
|
||||
const newElement = duplicateElement(element);
|
||||
newElement.isSelected = true;
|
||||
return newElement;
|
||||
}),
|
||||
];
|
||||
// Move the currently selected elements to the top of the z index stack, and
|
||||
// put the duplicates where the selected elements used to be.
|
||||
const nextElements = [];
|
||||
const elementsToAppend = [];
|
||||
for (const element of elements) {
|
||||
if (this.state.selectedElementIds[element.id]) {
|
||||
nextElements.push(duplicateElement(element));
|
||||
elementsToAppend.push(element);
|
||||
} else {
|
||||
nextElements.push(element);
|
||||
}
|
||||
}
|
||||
elements = [...nextElements, ...elementsToAppend];
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
elements = clearSelection(elements);
|
||||
this.setState({ selectedElementIds: {} });
|
||||
}
|
||||
|
||||
if (isTextElement(element)) {
|
||||
@ -872,10 +887,15 @@ export class App extends React.Component<any, AppState> {
|
||||
text,
|
||||
this.state.currentItemFont,
|
||||
),
|
||||
isSelected: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
this.setState(prevState => ({
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
[element.id]: true,
|
||||
},
|
||||
}));
|
||||
if (this.state.elementLocked) {
|
||||
setCursorForShape(this.state.elementType);
|
||||
}
|
||||
@ -905,13 +925,23 @@ export class App extends React.Component<any, AppState> {
|
||||
if (this.state.multiElement) {
|
||||
const { multiElement } = this.state;
|
||||
const { x: rx, y: ry } = multiElement;
|
||||
multiElement.isSelected = true;
|
||||
this.setState(prevState => ({
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
[multiElement.id]: true,
|
||||
},
|
||||
}));
|
||||
multiElement.points.push([x - rx, y - ry]);
|
||||
multiElement.shape = null;
|
||||
invalidateShapeForElement(multiElement);
|
||||
} else {
|
||||
element.isSelected = false;
|
||||
this.setState(prevState => ({
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
[element.id]: false,
|
||||
},
|
||||
}));
|
||||
element.points.push([0, 0]);
|
||||
element.shape = null;
|
||||
invalidateShapeForElement(element);
|
||||
elements = [...elements, element];
|
||||
this.setState({
|
||||
draggingElement: element,
|
||||
@ -1047,7 +1077,10 @@ export class App extends React.Component<any, AppState> {
|
||||
if (isResizingElements && this.state.resizingElement) {
|
||||
this.setState({ isResizing: true });
|
||||
const el = this.state.resizingElement;
|
||||
const selectedElements = getSelectedElements(elements);
|
||||
const selectedElements = getSelectedElements(
|
||||
elements,
|
||||
this.state,
|
||||
);
|
||||
if (selectedElements.length === 1) {
|
||||
const { x, y } = viewportCoordsToSceneCoords(
|
||||
event,
|
||||
@ -1261,7 +1294,7 @@ export class App extends React.Component<any, AppState> {
|
||||
);
|
||||
el.x = element.x;
|
||||
el.y = element.y;
|
||||
el.shape = null;
|
||||
invalidateShapeForElement(el);
|
||||
|
||||
lastX = x;
|
||||
lastY = y;
|
||||
@ -1270,11 +1303,17 @@ export class App extends React.Component<any, AppState> {
|
||||
}
|
||||
}
|
||||
|
||||
if (hitElement?.isSelected) {
|
||||
if (
|
||||
hitElement &&
|
||||
this.state.selectedElementIds[hitElement.id]
|
||||
) {
|
||||
// Marking that click was used for dragging to check
|
||||
// if elements should be deselected on pointerup
|
||||
draggingOccurred = true;
|
||||
const selectedElements = getSelectedElements(elements);
|
||||
const selectedElements = getSelectedElements(
|
||||
elements,
|
||||
this.state,
|
||||
);
|
||||
if (selectedElements.length > 0) {
|
||||
const { x, y } = viewportCoordsToSceneCoords(
|
||||
event,
|
||||
@ -1354,19 +1393,30 @@ export class App extends React.Component<any, AppState> {
|
||||
draggingElement.height = height;
|
||||
}
|
||||
|
||||
draggingElement.shape = null;
|
||||
invalidateShapeForElement(draggingElement);
|
||||
|
||||
if (this.state.elementType === "selection") {
|
||||
if (!event.shiftKey && isSomeElementSelected(elements)) {
|
||||
elements = clearSelection(elements);
|
||||
if (
|
||||
!event.shiftKey &&
|
||||
isSomeElementSelected(elements, this.state)
|
||||
) {
|
||||
this.setState({ selectedElementIds: {} });
|
||||
}
|
||||
const elementsWithinSelection = getElementsWithinSelection(
|
||||
elements,
|
||||
draggingElement,
|
||||
);
|
||||
elementsWithinSelection.forEach(element => {
|
||||
element.isSelected = true;
|
||||
});
|
||||
this.setState(prevState => ({
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
...Object.fromEntries(
|
||||
elementsWithinSelection.map(element => [
|
||||
element.id,
|
||||
true,
|
||||
]),
|
||||
),
|
||||
},
|
||||
}));
|
||||
}
|
||||
this.setState({});
|
||||
};
|
||||
@ -1406,20 +1456,27 @@ export class App extends React.Component<any, AppState> {
|
||||
x - draggingElement.x,
|
||||
y - draggingElement.y,
|
||||
]);
|
||||
draggingElement.shape = null;
|
||||
invalidateShapeForElement(draggingElement);
|
||||
this.setState({ multiElement: this.state.draggingElement });
|
||||
} else if (draggingOccurred && !multiElement) {
|
||||
this.state.draggingElement!.isSelected = true;
|
||||
if (!elementLocked) {
|
||||
resetCursor();
|
||||
this.setState({
|
||||
this.setState(prevState => ({
|
||||
draggingElement: null,
|
||||
elementType: "selection",
|
||||
});
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
[this.state.draggingElement!.id]: true,
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
this.setState({
|
||||
this.setState(prevState => ({
|
||||
draggingElement: null,
|
||||
});
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
[this.state.draggingElement!.id]: true,
|
||||
},
|
||||
}));
|
||||
}
|
||||
}
|
||||
return;
|
||||
@ -1470,27 +1527,37 @@ export class App extends React.Component<any, AppState> {
|
||||
!elementIsAddedToSelection
|
||||
) {
|
||||
if (event.shiftKey) {
|
||||
hitElement.isSelected = false;
|
||||
this.setState(prevState => ({
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
[hitElement!.id]: false,
|
||||
},
|
||||
}));
|
||||
} else {
|
||||
elements = clearSelection(elements);
|
||||
hitElement.isSelected = true;
|
||||
this.setState(prevState => ({
|
||||
selectedElementIds: { [hitElement!.id]: true },
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
if (draggingElement === null) {
|
||||
// if no element is clicked, clear the selection and redraw
|
||||
elements = clearSelection(elements);
|
||||
this.setState({});
|
||||
this.setState({ selectedElementIds: {} });
|
||||
return;
|
||||
}
|
||||
|
||||
if (!elementLocked) {
|
||||
draggingElement.isSelected = true;
|
||||
this.setState(prevState => ({
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
[draggingElement.id]: true,
|
||||
},
|
||||
}));
|
||||
}
|
||||
|
||||
if (
|
||||
elementType !== "selection" ||
|
||||
isSomeElementSelected(elements)
|
||||
isSomeElementSelected(elements, this.state)
|
||||
) {
|
||||
history.resumeRecording();
|
||||
}
|
||||
@ -1524,6 +1591,7 @@ export class App extends React.Component<any, AppState> {
|
||||
|
||||
const elementAtPosition = getElementAtPosition(
|
||||
elements,
|
||||
this.state,
|
||||
x,
|
||||
y,
|
||||
this.state.zoom,
|
||||
@ -1616,10 +1684,15 @@ export class App extends React.Component<any, AppState> {
|
||||
// we need to recreate the element to update dimensions &
|
||||
// position
|
||||
...newTextElement(element, text, element.font),
|
||||
isSelected: true,
|
||||
},
|
||||
];
|
||||
}
|
||||
this.setState(prevState => ({
|
||||
selectedElementIds: {
|
||||
...prevState.selectedElementIds,
|
||||
[element.id]: true,
|
||||
},
|
||||
}));
|
||||
history.resumeRecording();
|
||||
resetSelection();
|
||||
},
|
||||
@ -1695,7 +1768,7 @@ export class App extends React.Component<any, AppState> {
|
||||
const pnt = points[points.length - 1];
|
||||
pnt[0] = x - originX;
|
||||
pnt[1] = y - originY;
|
||||
multiElement.shape = null;
|
||||
invalidateShapeForElement(multiElement);
|
||||
this.setState({});
|
||||
return;
|
||||
}
|
||||
@ -1708,10 +1781,14 @@ export class App extends React.Component<any, AppState> {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedElements = getSelectedElements(elements);
|
||||
const selectedElements = getSelectedElements(
|
||||
elements,
|
||||
this.state,
|
||||
);
|
||||
if (selectedElements.length === 1 && !isOverScrollBar) {
|
||||
const resizeElement = getElementWithResizeHandler(
|
||||
elements,
|
||||
this.state,
|
||||
{ x, y },
|
||||
this.state.zoom,
|
||||
event.pointerType,
|
||||
@ -1725,6 +1802,7 @@ export class App extends React.Component<any, AppState> {
|
||||
}
|
||||
const hitElement = getElementAtPosition(
|
||||
elements,
|
||||
this.state,
|
||||
x,
|
||||
y,
|
||||
this.state.zoom,
|
||||
@ -1782,8 +1860,6 @@ export class App extends React.Component<any, AppState> {
|
||||
private addElementsFromPaste = (
|
||||
clipboardElements: readonly ExcalidrawElement[],
|
||||
) => {
|
||||
elements = clearSelection(elements);
|
||||
|
||||
const [minX, minY, maxX, maxY] = getCommonBounds(clipboardElements);
|
||||
|
||||
const elementsCenterX = distance(minX, maxX) / 2;
|
||||
@ -1798,17 +1874,20 @@ export class App extends React.Component<any, AppState> {
|
||||
const dx = x - elementsCenterX;
|
||||
const dy = y - elementsCenterY;
|
||||
|
||||
elements = [
|
||||
...elements,
|
||||
...clipboardElements.map(clipboardElements => {
|
||||
const duplicate = duplicateElement(clipboardElements);
|
||||
duplicate.x += dx - minX;
|
||||
duplicate.y += dy - minY;
|
||||
return duplicate;
|
||||
}),
|
||||
];
|
||||
const newElements = clipboardElements.map(clipboardElements => {
|
||||
const duplicate = duplicateElement(clipboardElements);
|
||||
duplicate.x += dx - minX;
|
||||
duplicate.y += dy - minY;
|
||||
return duplicate;
|
||||
});
|
||||
|
||||
elements = [...elements, ...newElements];
|
||||
history.resumeRecording();
|
||||
this.setState({});
|
||||
this.setState({
|
||||
selectedElementIds: Object.fromEntries(
|
||||
newElements.map(element => [element.id, true]),
|
||||
),
|
||||
});
|
||||
};
|
||||
|
||||
private getTextWysiwygSnappedToCenterPosition(x: number, y: number) {
|
||||
@ -1845,6 +1924,7 @@ export class App extends React.Component<any, AppState> {
|
||||
componentDidUpdate() {
|
||||
const { atLeastOneVisibleElement, scrollBars } = renderScene(
|
||||
elements,
|
||||
this.state,
|
||||
this.state.selectionElement,
|
||||
this.rc!,
|
||||
this.canvas!,
|
||||
|
@ -48,7 +48,7 @@ function ExportModal({
|
||||
onExportToBackend: ExportCB;
|
||||
onCloseRequest: () => void;
|
||||
}) {
|
||||
const someElementIsSelected = isSomeElementSelected(elements);
|
||||
const someElementIsSelected = isSomeElementSelected(elements, appState);
|
||||
const [scale, setScale] = useState(defaultScale);
|
||||
const [exportSelected, setExportSelected] = useState(someElementIsSelected);
|
||||
const previewRef = useRef<HTMLDivElement>(null);
|
||||
@ -58,7 +58,7 @@ function ExportModal({
|
||||
const onlySelectedInput = useRef<HTMLInputElement>(null);
|
||||
|
||||
const exportedElements = exportSelected
|
||||
? getSelectedElements(elements)
|
||||
? getSelectedElements(elements, appState)
|
||||
: elements;
|
||||
|
||||
useEffect(() => {
|
||||
@ -67,7 +67,7 @@ function ExportModal({
|
||||
|
||||
useEffect(() => {
|
||||
const previewNode = previewRef.current;
|
||||
const canvas = exportToCanvas(exportedElements, {
|
||||
const canvas = exportToCanvas(exportedElements, appState, {
|
||||
exportBackground,
|
||||
viewBackgroundColor,
|
||||
exportPadding,
|
||||
@ -78,6 +78,7 @@ function ExportModal({
|
||||
previewNode?.removeChild(canvas);
|
||||
};
|
||||
}, [
|
||||
appState,
|
||||
exportedElements,
|
||||
exportBackground,
|
||||
exportPadding,
|
||||
|
@ -4,15 +4,16 @@ import { ExcalidrawElement } from "../element/types";
|
||||
import { getSelectedElements } from "../scene";
|
||||
|
||||
import "./HintViewer.css";
|
||||
import { AppState } from "../types";
|
||||
|
||||
interface Hint {
|
||||
elementType: string;
|
||||
multiMode: boolean;
|
||||
isResizing: boolean;
|
||||
appState: AppState;
|
||||
elements: readonly ExcalidrawElement[];
|
||||
}
|
||||
|
||||
const getHints = ({ elementType, multiMode, isResizing, elements }: Hint) => {
|
||||
const getHints = ({ appState, elements }: Hint) => {
|
||||
const { elementType, isResizing } = appState;
|
||||
const multiMode = appState.multiElement !== null;
|
||||
if (elementType === "arrow" || elementType === "line") {
|
||||
if (!multiMode) {
|
||||
return t("hints.linearElement");
|
||||
@ -21,7 +22,7 @@ const getHints = ({ elementType, multiMode, isResizing, elements }: Hint) => {
|
||||
}
|
||||
|
||||
if (isResizing) {
|
||||
const selectedElements = getSelectedElements(elements);
|
||||
const selectedElements = getSelectedElements(elements, appState);
|
||||
if (
|
||||
selectedElements.length === 1 &&
|
||||
(selectedElements[0].type === "arrow" ||
|
||||
@ -36,16 +37,9 @@ const getHints = ({ elementType, multiMode, isResizing, elements }: Hint) => {
|
||||
return null;
|
||||
};
|
||||
|
||||
export const HintViewer = ({
|
||||
elementType,
|
||||
multiMode,
|
||||
isResizing,
|
||||
elements,
|
||||
}: Hint) => {
|
||||
export const HintViewer = ({ appState, elements }: Hint) => {
|
||||
const hint = getHints({
|
||||
elementType,
|
||||
multiMode,
|
||||
isResizing,
|
||||
appState,
|
||||
elements,
|
||||
});
|
||||
if (!hint) {
|
||||
|
@ -50,7 +50,7 @@ export const LayerUI = React.memo(
|
||||
scale,
|
||||
) => {
|
||||
if (canvas) {
|
||||
exportCanvas(type, exportedElements, canvas, {
|
||||
exportCanvas(type, exportedElements, appState, canvas, {
|
||||
exportBackground: appState.exportBackground,
|
||||
name: appState.name,
|
||||
viewBackgroundColor: appState.viewBackgroundColor,
|
||||
@ -70,10 +70,11 @@ export const LayerUI = React.memo(
|
||||
if (canvas) {
|
||||
exportCanvas(
|
||||
"backend",
|
||||
exportedElements.map(element => ({
|
||||
...element,
|
||||
isSelected: false,
|
||||
})),
|
||||
exportedElements,
|
||||
{
|
||||
...appState,
|
||||
selectedElementIds: {},
|
||||
},
|
||||
canvas,
|
||||
appState,
|
||||
);
|
||||
@ -95,12 +96,7 @@ export const LayerUI = React.memo(
|
||||
) : (
|
||||
<>
|
||||
<FixedSideContainer side="top">
|
||||
<HintViewer
|
||||
elementType={appState.elementType}
|
||||
multiMode={appState.multiElement !== null}
|
||||
isResizing={appState.isResizing}
|
||||
elements={elements}
|
||||
/>
|
||||
<HintViewer appState={appState} elements={elements} />
|
||||
<div className="App-menu App-menu_top">
|
||||
<Stack.Col gap={4} align="end">
|
||||
<Section className="App-right-menu" heading="canvasActions">
|
||||
@ -123,10 +119,7 @@ export const LayerUI = React.memo(
|
||||
>
|
||||
<Island padding={4}>
|
||||
<SelectedShapeActions
|
||||
targetElements={getTargetElement(
|
||||
appState.editingElement,
|
||||
elements,
|
||||
)}
|
||||
targetElements={getTargetElement(elements, appState)}
|
||||
renderAction={actionManager.renderAction}
|
||||
elementType={appState.elementType}
|
||||
/>
|
||||
|
@ -58,10 +58,7 @@ export function MobileMenu({
|
||||
<Section className="App-mobile-menu" heading="selectedShapeActions">
|
||||
<div className="App-mobile-menu-scroller">
|
||||
<SelectedShapeActions
|
||||
targetElements={getTargetElement(
|
||||
appState.editingElement,
|
||||
elements,
|
||||
)}
|
||||
targetElements={getTargetElement(elements, appState)}
|
||||
renderAction={actionManager.renderAction}
|
||||
elementType={appState.elementType}
|
||||
/>
|
||||
@ -88,12 +85,7 @@ export function MobileMenu({
|
||||
</Stack.Col>
|
||||
)}
|
||||
</Section>
|
||||
<HintViewer
|
||||
elementType={appState.elementType}
|
||||
multiMode={appState.multiElement !== null}
|
||||
isResizing={appState.isResizing}
|
||||
elements={elements}
|
||||
/>
|
||||
<HintViewer appState={appState} elements={elements} />
|
||||
</FixedSideContainer>
|
||||
<footer className="App-toolbar">
|
||||
<div className="App-toolbar-content">
|
||||
|
Reference in New Issue
Block a user