mirror of
https://github.com/excalidraw/excalidraw.git
synced 2025-05-03 10:00:07 -04:00
154 lines
4.4 KiB
TypeScript
154 lines
4.4 KiB
TypeScript
import React, { useEffect, useMemo, useState } from "react";
|
|
import { getCommonBounds } from "../element/bounds";
|
|
import { NonDeletedExcalidrawElement } from "../element/types";
|
|
import { t } from "../i18n";
|
|
import { getTargetElements } from "../scene";
|
|
import Scene from "../scene/Scene";
|
|
import { AppState, ExcalidrawProps } from "../types";
|
|
import { CloseIcon } from "./icons";
|
|
import { Island } from "./Island";
|
|
import "./Stats.scss";
|
|
import { throttle } from "lodash";
|
|
import DragInput from "./DragInput";
|
|
|
|
const STATS_TIMEOUT = 50;
|
|
interface StatsProps {
|
|
appState: AppState;
|
|
scene: Scene;
|
|
setAppState: React.Component<any, AppState>["setState"];
|
|
onClose: () => void;
|
|
renderCustomStats: ExcalidrawProps["renderCustomStats"];
|
|
}
|
|
|
|
type ElementStatItem = {
|
|
label: string;
|
|
property: "x" | "y" | "width" | "height" | "angle";
|
|
};
|
|
|
|
export const Stats = (props: StatsProps) => {
|
|
const elements = props.scene.getNonDeletedElements();
|
|
const selectedElements = getTargetElements(elements, props.appState);
|
|
|
|
const singleElement =
|
|
selectedElements.length === 1 ? selectedElements[0] : 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);
|
|
}, [props.scene.version, 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>
|
|
|
|
{singleElement && (
|
|
<div
|
|
className="section"
|
|
style={{
|
|
marginTop: 12,
|
|
}}
|
|
>
|
|
<h3>{t("stats.elementStats")}</h3>
|
|
|
|
<div className="sectionContent">
|
|
<div className="elementType">
|
|
{t(`element.${singleElement.type}`)}
|
|
</div>
|
|
|
|
<div
|
|
style={{
|
|
display: "grid",
|
|
gridTemplateColumns: "repeat(2, 1fr)",
|
|
gap: "4px 8px",
|
|
}}
|
|
>
|
|
{(
|
|
[
|
|
{
|
|
label: "X",
|
|
property: "x",
|
|
},
|
|
{
|
|
label: "Y",
|
|
property: "y",
|
|
},
|
|
{
|
|
label: "W",
|
|
property: "width",
|
|
},
|
|
{
|
|
label: "H",
|
|
property: "height",
|
|
},
|
|
{
|
|
label: "A",
|
|
property: "angle",
|
|
},
|
|
] as ElementStatItem[]
|
|
).map((statsItem) => (
|
|
<DragInput
|
|
key={statsItem.label}
|
|
label={statsItem.label}
|
|
property={statsItem.property}
|
|
element={singleElement}
|
|
zoom={props.appState.zoom}
|
|
/>
|
|
))}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</Island>
|
|
</div>
|
|
);
|
|
};
|