2024-06-04 23:06:27 +08:00

176 lines
5.3 KiB
TypeScript

import React, { useEffect, useMemo, useState } from "react";
import { getCommonBounds } from "../../element/bounds";
import type { NonDeletedExcalidrawElement } from "../../element/types";
import { t } from "../../i18n";
import { getSelectedElements } from "../../scene";
import type Scene from "../../scene/Scene";
import type { AppState, ExcalidrawProps } from "../../types";
import { CloseIcon } from "../icons";
import { Island } from "../Island";
import { throttle } from "lodash";
import Dimension from "./Dimension";
import Angle from "./Angle";
import "./index.scss";
import FontSize from "./FontSize";
import MultiDimension from "./MultiDimension";
import { elementsAreInSameGroup } from "../../groups";
interface StatsProps {
appState: AppState;
scene: Scene;
setAppState: React.Component<any, AppState>["setState"];
onClose: () => void;
renderCustomStats: ExcalidrawProps["renderCustomStats"];
}
const STATS_TIMEOUT = 50;
export const Stats = (props: StatsProps) => {
const elements = props.scene.getNonDeletedElements();
const elementsMap = props.scene.getNonDeletedElementsMap();
const sceneNonce = props.scene.getSceneNonce();
// const selectedElements = getTargetElements(elements, props.appState);
const selectedElements = getSelectedElements(
props.scene.getNonDeletedElementsMap(),
props.appState,
{
includeBoundTextElement: false,
},
);
const singleElement =
selectedElements.length === 1 ? selectedElements[0] : null;
const multipleElements =
selectedElements.length > 1 ? selectedElements : null;
const [sceneDimension, setSceneDimension] = useState<{
width: number;
height: number;
}>({
width: 0,
height: 0,
});
const throttledSetSceneDimension = useMemo(
() =>
throttle((elements: readonly NonDeletedExcalidrawElement[]) => {
const boundingBox = getCommonBounds(elements);
setSceneDimension({
width: Math.round(boundingBox[2]) - Math.round(boundingBox[0]),
height: Math.round(boundingBox[3]) - Math.round(boundingBox[1]),
});
}, STATS_TIMEOUT),
[],
);
useEffect(() => {
throttledSetSceneDimension(elements);
}, [sceneNonce, elements, throttledSetSceneDimension]);
useEffect(
() => () => throttledSetSceneDimension.cancel(),
[throttledSetSceneDimension],
);
return (
<div className="Stats">
<Island padding={3}>
<div className="section">
<div className="close" onClick={props.onClose}>
{CloseIcon}
</div>
<h3>{t("stats.generalStats")}</h3>
<table>
<tbody>
<tr>
<th colSpan={2}>{t("stats.scene")}</th>
</tr>
<tr>
<td>{t("stats.elements")}</td>
<td>{elements.length}</td>
</tr>
<tr>
<td>{t("stats.width")}</td>
<td>{sceneDimension.width}</td>
</tr>
<tr>
<td>{t("stats.height")}</td>
<td>{sceneDimension.height}</td>
</tr>
{props.renderCustomStats?.(elements, props.appState)}
</tbody>
</table>
</div>
{selectedElements.length > 0 && (
<div
className="section"
style={{
marginTop: 12,
}}
>
<h3>{t("stats.elementStats")}</h3>
{singleElement && (
<div className="sectionContent">
<div className="elementType">
{t(`element.${singleElement.type}`)}
</div>
<div className="statsItem">
<Dimension
property="width"
element={singleElement}
elementsMap={elementsMap}
/>
<Dimension
property="height"
element={singleElement}
elementsMap={elementsMap}
/>
<Angle element={singleElement} elementsMap={elementsMap} />
{singleElement.type === "text" && (
<FontSize
element={singleElement}
elementsMap={elementsMap}
/>
)}
</div>
{singleElement.type === "text" && <div></div>}
</div>
)}
{multipleElements && (
<div className="sectionContent">
{elementsAreInSameGroup(multipleElements) && (
<div className="elementType">{t("element.group")}</div>
)}
<div className="elementsCount">
<div>{t("stats.elements")}</div>
<div>{selectedElements.length}</div>
</div>
<div className="statsItem">
<MultiDimension
property="width"
elements={multipleElements}
elementsMap={elementsMap}
/>
<MultiDimension
property="height"
elements={multipleElements}
elementsMap={elementsMap}
/>
</div>
</div>
)}
</div>
)}
</Island>
</div>
);
};