parent
ca5f37850e
commit
82ce068972
@ -14,7 +14,10 @@ import { newElementWith } from "../element/mutateElement";
|
||||
export const actionChangeViewBackgroundColor = register({
|
||||
name: "changeViewBackgroundColor",
|
||||
perform: (_, appState, value) => {
|
||||
return { appState: { ...appState, viewBackgroundColor: value } };
|
||||
return {
|
||||
appState: { ...appState, viewBackgroundColor: value },
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ appState, updateData }) => {
|
||||
return (
|
||||
@ -28,18 +31,17 @@ export const actionChangeViewBackgroundColor = register({
|
||||
</div>
|
||||
);
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
});
|
||||
|
||||
export const actionClearCanvas = register({
|
||||
name: "clearCanvas",
|
||||
commitToHistory: () => true,
|
||||
perform: elements => {
|
||||
return {
|
||||
elements: elements.map(element =>
|
||||
newElementWith(element, { isDeleted: true }),
|
||||
),
|
||||
appState: getDefaultAppState(),
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ updateData }) => (
|
||||
@ -81,6 +83,7 @@ export const actionZoomIn = register({
|
||||
...appState,
|
||||
zoom: getNormalizedZoom(appState.zoom + ZOOM_STEP),
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ updateData }) => (
|
||||
@ -107,6 +110,7 @@ export const actionZoomOut = register({
|
||||
...appState,
|
||||
zoom: getNormalizedZoom(appState.zoom - ZOOM_STEP),
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ updateData }) => (
|
||||
@ -133,6 +137,7 @@ export const actionResetZoom = register({
|
||||
...appState,
|
||||
zoom: 1,
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ updateData }) => (
|
||||
|
@ -20,12 +20,11 @@ export const actionDeleteSelected = register({
|
||||
elementType: "selection",
|
||||
multiElement: null,
|
||||
},
|
||||
commitToHistory: isSomeElementSelected(elements, appState),
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.delete",
|
||||
contextMenuOrder: 3,
|
||||
commitToHistory: (appState, elements) =>
|
||||
isSomeElementSelected(elements, appState),
|
||||
keyTest: event => event.key === KEYS.BACKSPACE || event.key === KEYS.DELETE,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<ToolButton
|
||||
|
@ -23,6 +23,7 @@ export const actionDuplicateSelection = register({
|
||||
},
|
||||
[],
|
||||
),
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.duplicateSelection",
|
||||
|
@ -10,7 +10,7 @@ import { register } from "./register";
|
||||
export const actionChangeProjectName = register({
|
||||
name: "changeProjectName",
|
||||
perform: (_elements, appState, value) => {
|
||||
return { appState: { ...appState, name: value } };
|
||||
return { appState: { ...appState, name: value }, commitToHistory: false };
|
||||
},
|
||||
PanelComponent: ({ appState, updateData }) => (
|
||||
<ProjectName
|
||||
@ -24,7 +24,10 @@ export const actionChangeProjectName = register({
|
||||
export const actionChangeExportBackground = register({
|
||||
name: "changeExportBackground",
|
||||
perform: (_elements, appState, value) => {
|
||||
return { appState: { ...appState, exportBackground: value } };
|
||||
return {
|
||||
appState: { ...appState, exportBackground: value },
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ appState, updateData }) => (
|
||||
<label>
|
||||
@ -42,7 +45,7 @@ export const actionSaveScene = register({
|
||||
name: "saveScene",
|
||||
perform: (elements, appState, value) => {
|
||||
saveAsJSON(elements, appState).catch(error => console.error(error));
|
||||
return {};
|
||||
return { commitToHistory: false };
|
||||
},
|
||||
PanelComponent: ({ updateData }) => (
|
||||
<ToolButton
|
||||
@ -63,7 +66,11 @@ export const actionLoadScene = register({
|
||||
appState,
|
||||
{ elements: loadedElements, appState: loadedAppState },
|
||||
) => {
|
||||
return { elements: loadedElements, appState: loadedAppState };
|
||||
return {
|
||||
elements: loadedElements,
|
||||
appState: loadedAppState,
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
PanelComponent: ({ updateData }) => (
|
||||
<ToolButton
|
||||
|
@ -52,6 +52,7 @@ export const actionFinalize = register({
|
||||
editingElement: null,
|
||||
selectedElementIds: {},
|
||||
},
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
keyTest: (event, appState) =>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { Action } from "./types";
|
||||
import { Action, ActionResult } from "./types";
|
||||
import React from "react";
|
||||
import { undo, redo } from "../components/icons";
|
||||
import { ToolButton } from "../components/ToolButton";
|
||||
@ -13,8 +13,12 @@ import { newElementWith } from "../element/mutateElement";
|
||||
const writeData = (
|
||||
prevElements: readonly ExcalidrawElement[],
|
||||
appState: AppState,
|
||||
updater: () => { elements: ExcalidrawElement[]; appState: AppState } | null,
|
||||
) => {
|
||||
updater: () => {
|
||||
elements: ExcalidrawElement[];
|
||||
appState: AppState;
|
||||
} | null,
|
||||
): ActionResult => {
|
||||
const commitToHistory = false;
|
||||
if (
|
||||
!appState.multiElement &&
|
||||
!appState.resizingElement &&
|
||||
@ -23,7 +27,7 @@ const writeData = (
|
||||
) {
|
||||
const data = updater();
|
||||
if (data === null) {
|
||||
return {};
|
||||
return { commitToHistory };
|
||||
}
|
||||
|
||||
const prevElementMap = getElementMap(prevElements);
|
||||
@ -47,9 +51,10 @@ const writeData = (
|
||||
),
|
||||
),
|
||||
appState: { ...appState, ...data.appState },
|
||||
commitToHistory,
|
||||
};
|
||||
}
|
||||
return {};
|
||||
return { commitToHistory };
|
||||
};
|
||||
|
||||
const testUndo = (shift: boolean) => (event: KeyboardEvent) =>
|
||||
|
@ -12,6 +12,7 @@ export const actionToggleCanvasMenu = register({
|
||||
...appState,
|
||||
openMenu: appState.openMenu === "canvas" ? null : "canvas",
|
||||
},
|
||||
commitToHistory: false,
|
||||
}),
|
||||
PanelComponent: ({ appState, updateData }) => (
|
||||
<ToolButton
|
||||
@ -31,6 +32,7 @@ export const actionToggleEditMenu = register({
|
||||
...appState,
|
||||
openMenu: appState.openMenu === "shape" ? null : "shape",
|
||||
},
|
||||
commitToHistory: false,
|
||||
}),
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<ToolButton
|
||||
|
@ -52,9 +52,9 @@ export const actionChangeStrokeColor = register({
|
||||
}),
|
||||
),
|
||||
appState: { ...appState, currentItemStrokeColor: value },
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<>
|
||||
<h3 aria-hidden="true">{t("labels.stroke")}</h3>
|
||||
@ -83,9 +83,9 @@ export const actionChangeBackgroundColor = register({
|
||||
}),
|
||||
),
|
||||
appState: { ...appState, currentItemBackgroundColor: value },
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<>
|
||||
<h3 aria-hidden="true">{t("labels.background")}</h3>
|
||||
@ -114,9 +114,9 @@ export const actionChangeFillStyle = register({
|
||||
}),
|
||||
),
|
||||
appState: { ...appState, currentItemFillStyle: value },
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fill")}</legend>
|
||||
@ -151,9 +151,9 @@ export const actionChangeStrokeWidth = register({
|
||||
}),
|
||||
),
|
||||
appState: { ...appState, currentItemStrokeWidth: value },
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.strokeWidth")}</legend>
|
||||
@ -186,9 +186,9 @@ export const actionChangeSloppiness = register({
|
||||
}),
|
||||
),
|
||||
appState: { ...appState, currentItemRoughness: value },
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.sloppiness")}</legend>
|
||||
@ -221,9 +221,9 @@ export const actionChangeOpacity = register({
|
||||
}),
|
||||
),
|
||||
appState: { ...appState, currentItemOpacity: value },
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<label className="control-label">
|
||||
{t("labels.opacity")}
|
||||
@ -281,9 +281,9 @@ export const actionChangeFontSize = register({
|
||||
appState.currentItemFont.split("px ")[1]
|
||||
}`,
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fontSize")}</legend>
|
||||
@ -328,9 +328,9 @@ export const actionChangeFontFamily = register({
|
||||
appState.currentItemFont.split("px ")[0]
|
||||
}px ${value}`,
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
PanelComponent: ({ elements, appState, updateData }) => (
|
||||
<fieldset>
|
||||
<legend>{t("labels.fontFamily")}</legend>
|
||||
|
@ -12,6 +12,7 @@ export const actionSelectAll = register({
|
||||
return map;
|
||||
}, {} as any),
|
||||
},
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.selectAll",
|
||||
|
@ -17,7 +17,9 @@ export const actionCopyStyles = register({
|
||||
if (element) {
|
||||
copiedStyles = JSON.stringify(element);
|
||||
}
|
||||
return {};
|
||||
return {
|
||||
commitToHistory: false,
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.copyStyles",
|
||||
keyTest: event =>
|
||||
@ -30,7 +32,7 @@ export const actionPasteStyles = register({
|
||||
perform: (elements, appState) => {
|
||||
const pastedElement = JSON.parse(copiedStyles);
|
||||
if (!isExcalidrawElement(pastedElement)) {
|
||||
return { elements };
|
||||
return { elements, commitToHistory: false };
|
||||
}
|
||||
return {
|
||||
elements: elements.map(element => {
|
||||
@ -53,9 +55,9 @@ export const actionPasteStyles = register({
|
||||
}
|
||||
return element;
|
||||
}),
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
contextItemLabel: "labels.pasteStyles",
|
||||
keyTest: event =>
|
||||
event[KEYS.CTRL_OR_CMD] && event.shiftKey && event.key === "V",
|
||||
|
@ -26,11 +26,11 @@ export const actionSendBackward = register({
|
||||
getSelectedIndices(elements, appState),
|
||||
),
|
||||
appState,
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.sendBackward",
|
||||
keyPriority: 40,
|
||||
commitToHistory: () => true,
|
||||
keyTest: event =>
|
||||
event[KEYS.CTRL_OR_CMD] && !event.shiftKey && event.code === "BracketLeft",
|
||||
PanelComponent: ({ updateData }) => (
|
||||
@ -54,11 +54,11 @@ export const actionBringForward = register({
|
||||
getSelectedIndices(elements, appState),
|
||||
),
|
||||
appState,
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.bringForward",
|
||||
keyPriority: 40,
|
||||
commitToHistory: () => true,
|
||||
keyTest: event =>
|
||||
event[KEYS.CTRL_OR_CMD] && !event.shiftKey && event.code === "BracketRight",
|
||||
PanelComponent: ({ updateData }) => (
|
||||
@ -82,10 +82,10 @@ export const actionSendToBack = register({
|
||||
getSelectedIndices(elements, appState),
|
||||
),
|
||||
appState,
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
contextItemLabel: "labels.sendToBack",
|
||||
commitToHistory: () => true,
|
||||
keyTest: event => {
|
||||
return isDarwin
|
||||
? event[KEYS.CTRL_OR_CMD] && event.altKey && event.code === "BracketLeft"
|
||||
@ -118,9 +118,9 @@ export const actionBringToFront = register({
|
||||
getSelectedIndices(elements, appState),
|
||||
),
|
||||
appState,
|
||||
commitToHistory: true,
|
||||
};
|
||||
},
|
||||
commitToHistory: () => true,
|
||||
contextItemLabel: "labels.bringToFront",
|
||||
keyTest: event => {
|
||||
return isDarwin
|
||||
|
@ -50,24 +50,12 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
}
|
||||
|
||||
event.preventDefault();
|
||||
const commitToHistory =
|
||||
data[0].commitToHistory &&
|
||||
data[0].commitToHistory(this.getAppState(), this.getElements());
|
||||
this.updater(
|
||||
data[0].perform(this.getElements(), this.getAppState(), null),
|
||||
commitToHistory,
|
||||
);
|
||||
this.updater(data[0].perform(this.getElements(), this.getAppState(), null));
|
||||
return true;
|
||||
}
|
||||
|
||||
executeAction(action: Action) {
|
||||
const commitToHistory =
|
||||
action.commitToHistory &&
|
||||
action.commitToHistory(this.getAppState(), this.getElements());
|
||||
this.updater(
|
||||
action.perform(this.getElements(), this.getAppState(), null),
|
||||
commitToHistory,
|
||||
);
|
||||
this.updater(action.perform(this.getElements(), this.getAppState(), null));
|
||||
}
|
||||
|
||||
getContextMenuItems(actionFilter: ActionFilterFn = action => action) {
|
||||
@ -82,12 +70,8 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
.map(action => ({
|
||||
label: action.contextItemLabel ? t(action.contextItemLabel) : "",
|
||||
action: () => {
|
||||
const commitToHistory =
|
||||
action.commitToHistory &&
|
||||
action.commitToHistory(this.getAppState(), this.getElements());
|
||||
this.updater(
|
||||
action.perform(this.getElements(), this.getAppState(), null),
|
||||
commitToHistory,
|
||||
);
|
||||
},
|
||||
}));
|
||||
@ -98,12 +82,8 @@ export class ActionManager implements ActionsManagerInterface {
|
||||
const action = this.actions[name];
|
||||
const PanelComponent = action.PanelComponent!;
|
||||
const updateData = (formState?: any) => {
|
||||
const commitToHistory =
|
||||
action.commitToHistory &&
|
||||
action.commitToHistory(this.getAppState(), this.getElements());
|
||||
this.updater(
|
||||
action.perform(this.getElements(), this.getAppState(), formState),
|
||||
commitToHistory,
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -5,6 +5,7 @@ import { AppState } from "../types";
|
||||
export type ActionResult = {
|
||||
elements?: readonly ExcalidrawElement[] | null;
|
||||
appState?: AppState | null;
|
||||
commitToHistory: boolean;
|
||||
};
|
||||
|
||||
type ActionFn = (
|
||||
@ -32,10 +33,6 @@ export interface Action {
|
||||
) => boolean;
|
||||
contextItemLabel?: string;
|
||||
contextMenuOrder?: number;
|
||||
commitToHistory?: (
|
||||
appState: AppState,
|
||||
elements: readonly ExcalidrawElement[],
|
||||
) => boolean;
|
||||
}
|
||||
|
||||
export interface ActionsManagerInterface {
|
||||
|
@ -105,11 +105,14 @@ import { isLinearElement } from "../element/typeChecks";
|
||||
import { rescalePoints } from "../points";
|
||||
import { actionFinalize } from "../actions";
|
||||
|
||||
/**
|
||||
* @param func handler taking at most single parameter (event).
|
||||
*/
|
||||
function withBatchedUpdates<
|
||||
TFunction extends ((event: any) => void) | (() => void)
|
||||
>(func: TFunction) {
|
||||
>(func: Parameters<TFunction>["length"] extends 0 | 1 ? TFunction : never) {
|
||||
return (event => {
|
||||
unstable_batchedUpdates(func, event);
|
||||
unstable_batchedUpdates(func as TFunction, event);
|
||||
}) as TFunction;
|
||||
}
|
||||
|
||||
@ -164,30 +167,28 @@ export class App extends React.Component<any, AppState> {
|
||||
this.actionManager.registerAction(createRedoAction(history));
|
||||
}
|
||||
|
||||
private syncActionResult = withBatchedUpdates(
|
||||
(res: ActionResult, commitToHistory: boolean = true) => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
if (res.elements) {
|
||||
globalSceneState.replaceAllElements(res.elements);
|
||||
if (commitToHistory) {
|
||||
history.resumeRecording();
|
||||
}
|
||||
private syncActionResult = withBatchedUpdates((res: ActionResult) => {
|
||||
if (this.unmounted) {
|
||||
return;
|
||||
}
|
||||
if (res.elements) {
|
||||
globalSceneState.replaceAllElements(res.elements);
|
||||
if (res.commitToHistory) {
|
||||
history.resumeRecording();
|
||||
}
|
||||
}
|
||||
|
||||
if (res.appState) {
|
||||
if (commitToHistory) {
|
||||
history.resumeRecording();
|
||||
}
|
||||
this.setState(state => ({
|
||||
...res.appState,
|
||||
isCollaborating: state.isCollaborating,
|
||||
collaborators: state.collaborators,
|
||||
}));
|
||||
if (res.appState) {
|
||||
if (res.commitToHistory) {
|
||||
history.resumeRecording();
|
||||
}
|
||||
},
|
||||
);
|
||||
this.setState(state => ({
|
||||
...res.appState,
|
||||
isCollaborating: state.isCollaborating,
|
||||
collaborators: state.collaborators,
|
||||
}));
|
||||
}
|
||||
});
|
||||
|
||||
private onCut = withBatchedUpdates((event: ClipboardEvent) => {
|
||||
if (isWritableElement(event.target)) {
|
||||
@ -917,7 +918,11 @@ export class App extends React.Component<any, AppState> {
|
||||
) {
|
||||
loadFromBlob(file)
|
||||
.then(({ elements, appState }) =>
|
||||
this.syncActionResult({ elements, appState }),
|
||||
this.syncActionResult({
|
||||
elements,
|
||||
appState,
|
||||
commitToHistory: false,
|
||||
}),
|
||||
)
|
||||
.catch(error => console.error(error));
|
||||
}
|
||||
|
@ -357,5 +357,6 @@ export async function loadScene(id: string | null, privateKey?: string) {
|
||||
return {
|
||||
elements: data.elements,
|
||||
appState: data.appState && { ...data.appState },
|
||||
commitToHistory: false,
|
||||
};
|
||||
}
|
||||
|
@ -83,7 +83,7 @@ export class SceneHistory {
|
||||
}
|
||||
|
||||
undoOnce(): Result | null {
|
||||
if (this.stateHistory.length === 0) {
|
||||
if (this.stateHistory.length === 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user