From dd993adc5c2f8fa764ed40fdb5be02b33f37ba5f Mon Sep 17 00:00:00 2001 From: Lipis Date: Mon, 7 Dec 2020 18:35:16 +0200 Subject: [PATCH] Add stats for nerds (#2453) Co-authored-by: David Luzar Co-authored-by: dwelle --- src/actions/actionCanvas.tsx | 1 + src/actions/actionProperties.tsx | 4 +- src/appState.ts | 2 + src/components/Actions.tsx | 4 +- src/components/App.tsx | 21 +++ src/components/Stats.scss | 37 ++++ src/components/Stats.tsx | 159 ++++++++++++++++++ src/excalidraw-app/data/localStorage.ts | 10 +- src/locales/en.json | 13 ++ src/scene/index.ts | 2 +- src/scene/selection.ts | 16 +- .../regressionTests.test.tsx.snap | 67 ++++++++ src/tests/regressionTests.test.tsx | 34 ++-- src/types.ts | 1 + src/utils.ts | 23 +++ 15 files changed, 361 insertions(+), 33 deletions(-) create mode 100644 src/components/Stats.scss create mode 100644 src/components/Stats.tsx diff --git a/src/actions/actionCanvas.tsx b/src/actions/actionCanvas.tsx index 2a90dd78..12b0c765 100644 --- a/src/actions/actionCanvas.tsx +++ b/src/actions/actionCanvas.tsx @@ -64,6 +64,7 @@ export const actionClearCanvas = register({ exportEmbedScene: appState.exportEmbedScene, gridSize: appState.gridSize, shouldAddWatermark: appState.shouldAddWatermark, + showStats: appState.showStats, }, commitToHistory: true, }; diff --git a/src/actions/actionProperties.tsx b/src/actions/actionProperties.tsx index de74d2cc..de2b48a5 100644 --- a/src/actions/actionProperties.tsx +++ b/src/actions/actionProperties.tsx @@ -8,7 +8,7 @@ import { import { getCommonAttributeOfSelectedElements, isSomeElementSelected, - getTargetElement, + getTargetElements, canChangeSharpness, } from "../scene"; import { ButtonSelect } from "../components/ButtonSelect"; @@ -561,7 +561,7 @@ export const actionChangeTextAlign = register({ export const actionChangeSharpness = register({ name: "changeSharpness", perform: (elements, appState, value) => { - const targetElements = getTargetElement( + const targetElements = getTargetElements( getNonDeletedElements(elements), appState, ); diff --git a/src/appState.ts b/src/appState.ts index f99da991..4a3853f8 100644 --- a/src/appState.ts +++ b/src/appState.ts @@ -71,6 +71,7 @@ export const getDefaultAppState = (): Omit< isLibraryOpen: false, fileHandle: null, collaborators: new Map(), + showStats: false, }; }; @@ -146,6 +147,7 @@ const APP_STATE_STORAGE_CONF = (< offsetLeft: { browser: false, export: false }, fileHandle: { browser: false, export: false }, collaborators: { browser: false, export: false }, + showStats: { browser: true, export: false }, }); const _clearAppStateForStorage = ( diff --git a/src/components/Actions.tsx b/src/components/Actions.tsx index 1377cf67..f7f3b727 100644 --- a/src/components/Actions.tsx +++ b/src/components/Actions.tsx @@ -7,7 +7,7 @@ import { hasStroke, canChangeSharpness, hasText, - getTargetElement, + getTargetElements, } from "../scene"; import { t } from "../i18n"; import { SHAPES } from "../shapes"; @@ -29,7 +29,7 @@ export const SelectedShapeActions = ({ renderAction: ActionManager["renderAction"]; elementType: ExcalidrawElement["type"]; }) => { - const targetElements = getTargetElement( + const targetElements = getTargetElements( getNonDeletedElements(elements), appState, ); diff --git a/src/components/App.tsx b/src/components/App.tsx index 613dd51a..18b873e9 100644 --- a/src/components/App.tsx +++ b/src/components/App.tsx @@ -175,6 +175,7 @@ import { EVENT_SHAPE, trackEvent, } from "../analytics"; +import { Stats } from "./Stats"; const { history } = createHistory(); @@ -377,6 +378,13 @@ class App extends React.Component { lng={getLanguage().lng} isCollaborating={this.props.isCollaborating || false} /> + {this.state.showStats && ( + + )}
{ }); }; + toggleStats = () => { + if (!this.state.showStats) { + trackEvent(EVENT_DIALOG, "stats"); + } + this.setState({ + showStats: !this.state.showStats, + }); + }; + setScrollToCenter = (remoteElements: readonly ExcalidrawElement[]) => { this.setState({ ...calculateScrollCenter( @@ -3564,6 +3581,10 @@ class App extends React.Component { label: t("labels.toggleGridMode"), action: this.toggleGridMode, }, + { + label: t("labels.toggleStats"), + action: this.toggleStats, + }, ], top: clientY, left: clientX, diff --git a/src/components/Stats.scss b/src/components/Stats.scss new file mode 100644 index 00000000..17e678f0 --- /dev/null +++ b/src/components/Stats.scss @@ -0,0 +1,37 @@ +@import "../css/_variables"; + +.Stats { + position: fixed; + top: 64px; + right: 12px; + font-size: 12px; + z-index: 999; + h3 { + margin: 0 24px 8px 0; + } + + .close { + float: right; + height: 16px; + width: 16px; + cursor: pointer; + svg { + width: 100%; + height: 100%; + } + } + + table { + width: 100%; + th { + border-bottom: 1px solid var(--input-border-color); + padding: 4px; + } + tr { + td:nth-child(2) { + min-width: 48px; + text-align: right; + } + } + } +} diff --git a/src/components/Stats.tsx b/src/components/Stats.tsx new file mode 100644 index 00000000..55d3a842 --- /dev/null +++ b/src/components/Stats.tsx @@ -0,0 +1,159 @@ +import React, { useEffect, useState } from "react"; +import { getCommonBounds } from "../element/bounds"; +import { NonDeletedExcalidrawElement } from "../element/types"; +import { + getElementsStorageSize, + getTotalStorageSize, +} from "../excalidraw-app/data/localStorage"; +import { t } from "../i18n"; +import { getTargetElements } from "../scene"; +import { AppState } from "../types"; +import { debounce, nFormatter } from "../utils"; +import { close } from "./icons"; +import { Island } from "./Island"; +import "./Stats.scss"; + +type StorageSizes = { scene: number; total: number }; + +const getStorageSizes = debounce((cb: (sizes: StorageSizes) => void) => { + cb({ + scene: getElementsStorageSize(), + total: getTotalStorageSize(), + }); +}, 500); + +export const Stats = (props: { + appState: AppState; + elements: readonly NonDeletedExcalidrawElement[]; + onClose: () => void; +}) => { + const [storageSizes, setStorageSizes] = useState({ + scene: 0, + total: 0, + }); + + useEffect(() => { + getStorageSizes((sizes) => { + setStorageSizes(sizes); + }); + }); + + useEffect(() => () => getStorageSizes.cancel(), []); + + const boundingBox = getCommonBounds(props.elements); + const selectedElements = getTargetElements(props.elements, props.appState); + const selectedBoundingBox = getCommonBounds(selectedElements); + + return ( +
+ +
+ {close} +
+

{t("stats.title")}

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {selectedElements.length === 1 && ( + + + + )} + + {selectedElements.length > 1 && ( + <> + + + + + + + + + )} + {selectedElements.length > 0 && ( + <> + + + + + + + + + + + + + + + + + + )} + {selectedElements.length === 1 && ( + + + + + )} + +
{t("stats.scene")}
{t("stats.elements")}{props.elements.length}
{t("stats.width")}{Math.round(boundingBox[2]) - Math.round(boundingBox[0])}
{t("stats.height")}{Math.round(boundingBox[3]) - Math.round(boundingBox[1])}
{t("stats.storage")}
{t("stats.scene")}{nFormatter(storageSizes.scene, 1)}
{t("stats.total")}{nFormatter(storageSizes.total, 1)}
{t("stats.element")}
{t("stats.selected")}
{t("stats.elements")}{selectedElements.length}
{"x"} + {Math.round( + selectedElements.length === 1 + ? selectedElements[0].x + : selectedBoundingBox[0], + )} +
{"y"} + {Math.round( + selectedElements.length === 1 + ? selectedElements[0].y + : selectedBoundingBox[1], + )} +
{t("stats.width")} + {Math.round( + selectedElements.length === 1 + ? selectedElements[0].width + : selectedBoundingBox[2] - selectedBoundingBox[0], + )} +
{t("stats.height")} + {Math.round( + selectedElements.length === 1 + ? selectedElements[0].height + : selectedBoundingBox[3] - selectedBoundingBox[1], + )} +
{t("stats.angle")} + {`${Math.round( + (selectedElements[0].angle * 180) / Math.PI, + )}°`} +
+
+
+ ); +}; diff --git a/src/excalidraw-app/data/localStorage.ts b/src/excalidraw-app/data/localStorage.ts index 21cfe097..c9f4dc0d 100644 --- a/src/excalidraw-app/data/localStorage.ts +++ b/src/excalidraw-app/data/localStorage.ts @@ -98,16 +98,20 @@ export const importFromLocalStorage = () => { return { elements, appState }; }; +export const getElementsStorageSize = () => { + const elements = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS); + const elementsSize = elements ? JSON.stringify(elements).length : 0; + return elementsSize; +}; + export const getTotalStorageSize = () => { const appState = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_APP_STATE); const collab = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_COLLAB); - const elements = localStorage.getItem(STORAGE_KEYS.LOCAL_STORAGE_ELEMENTS); const library = localStorage.getItem(APP_STORAGE_KEYS.LOCAL_STORAGE_LIBRARY); const appStateSize = appState ? JSON.stringify(appState).length : 0; const collabSize = collab ? JSON.stringify(collab).length : 0; - const elementsSize = elements ? JSON.stringify(elements).length : 0; const librarySize = library ? JSON.stringify(library).length : 0; - return appStateSize + collabSize + elementsSize + librarySize; + return appStateSize + collabSize + librarySize + getElementsStorageSize(); }; diff --git a/src/locales/en.json b/src/locales/en.json index 446228a7..4c8271f1 100644 --- a/src/locales/en.json +++ b/src/locales/en.json @@ -71,6 +71,7 @@ "ungroup": "Ungroup selection", "collaborators": "Collaborators", "toggleGridMode": "Toggle grid mode", + "toggleStats": "Toggle stats for nerds", "addToLibrary": "Add to library", "removeFromLibrary": "Remove from library", "libraryLoadingMessage": "Loading library...", @@ -213,5 +214,17 @@ "charts": { "noNumericColumn": "You pasted a spreadsheet without a numeric column.", "tooManyColumns": "You pasted a spreadsheet with more than two columns." + }, + "stats": { + "angle": "Angle", + "element": "Element", + "elements": "Elements", + "height": "Height", + "scene": "Scene", + "selected": "Selected", + "storage": "Storage", + "title": "Stats for nerds", + "total": "Total", + "width": "Width" } } diff --git a/src/scene/index.ts b/src/scene/index.ts index f914444c..6f3999a3 100644 --- a/src/scene/index.ts +++ b/src/scene/index.ts @@ -4,7 +4,7 @@ export { getElementsWithinSelection, getCommonAttributeOfSelectedElements, getSelectedElements, - getTargetElement, + getTargetElements, } from "./selection"; export { normalizeScroll, calculateScrollCenter } from "./scroll"; export { diff --git a/src/scene/selection.ts b/src/scene/selection.ts index 4e7e5eb5..93a7204a 100644 --- a/src/scene/selection.ts +++ b/src/scene/selection.ts @@ -33,9 +33,8 @@ export const getElementsWithinSelection = ( export const isSomeElementSelected = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, -): boolean => { - return elements.some((element) => appState.selectedElementIds[element.id]); -}; +): boolean => + elements.some((element) => appState.selectedElementIds[element.id]); /** * Returns common attribute (picked by `getAttribute` callback) of selected @@ -59,15 +58,12 @@ export const getCommonAttributeOfSelectedElements = ( export const getSelectedElements = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, -) => { - return elements.filter((element) => appState.selectedElementIds[element.id]); -}; +) => elements.filter((element) => appState.selectedElementIds[element.id]); -export const getTargetElement = ( +export const getTargetElements = ( elements: readonly NonDeletedExcalidrawElement[], appState: AppState, -) => { - return appState.editingElement +) => + appState.editingElement ? [appState.editingElement] : getSelectedElements(elements, appState); -}; diff --git a/src/tests/__snapshots__/regressionTests.test.tsx.snap b/src/tests/__snapshots__/regressionTests.test.tsx.snap index 59e6d58f..fa05c2ce 100644 --- a/src/tests/__snapshots__/regressionTests.test.tsx.snap +++ b/src/tests/__snapshots__/regressionTests.test.tsx.snap @@ -66,6 +66,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -525,6 +526,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -966,6 +968,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -1735,6 +1738,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -1935,6 +1939,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -2379,6 +2384,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -2620,6 +2626,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -2778,6 +2785,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -3243,6 +3251,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -3545,6 +3554,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -3742,6 +3752,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -3975,6 +3986,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -4219,6 +4231,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -4613,6 +4626,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -4877,6 +4891,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -5195,6 +5210,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -5372,6 +5388,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -5527,6 +5544,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -5978,6 +5996,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -6280,6 +6299,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -8256,6 +8276,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -8610,6 +8631,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -8854,6 +8876,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -9099,6 +9122,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -9400,6 +9424,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -9555,6 +9580,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -9710,6 +9736,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -9865,6 +9892,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -10046,6 +10074,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -10227,6 +10256,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -10408,6 +10438,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -10589,6 +10620,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -10744,6 +10776,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -10899,6 +10932,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -11080,6 +11114,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -11235,6 +11270,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -11427,6 +11463,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -12127,6 +12164,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -12367,6 +12405,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": true, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -12458,6 +12497,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -12551,6 +12591,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -12706,6 +12747,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -13005,6 +13047,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -13304,6 +13347,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -13457,6 +13501,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -13646,6 +13691,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -13892,6 +13938,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -14201,6 +14248,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -15031,6 +15079,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -15330,6 +15379,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -15633,6 +15683,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -16002,6 +16053,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -16166,6 +16218,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -16472,6 +16525,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -16705,6 +16759,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -16953,6 +17008,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -17261,6 +17317,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -17354,6 +17411,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -17520,6 +17578,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -18319,6 +18378,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -18412,6 +18472,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -19181,6 +19242,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -19575,6 +19637,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -19815,6 +19878,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": true, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -19908,6 +19972,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -20389,6 +20454,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", @@ -20480,6 +20546,7 @@ Object { "shouldAddWatermark": false, "shouldCacheIgnoreZoom": false, "showShortcutsDialog": false, + "showStats": false, "startBoundElement": null, "suggestedBindings": Array [], "viewBackgroundColor": "#ffffff", diff --git a/src/tests/regressionTests.test.tsx b/src/tests/regressionTests.test.tsx index 9dec3230..60762742 100644 --- a/src/tests/regressionTests.test.tsx +++ b/src/tests/regressionTests.test.tsx @@ -1,23 +1,23 @@ -import { reseed } from "../random"; +import { queryByText } from "@testing-library/react"; import React from "react"; import ReactDOM from "react-dom"; +import { copiedStyles } from "../actions/actionStyles"; +import { ExcalidrawElement } from "../element/types"; +import { setLanguage, t } from "../i18n"; +import { CODES, KEYS } from "../keys"; +import Excalidraw from "../packages/excalidraw/index"; +import { reseed } from "../random"; import * as Renderer from "../renderer/renderScene"; +import { setDateTimeForTests } from "../utils"; +import { API } from "./helpers/api"; +import { Keyboard, Pointer, UI } from "./helpers/ui"; import { - waitFor, - render, - screen, fireEvent, GlobalTestState, + render, + screen, + waitFor, } from "./test-utils"; -import Excalidraw from "../packages/excalidraw/index"; -import { setLanguage } from "../i18n"; -import { setDateTimeForTests } from "../utils"; -import { ExcalidrawElement } from "../element/types"; -import { queryByText } from "@testing-library/react"; -import { copiedStyles } from "../actions/actionStyles"; -import { UI, Pointer, Keyboard } from "./helpers/ui"; -import { API } from "./helpers/api"; -import { CODES, KEYS } from "../keys"; const { h } = window; @@ -633,10 +633,14 @@ describe("regression tests", () => { }); const contextMenu = document.querySelector(".context-menu"); const options = contextMenu?.querySelectorAll(".context-menu-option"); - const expectedOptions = ["Select all", "Toggle grid mode"]; + const expectedOptions = [ + t("labels.selectAll"), + t("labels.toggleGridMode"), + t("labels.toggleStats"), + ]; expect(contextMenu).not.toBeNull(); - expect(options?.length).toBe(2); + expect(options?.length).toBe(3); expect(options?.item(0).textContent).toBe(expectedOptions[0]); }); diff --git a/src/types.ts b/src/types.ts index d31aa961..377b2197 100644 --- a/src/types.ts +++ b/src/types.ts @@ -95,6 +95,7 @@ export type AppState = { isLibraryOpen: boolean; fileHandle: import("browser-nativefs").FileSystemHandle | null; collaborators: Map; + showStats: boolean; }; export type NormalizedZoomValue = number & { _brand: "normalizedZoom" }; diff --git a/src/utils.ts b/src/utils.ts index f22ca8c0..a31a0c4a 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -133,6 +133,9 @@ export const debounce = ( fn(...lastArgs); } }; + ret.cancel = () => { + clearTimeout(handle); + }; return ret; }; @@ -336,3 +339,23 @@ export const withBatchedUpdates = < ((event) => { unstable_batchedUpdates(func as TFunction, event); }) as TFunction; + +//https://stackoverflow.com/a/9462382/8418 +export const nFormatter = (num: number, digits: number): string => { + const si = [ + { value: 1, symbol: "b" }, + { value: 1e3, symbol: "k" }, + { value: 1e6, symbol: "M" }, + { value: 1e9, symbol: "G" }, + ]; + const rx = /\.0+$|(\.[0-9]*[1-9])0+$/; + let index; + for (index = si.length - 1; index > 0; index--) { + if (num >= si[index].value) { + break; + } + } + return ( + (num / si[index].value).toFixed(digits).replace(rx, "$1") + si[index].symbol + ); +};