excalidraw/src/appState.ts

216 lines
8.7 KiB
TypeScript
Raw Normal View History

2020-04-10 18:09:29 -04:00
import oc from "open-color";
import {
DEFAULT_FONT_FAMILY,
DEFAULT_FONT_SIZE,
DEFAULT_TEXT_ALIGN,
EXPORT_SCALES,
THEME,
} from "./constants";
import { t } from "./i18n";
import { AppState, NormalizedZoomValue } from "./types";
import { getDateTime } from "./utils";
const defaultExportScale = EXPORT_SCALES.includes(devicePixelRatio)
? devicePixelRatio
: 1;
export const getDefaultAppState = (): Omit<
AppState,
"offsetTop" | "offsetLeft" | "width" | "height"
> => {
return {
theme: THEME.LIGHT,
collaborators: new Map(),
currentChartType: "bar",
currentItemBackgroundColor: "transparent",
currentItemEndArrowhead: "arrow",
currentItemFillStyle: "hachure",
currentItemFontFamily: DEFAULT_FONT_FAMILY,
currentItemFontSize: DEFAULT_FONT_SIZE,
currentItemLinearStrokeSharpness: "round",
currentItemOpacity: 100,
currentItemRoughness: 1,
2020-12-12 16:42:30 +00:00
currentItemStartArrowhead: null,
currentItemStrokeColor: oc.black,
currentItemStrokeSharpness: "sharp",
currentItemStrokeStyle: "solid",
currentItemStrokeWidth: 1,
currentItemTextAlign: DEFAULT_TEXT_ALIGN,
cursorButton: "up",
draggingElement: null,
editingElement: null,
editingGroupId: null,
editingLinearElement: null,
elementLocked: false,
elementType: "selection",
penMode: false,
penDetected: false,
errorMessage: null,
exportBackground: true,
exportScale: defaultExportScale,
exportEmbedScene: false,
exportWithDarkMode: false,
fileHandle: null,
gridSize: null,
Allow binding linear elements to other elements (#1899) * Refactor: simplify linear element type * Refactor: dedupe scrollbar handling * First step towards binding - establish relationship and basic test for dragged lines * Refactor: use zoom from appstate * Refactor: generalize getElementAtPosition * Only consider bindable elements in hit test * Refactor: pull out pieces of hit test for reuse later * Refactor: pull out diamond from hit test for reuse later * Refactor: pull out text from hit test for reuse later * Suggest binding when hovering * Give shapes in regression test real size * Give shapes in undo/redo test real size * Keep bound element highlighted * Show binding suggestion for multi-point elements * Move binding to its on module with functions so that I can use it from actions, add support for binding end of multi-point elements * Use Id instead of ID * Improve boundary offset for non-squarish elements * Fix localStorage for binding on linear elements * Simplify dragging code and fix elements bound twice to the same shape * Fix binding for rectangles * Bind both ends at the end of the linear element creation, needed for focus points * wip * Refactor: Renames and reshapes for next commit * Calculate and store focus points and gaps, but dont use them yet * Focus points for rectangles * Dont blow up when canceling linear element * Stop suggesting binding when a non-compatible tool is selected * Clean up collision code * Using Geometric Algebra for hit tests * Correct binding for all shapes * Constant gap around polygon corners * Fix rotation handling * Generalize update and fix hit test for rotated elements * Handle rotation realtime * Handle scaling * Remove vibration when moving bound and binding element together * Handle simultenous scaling * Allow binding and unbinding when editing linear elements * Dont delete binding when the end point wasnt touched * Bind on enter/escape when editing * Support multiple suggested bindable elements in preparation for supporting linear elements dragging * Update binding when moving linear elements * Update binding when resizing linear elements * Dont re-render UI on binding hints * Update both ends when one is moved * Use distance instead of focus point for binding * Complicated approach for posterity, ignore this commit * Revert the complicated approach * Better focus point strategy, working for all shapes * Update snapshots * Dont break binding gap when mirroring shape * Dont break binding gap when grid mode pushes it inside * Dont bind draw elements * Support alt duplication * Fix alt duplication to * Support cmd+D duplication * All copy mechanisms are supported * Allow binding shapes to arrows, having arrows created first * Prevent arrows from disappearing for ellipses * Better binding suggestion highlight for shapes * Dont suggest second binding for simple elements when editing or moving them * Dont steal already bound linear elements when moving shapes * Fix highlighting diamonds and more precisely highlight other shapes * Highlight linear element edges for binding * Highlight text binding too * Handle deletion * Dont suggest second binding for simple linear elements when creating them * Dont highlight bound element during creation * Fix binding for rotated linear elements * Fix collision check for ellipses * Dont show suggested bindings for selected pairs * Bind multi-point linear elements when the tool is switched - important for mobile * Handle unbinding one of two bound edges correctly * Rename boundElement in state to startBoundElement * Dont double account for zoom when rendering binding highlight * Fix rendering of edited linear element point handles * Suggest binding when adding new point to a linear element * Bind when adding a new point to a linear element and dont unbind when moving middle elements * Handle deleting points * Add cmd modifier key to disable binding * Use state for enabling binding, fix not binding for linear elements during creation * Drop support for binding lines, only arrows are bindable * Reset binding mode on blur * Fix not binding lines
2020-08-08 21:04:15 -07:00
isBindingEnabled: true,
isLibraryOpen: false,
isLoading: false,
isResizing: false,
Rotation support (#1099) * rotate rectanble with fixed angle * rotate dashed rectangle with fixed angle * fix rotate handler rect * fix canvas size with rotation * angle in element base * fix bug in calculating canvas size * trial only for rectangle * hitTest for rectangle rotation * properly resize rotated rectangle * fix canvas size calculation * giving up... workaround for now * **experimental** handler to rotate rectangle * remove rotation on copy for debugging * update snapshots * better rotation handler with atan2 * rotate when drawImage * add rotation handler * hitTest for any shapes * fix hitTest for curved lines * rotate text element * rotation locking * hint messaage for rotating * show proper handlers on mobile (a workaround, there should be a better way) * refactor hitTest * support exporting png * support exporting svg * fix rotating curved line * refactor drawElementFromCanvas with getElementAbsoluteCoords * fix export png and svg * adjust resize positions for lines (N, E, S, W) * do not make handlers big on mobile * Update src/locales/en.json Alright! Co-Authored-By: Lipis <lipiridis@gmail.com> * do not show rotation/resizing hints on mobile * proper calculation for N and W positions * simplify calculation * use "rotation" as property name for clarification (may increase bundle size) * update snapshots excluding rotation handle * refactor with adjustPositionWithRotation * refactor with adjustXYWithRotation * forgot to rename rotation * rename internal function * initialize element angle on restore * rotate wysiwyg editor * fix shift-rotate around 270deg * improve rotation locking * refactor adjustXYWithRotation * avoid rotation degree becomes >=360 * refactor with generateHandler Co-authored-by: Lipis <lipiridis@gmail.com> Co-authored-by: dwelle <luzar.david@gmail.com>
2020-04-02 17:40:26 +09:00
isRotating: false,
lastPointerDownWith: "mouse",
multiElement: null,
name: `${t("labels.untitled")}-${getDateTime()}`,
openMenu: null,
openPopup: null,
pasteDialog: { shown: false, data: null },
previousSelectedElementIds: {},
resizingElement: null,
scrolledOutside: false,
scrollX: 0,
scrollY: 0,
selectedElementIds: {},
selectedGroupIds: {},
selectionElement: null,
shouldCacheIgnoreZoom: false,
showHelpDialog: false,
showStats: false,
startBoundElement: null,
Allow binding linear elements to other elements (#1899) * Refactor: simplify linear element type * Refactor: dedupe scrollbar handling * First step towards binding - establish relationship and basic test for dragged lines * Refactor: use zoom from appstate * Refactor: generalize getElementAtPosition * Only consider bindable elements in hit test * Refactor: pull out pieces of hit test for reuse later * Refactor: pull out diamond from hit test for reuse later * Refactor: pull out text from hit test for reuse later * Suggest binding when hovering * Give shapes in regression test real size * Give shapes in undo/redo test real size * Keep bound element highlighted * Show binding suggestion for multi-point elements * Move binding to its on module with functions so that I can use it from actions, add support for binding end of multi-point elements * Use Id instead of ID * Improve boundary offset for non-squarish elements * Fix localStorage for binding on linear elements * Simplify dragging code and fix elements bound twice to the same shape * Fix binding for rectangles * Bind both ends at the end of the linear element creation, needed for focus points * wip * Refactor: Renames and reshapes for next commit * Calculate and store focus points and gaps, but dont use them yet * Focus points for rectangles * Dont blow up when canceling linear element * Stop suggesting binding when a non-compatible tool is selected * Clean up collision code * Using Geometric Algebra for hit tests * Correct binding for all shapes * Constant gap around polygon corners * Fix rotation handling * Generalize update and fix hit test for rotated elements * Handle rotation realtime * Handle scaling * Remove vibration when moving bound and binding element together * Handle simultenous scaling * Allow binding and unbinding when editing linear elements * Dont delete binding when the end point wasnt touched * Bind on enter/escape when editing * Support multiple suggested bindable elements in preparation for supporting linear elements dragging * Update binding when moving linear elements * Update binding when resizing linear elements * Dont re-render UI on binding hints * Update both ends when one is moved * Use distance instead of focus point for binding * Complicated approach for posterity, ignore this commit * Revert the complicated approach * Better focus point strategy, working for all shapes * Update snapshots * Dont break binding gap when mirroring shape * Dont break binding gap when grid mode pushes it inside * Dont bind draw elements * Support alt duplication * Fix alt duplication to * Support cmd+D duplication * All copy mechanisms are supported * Allow binding shapes to arrows, having arrows created first * Prevent arrows from disappearing for ellipses * Better binding suggestion highlight for shapes * Dont suggest second binding for simple elements when editing or moving them * Dont steal already bound linear elements when moving shapes * Fix highlighting diamonds and more precisely highlight other shapes * Highlight linear element edges for binding * Highlight text binding too * Handle deletion * Dont suggest second binding for simple linear elements when creating them * Dont highlight bound element during creation * Fix binding for rotated linear elements * Fix collision check for ellipses * Dont show suggested bindings for selected pairs * Bind multi-point linear elements when the tool is switched - important for mobile * Handle unbinding one of two bound edges correctly * Rename boundElement in state to startBoundElement * Dont double account for zoom when rendering binding highlight * Fix rendering of edited linear element point handles * Suggest binding when adding new point to a linear element * Bind when adding a new point to a linear element and dont unbind when moving middle elements * Handle deleting points * Add cmd modifier key to disable binding * Use state for enabling binding, fix not binding for linear elements during creation * Drop support for binding lines, only arrows are bindable * Reset binding mode on blur * Fix not binding lines
2020-08-08 21:04:15 -07:00
suggestedBindings: [],
toastMessage: null,
viewBackgroundColor: oc.white,
zenModeEnabled: false,
zoom: {
value: 1 as NormalizedZoomValue,
},
viewModeEnabled: false,
pendingImageElement: null,
feat: Support hyperlinks 🔥 (#4620) * feat: Support hypelinks * dont show edit when link not present * auto submit on blur * Add link button in sidebar and do it react way * add key to hyperlink to remount when element selection changes * autofocus input * remove click handler and use pointerup/down to show /hide popup * add keydown and support enter/escape to submit * show extrrnal link icon when element has link * use icons and open link in new tab * dnt submit unless link updated * renamed ffiles * remove unnecessary changes * update snap * hide link popup once user starts interacting with element and show again only if clicked outside and clicked on element again * render link icon outside the element * fix hit testing * rewrite implementation to render hyperlinks outside elements and hide when element selected * remove * remove * tweak icon position and size * rotate link icon when element rotated, handle zooming and render exactly where ne resize handle is rendered * no need to create a new reference anymore for element when link added/updated * rotate the link image as well when rotating element * calculate hitbox of link icon and show pointer when hovering over link icon * open link when clicked on link icon * show tooltip when hovering over link icon * show link action only when single element selected * support other protocols * add shortcut cmd/ctrl+k to edit/update link * don't hide popup after submit * renderes decreased woo * Add context mneu label to add/edit link * fix tests * remove tick and show trash when in edit mode * show edit view when element contains link * fix snap * horizontally center the hyperlink container with respect to elemnt * fix padding * remove checkcircle * show popup on hover of selected element and dismiss when outside hitbox * check if element has link before setting popup state * move logic of auto hide to hyperlink and dnt hide when editing * hide popover when drag/resize/rotate * unmount during autohide * autohide after 500ms * fix regression * prevent cmd/ctrl+k when inside link editor * submit when input not updated * allow custom urls * fix centering of popup when zoomed * fix hitbox during zoom * fix * tweak link normalization * touch hyperlink tooltip DOM only if needed * consider 0 if no offsetY * reduce hitbox of link icon and make sure link icon doesn't show on top of higher z-index elements * show link tooltip only if element has higher z-index * dnt show hyperlink popup when selection changes from element with link to element with no link and also hide popover when element type changes from selection to something else * lint: EOL * fix link icon tooltip positioning * open the link only when last pointer down and last pointer up hit the link hitbox * render tooltip after 300ms delay * ensure link popup and editor input have same height * wip: cache the link icon canvas * fix the image quality after caching using device pixel ratio yay * some cleanup * remove unused selectedElementIds from renderConfig * Update src/renderer/renderElement.ts * fix `opener` vulnerability * tweak styling * decrease padding * open local links in the same tab * fix caching * code style refactor * remove unnecessary save & restore * show link shortcut in help dialog * submit on cmd/ctrl+k * merge state props * Add title for link * update editview if prop changes * tweak link action logic * make `Hyperlink` compo editor state fully controlled * dont show popup when context menu open * show in contextMenu only for single selection & change pos * set button `selected` state * set contextMenuOpen on pointerdown * set contextMenyOpen to false when action triggered * don't render link icons on export * fix tests * fix buttons wrap * move focus states to input top-level rule * fix elements sharing `Hyperlink` state * fix hitbox for link icon in case of rect * Early return if hitting link icon Co-authored-by: dwelle <luzar.david@gmail.com>
2022-02-03 20:34:59 +05:30
showHyperlinkPopup: false,
};
};
/**
* Config containing all AppState keys. Used to determine whether given state
* prop should be stripped when exporting to given storage type.
*/
const APP_STATE_STORAGE_CONF = (<
Values extends {
/** whether to keep when storing to browser storage (localStorage/IDB) */
browser: boolean;
/** whether to keep when exporting to file/database */
export: boolean;
/** server (shareLink/collab/...) */
server: boolean;
},
T extends Record<keyof AppState, Values>,
>(config: { [K in keyof T]: K extends keyof AppState ? T[K] : never }) =>
config)({
theme: { browser: true, export: false, server: false },
collaborators: { browser: false, export: false, server: false },
currentChartType: { browser: true, export: false, server: false },
currentItemBackgroundColor: { browser: true, export: false, server: false },
currentItemEndArrowhead: { browser: true, export: false, server: false },
currentItemFillStyle: { browser: true, export: false, server: false },
currentItemFontFamily: { browser: true, export: false, server: false },
currentItemFontSize: { browser: true, export: false, server: false },
currentItemLinearStrokeSharpness: {
browser: true,
export: false,
server: false,
},
currentItemOpacity: { browser: true, export: false, server: false },
currentItemRoughness: { browser: true, export: false, server: false },
currentItemStartArrowhead: { browser: true, export: false, server: false },
currentItemStrokeColor: { browser: true, export: false, server: false },
currentItemStrokeSharpness: { browser: true, export: false, server: false },
currentItemStrokeStyle: { browser: true, export: false, server: false },
currentItemStrokeWidth: { browser: true, export: false, server: false },
currentItemTextAlign: { browser: true, export: false, server: false },
cursorButton: { browser: true, export: false, server: false },
draggingElement: { browser: false, export: false, server: false },
editingElement: { browser: false, export: false, server: false },
editingGroupId: { browser: true, export: false, server: false },
editingLinearElement: { browser: false, export: false, server: false },
elementLocked: { browser: true, export: false, server: false },
elementType: { browser: true, export: false, server: false },
penMode: { browser: false, export: false, server: false },
penDetected: { browser: false, export: false, server: false },
errorMessage: { browser: false, export: false, server: false },
exportBackground: { browser: true, export: false, server: false },
exportEmbedScene: { browser: true, export: false, server: false },
exportScale: { browser: true, export: false, server: false },
exportWithDarkMode: { browser: true, export: false, server: false },
fileHandle: { browser: false, export: false, server: false },
gridSize: { browser: true, export: true, server: true },
height: { browser: false, export: false, server: false },
isBindingEnabled: { browser: false, export: false, server: false },
isLibraryOpen: { browser: false, export: false, server: false },
isLoading: { browser: false, export: false, server: false },
isResizing: { browser: false, export: false, server: false },
isRotating: { browser: false, export: false, server: false },
lastPointerDownWith: { browser: true, export: false, server: false },
multiElement: { browser: false, export: false, server: false },
name: { browser: true, export: false, server: false },
offsetLeft: { browser: false, export: false, server: false },
offsetTop: { browser: false, export: false, server: false },
openMenu: { browser: true, export: false, server: false },
openPopup: { browser: false, export: false, server: false },
pasteDialog: { browser: false, export: false, server: false },
previousSelectedElementIds: { browser: true, export: false, server: false },
resizingElement: { browser: false, export: false, server: false },
scrolledOutside: { browser: true, export: false, server: false },
scrollX: { browser: true, export: false, server: false },
scrollY: { browser: true, export: false, server: false },
selectedElementIds: { browser: true, export: false, server: false },
selectedGroupIds: { browser: true, export: false, server: false },
selectionElement: { browser: false, export: false, server: false },
shouldCacheIgnoreZoom: { browser: true, export: false, server: false },
showHelpDialog: { browser: false, export: false, server: false },
showStats: { browser: true, export: false, server: false },
startBoundElement: { browser: false, export: false, server: false },
suggestedBindings: { browser: false, export: false, server: false },
toastMessage: { browser: false, export: false, server: false },
viewBackgroundColor: { browser: true, export: true, server: true },
width: { browser: false, export: false, server: false },
zenModeEnabled: { browser: true, export: false, server: false },
zoom: { browser: true, export: false, server: false },
viewModeEnabled: { browser: false, export: false, server: false },
pendingImageElement: { browser: false, export: false, server: false },
feat: Support hyperlinks 🔥 (#4620) * feat: Support hypelinks * dont show edit when link not present * auto submit on blur * Add link button in sidebar and do it react way * add key to hyperlink to remount when element selection changes * autofocus input * remove click handler and use pointerup/down to show /hide popup * add keydown and support enter/escape to submit * show extrrnal link icon when element has link * use icons and open link in new tab * dnt submit unless link updated * renamed ffiles * remove unnecessary changes * update snap * hide link popup once user starts interacting with element and show again only if clicked outside and clicked on element again * render link icon outside the element * fix hit testing * rewrite implementation to render hyperlinks outside elements and hide when element selected * remove * remove * tweak icon position and size * rotate link icon when element rotated, handle zooming and render exactly where ne resize handle is rendered * no need to create a new reference anymore for element when link added/updated * rotate the link image as well when rotating element * calculate hitbox of link icon and show pointer when hovering over link icon * open link when clicked on link icon * show tooltip when hovering over link icon * show link action only when single element selected * support other protocols * add shortcut cmd/ctrl+k to edit/update link * don't hide popup after submit * renderes decreased woo * Add context mneu label to add/edit link * fix tests * remove tick and show trash when in edit mode * show edit view when element contains link * fix snap * horizontally center the hyperlink container with respect to elemnt * fix padding * remove checkcircle * show popup on hover of selected element and dismiss when outside hitbox * check if element has link before setting popup state * move logic of auto hide to hyperlink and dnt hide when editing * hide popover when drag/resize/rotate * unmount during autohide * autohide after 500ms * fix regression * prevent cmd/ctrl+k when inside link editor * submit when input not updated * allow custom urls * fix centering of popup when zoomed * fix hitbox during zoom * fix * tweak link normalization * touch hyperlink tooltip DOM only if needed * consider 0 if no offsetY * reduce hitbox of link icon and make sure link icon doesn't show on top of higher z-index elements * show link tooltip only if element has higher z-index * dnt show hyperlink popup when selection changes from element with link to element with no link and also hide popover when element type changes from selection to something else * lint: EOL * fix link icon tooltip positioning * open the link only when last pointer down and last pointer up hit the link hitbox * render tooltip after 300ms delay * ensure link popup and editor input have same height * wip: cache the link icon canvas * fix the image quality after caching using device pixel ratio yay * some cleanup * remove unused selectedElementIds from renderConfig * Update src/renderer/renderElement.ts * fix `opener` vulnerability * tweak styling * decrease padding * open local links in the same tab * fix caching * code style refactor * remove unnecessary save & restore * show link shortcut in help dialog * submit on cmd/ctrl+k * merge state props * Add title for link * update editview if prop changes * tweak link action logic * make `Hyperlink` compo editor state fully controlled * dont show popup when context menu open * show in contextMenu only for single selection & change pos * set button `selected` state * set contextMenuOpen on pointerdown * set contextMenyOpen to false when action triggered * don't render link icons on export * fix tests * fix buttons wrap * move focus states to input top-level rule * fix elements sharing `Hyperlink` state * fix hitbox for link icon in case of rect * Early return if hitting link icon Co-authored-by: dwelle <luzar.david@gmail.com>
2022-02-03 20:34:59 +05:30
showHyperlinkPopup: { browser: false, export: false, server: false },
});
const _clearAppStateForStorage = <
ExportType extends "export" | "browser" | "server",
>(
appState: Partial<AppState>,
exportType: ExportType,
) => {
type ExportableKeys = {
[K in keyof typeof APP_STATE_STORAGE_CONF]: typeof APP_STATE_STORAGE_CONF[K][ExportType] extends true
? K
: never;
}[keyof typeof APP_STATE_STORAGE_CONF];
const stateForExport = {} as { [K in ExportableKeys]?: typeof appState[K] };
for (const key of Object.keys(appState) as (keyof typeof appState)[]) {
const propConfig = APP_STATE_STORAGE_CONF[key];
if (propConfig?.[exportType]) {
const nextValue = appState[key];
// https://github.com/microsoft/TypeScript/issues/31445
(stateForExport as any)[key] = nextValue;
}
}
return stateForExport;
};
export const clearAppStateForLocalStorage = (appState: Partial<AppState>) => {
return _clearAppStateForStorage(appState, "browser");
};
export const cleanAppStateForExport = (appState: Partial<AppState>) => {
return _clearAppStateForStorage(appState, "export");
};
export const clearAppStateForDatabase = (appState: Partial<AppState>) => {
return _clearAppStateForStorage(appState, "server");
};