import React, { useEffect, useState } from "react"; import { copyTextToSystemClipboard } from "../clipboard"; import { DEFAULT_VERSION } from "../constants"; import { getCommonBounds } from "../element/bounds"; import { NonDeletedExcalidrawElement } from "../element/types"; import { getElementsStorageSize, getTotalStorageSize, } from "../excalidraw-app/data/localStorage"; import { t } from "../i18n"; import { useIsMobile } from "../is-mobile"; import { getTargetElements } from "../scene"; import { AppState } from "../types"; import { debounce, getVersion, 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; setAppState: React.Component["setState"]; elements: readonly NonDeletedExcalidrawElement[]; onClose: () => void; }) => { const isMobile = useIsMobile(); 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); if (isMobile && props.appState.openMenu) { return null; } const version = getVersion(); let hash; let timestamp; if (version !== DEFAULT_VERSION) { timestamp = version.slice(0, 16).replace("T", " "); hash = version.slice(21); } else { timestamp = t("stats.versionNotAvailable"); } 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(selectedBoundingBox[0])}
{"y"} {Math.round(selectedBoundingBox[1])}
{t("stats.width")} {Math.round( selectedBoundingBox[2] - selectedBoundingBox[0], )}
{t("stats.height")} {Math.round( selectedBoundingBox[3] - selectedBoundingBox[1], )}
{t("stats.angle")} {`${Math.round( (selectedElements[0].angle * 180) / Math.PI, )}°`}
{t("stats.version")}
{ try { await copyTextToSystemClipboard(getVersion()); props.setAppState({ toastMessage: t("toast.copyToClipboard"), }); } catch {} }} title={t("stats.versionCopy")} > {timestamp}
{hash}
); };