mirror of
https://github.com/excalidraw/excalidraw.git
synced 2024-11-02 03:25:53 +01:00
feat: Stats popup style tweaks (#8361)
This commit is contained in:
parent
f7b3befd0a
commit
97981804d7
@ -9,6 +9,7 @@ import { t } from "../packages/excalidraw/i18n";
|
|||||||
import { copyTextToSystemClipboard } from "../packages/excalidraw/clipboard";
|
import { copyTextToSystemClipboard } from "../packages/excalidraw/clipboard";
|
||||||
import type { NonDeletedExcalidrawElement } from "../packages/excalidraw/element/types";
|
import type { NonDeletedExcalidrawElement } from "../packages/excalidraw/element/types";
|
||||||
import type { UIAppState } from "../packages/excalidraw/types";
|
import type { UIAppState } from "../packages/excalidraw/types";
|
||||||
|
import { Stats } from "../packages/excalidraw";
|
||||||
|
|
||||||
type StorageSizes = { scene: number; total: number };
|
type StorageSizes = { scene: number; total: number };
|
||||||
|
|
||||||
@ -51,39 +52,33 @@ const CustomStats = (props: Props) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<Stats.StatsRows order={-1}>
|
||||||
<tr>
|
<Stats.StatsRow heading>{t("stats.version")}</Stats.StatsRow>
|
||||||
<th colSpan={2}>{t("stats.storage")}</th>
|
<Stats.StatsRow
|
||||||
</tr>
|
style={{ textAlign: "center", cursor: "pointer" }}
|
||||||
<tr>
|
onClick={async () => {
|
||||||
<td>{t("stats.scene")}</td>
|
try {
|
||||||
<td>{nFormatter(storageSizes.scene, 1)}</td>
|
await copyTextToSystemClipboard(getVersion());
|
||||||
</tr>
|
props.setToast(t("toast.copyToClipboard"));
|
||||||
<tr>
|
} catch {}
|
||||||
<td>{t("stats.total")}</td>
|
}}
|
||||||
<td>{nFormatter(storageSizes.total, 1)}</td>
|
title={t("stats.versionCopy")}
|
||||||
</tr>
|
>
|
||||||
<tr>
|
{timestamp}
|
||||||
<th colSpan={2}>{t("stats.version")}</th>
|
<br />
|
||||||
</tr>
|
{hash}
|
||||||
<tr>
|
</Stats.StatsRow>
|
||||||
<td
|
|
||||||
colSpan={2}
|
<Stats.StatsRow heading>{t("stats.storage")}</Stats.StatsRow>
|
||||||
style={{ textAlign: "center", cursor: "pointer" }}
|
<Stats.StatsRow columns={2}>
|
||||||
onClick={async () => {
|
<div>{t("stats.scene")}</div>
|
||||||
try {
|
<div>{nFormatter(storageSizes.scene, 1)}</div>
|
||||||
await copyTextToSystemClipboard(getVersion());
|
</Stats.StatsRow>
|
||||||
props.setToast(t("toast.copyToClipboard"));
|
<Stats.StatsRow columns={2}>
|
||||||
} catch {}
|
<div>{t("stats.total")}</div>
|
||||||
}}
|
<div>{nFormatter(storageSizes.total, 1)}</div>
|
||||||
title={t("stats.versionCopy")}
|
</Stats.StatsRow>
|
||||||
>
|
</Stats.StatsRows>
|
||||||
{timestamp}
|
|
||||||
<br />
|
|
||||||
{hash}
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
</>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -39,6 +39,8 @@ Please add the latest change on the top under the correct section.
|
|||||||
|
|
||||||
### Breaking Changes
|
### Breaking Changes
|
||||||
|
|
||||||
|
- Stats container CSS changed, so if you're using `renderCustomStats`, you may need to adjust your styles to retain the same layout.
|
||||||
|
|
||||||
- `updateScene` API has changed due to the added `Store` component as part of the multiplayer undo / redo initiative. Specifically, `sceneData` property `commitToHistory: boolean` was replaced with `storeAction: StoreActionType`. Make sure to update all instances of `updateScene` according to the _before / after_ table below. [#7898](https://github.com/excalidraw/excalidraw/pull/7898)
|
- `updateScene` API has changed due to the added `Store` component as part of the multiplayer undo / redo initiative. Specifically, `sceneData` property `commitToHistory: boolean` was replaced with `storeAction: StoreActionType`. Make sure to update all instances of `updateScene` according to the _before / after_ table below. [#7898](https://github.com/excalidraw/excalidraw/pull/7898)
|
||||||
|
|
||||||
| | Before `commitToHistory` | After `storeAction` | Notes |
|
| | Before `commitToHistory` | After `storeAction` | Notes |
|
||||||
|
@ -27,99 +27,6 @@
|
|||||||
& > * {
|
& > * {
|
||||||
pointer-events: var(--ui-pointerEvents);
|
pointer-events: var(--ui-pointerEvents);
|
||||||
}
|
}
|
||||||
|
|
||||||
& > .Stats {
|
|
||||||
width: 204px;
|
|
||||||
position: absolute;
|
|
||||||
top: 60px;
|
|
||||||
font-size: 12px;
|
|
||||||
z-index: var(--zIndex-layerUI);
|
|
||||||
pointer-events: var(--ui-pointerEvents);
|
|
||||||
|
|
||||||
.title {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 12px;
|
|
||||||
|
|
||||||
h2 {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.sectionContent {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.elementType {
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.elementsCount {
|
|
||||||
width: 100%;
|
|
||||||
font-size: 12px;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
margin-top: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.statsItem {
|
|
||||||
margin-top: 8px;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 4px;
|
|
||||||
display: grid;
|
|
||||||
gap: 4px;
|
|
||||||
|
|
||||||
.label {
|
|
||||||
margin-right: 4px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
white-space: nowrap;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close {
|
|
||||||
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: 24px;
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.divider {
|
|
||||||
width: 100%;
|
|
||||||
height: 1px;
|
|
||||||
background-color: var(--default-border-color);
|
|
||||||
}
|
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
|
||||||
left: 12px;
|
|
||||||
right: initial;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__footer {
|
&__footer {
|
||||||
|
@ -31,7 +31,11 @@ const Collapsible = ({
|
|||||||
{label}
|
{label}
|
||||||
<InlineIcon icon={open ? collapseUpIcon : collapseDownIcon} />
|
<InlineIcon icon={open ? collapseUpIcon : collapseDownIcon} />
|
||||||
</div>
|
</div>
|
||||||
{open && <>{children}</>}
|
{open && (
|
||||||
|
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
&:focus-within {
|
&:focus-within {
|
||||||
box-shadow: 0 0 0 1px var(--color-primary-darkest);
|
box-shadow: 0 0 0 1px var(--color-primary-darkest);
|
||||||
border-radius: var(--border-radius-lg);
|
border-radius: var(--border-radius-md);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,11 +24,11 @@
|
|||||||
color: var(--popup-text-color);
|
color: var(--popup-text-color);
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
|
border-radius: var(--border-radius-md) 0 0 var(--border-radius-md);
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
:root[dir="rtl"] & {
|
||||||
border-radius: 0 var(--border-radius-lg) var(--border-radius-lg) 0;
|
border-radius: 0 var(--border-radius-md) var(--border-radius-md) 0;
|
||||||
border-right: 1px solid var(--default-border-color);
|
border-right: 1px solid var(--default-border-color);
|
||||||
border-left: 0;
|
border-left: 0;
|
||||||
}
|
}
|
||||||
@ -55,11 +55,11 @@
|
|||||||
letter-spacing: 0.4px;
|
letter-spacing: 0.4px;
|
||||||
|
|
||||||
:root[dir="ltr"] & {
|
:root[dir="ltr"] & {
|
||||||
border-radius: 0 var(--border-radius-lg) var(--border-radius-lg) 0;
|
border-radius: 0 var(--border-radius-md) var(--border-radius-md) 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
:root[dir="rtl"] & {
|
:root[dir="rtl"] & {
|
||||||
border-radius: var(--border-radius-lg) 0 0 var(--border-radius-lg);
|
border-radius: var(--border-radius-md) 0 0 var(--border-radius-md);
|
||||||
border-left: 1px solid var(--default-border-color);
|
border-left: 1px solid var(--default-border-color);
|
||||||
border-right: 0;
|
border-right: 0;
|
||||||
}
|
}
|
||||||
|
72
packages/excalidraw/components/Stats/Stats.scss
Normal file
72
packages/excalidraw/components/Stats/Stats.scss
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
.exc-stats {
|
||||||
|
width: 204px;
|
||||||
|
position: absolute;
|
||||||
|
top: 60px;
|
||||||
|
font-size: 12px;
|
||||||
|
z-index: var(--zIndex-layerUI);
|
||||||
|
pointer-events: var(--ui-pointerEvents);
|
||||||
|
|
||||||
|
:root[dir="rtl"] & {
|
||||||
|
left: 12px;
|
||||||
|
right: initial;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
margin-block-start: 0.83em;
|
||||||
|
margin-block-end: 0.83em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
h3 {
|
||||||
|
white-space: nowrap;
|
||||||
|
font-size: 1.17em;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rows {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.3125rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__row {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
display: grid;
|
||||||
|
gap: 4px;
|
||||||
|
|
||||||
|
div + div {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__row--heading {
|
||||||
|
text-align: center;
|
||||||
|
font-weight: bold;
|
||||||
|
margin: 0.25rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.close {
|
||||||
|
height: 16px;
|
||||||
|
width: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
svg {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -8,7 +8,6 @@ import { Island } from "../Island";
|
|||||||
import { throttle } from "lodash";
|
import { throttle } from "lodash";
|
||||||
import Dimension from "./Dimension";
|
import Dimension from "./Dimension";
|
||||||
import Angle from "./Angle";
|
import Angle from "./Angle";
|
||||||
|
|
||||||
import FontSize from "./FontSize";
|
import FontSize from "./FontSize";
|
||||||
import MultiDimension from "./MultiDimension";
|
import MultiDimension from "./MultiDimension";
|
||||||
import { elementsAreInSameGroup } from "../../groups";
|
import { elementsAreInSameGroup } from "../../groups";
|
||||||
@ -22,6 +21,9 @@ import { useExcalidrawAppState, useExcalidrawSetAppState } from "../App";
|
|||||||
import { getAtomicUnits } from "./utils";
|
import { getAtomicUnits } from "./utils";
|
||||||
import { STATS_PANELS } from "../../constants";
|
import { STATS_PANELS } from "../../constants";
|
||||||
import { isElbowArrow } from "../../element/typeChecks";
|
import { isElbowArrow } from "../../element/typeChecks";
|
||||||
|
import clsx from "clsx";
|
||||||
|
|
||||||
|
import "./Stats.scss";
|
||||||
|
|
||||||
interface StatsProps {
|
interface StatsProps {
|
||||||
scene: Scene;
|
scene: Scene;
|
||||||
@ -49,6 +51,50 @@ export const Stats = (props: StatsProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const StatsRow = ({
|
||||||
|
children,
|
||||||
|
columns = 1,
|
||||||
|
heading,
|
||||||
|
style,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
columns?: number;
|
||||||
|
heading?: boolean;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
} & React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div
|
||||||
|
className={clsx("exc-stats__row", { "exc-stats__row--heading": heading })}
|
||||||
|
style={{
|
||||||
|
gridTemplateColumns: `repeat(${columns}, 1fr)`,
|
||||||
|
...style,
|
||||||
|
}}
|
||||||
|
{...rest}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
StatsRow.displayName = "StatsRow";
|
||||||
|
|
||||||
|
const StatsRows = ({
|
||||||
|
children,
|
||||||
|
order,
|
||||||
|
style,
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
order?: number;
|
||||||
|
style?: React.CSSProperties;
|
||||||
|
} & React.HTMLAttributes<HTMLDivElement>) => (
|
||||||
|
<div className="exc-stats__rows" style={{ order, ...style }} {...rest}>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
StatsRows.displayName = "StatsRows";
|
||||||
|
|
||||||
|
Stats.StatsRow = StatsRow;
|
||||||
|
Stats.StatsRows = StatsRows;
|
||||||
|
|
||||||
export const StatsInner = memo(
|
export const StatsInner = memo(
|
||||||
({
|
({
|
||||||
scene,
|
scene,
|
||||||
@ -106,7 +152,7 @@ export const StatsInner = memo(
|
|||||||
}, [selectedElements, appState]);
|
}, [selectedElements, appState]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="Stats">
|
<div className="exc-stats">
|
||||||
<Island padding={3}>
|
<Island padding={3}>
|
||||||
<div className="title">
|
<div className="title">
|
||||||
<h2>{t("stats.title")}</h2>
|
<h2>{t("stats.title")}</h2>
|
||||||
@ -121,7 +167,6 @@ export const StatsInner = memo(
|
|||||||
openTrigger={() =>
|
openTrigger={() =>
|
||||||
setAppState((state) => {
|
setAppState((state) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
|
||||||
stats: {
|
stats: {
|
||||||
open: true,
|
open: true,
|
||||||
panels: state.stats.panels ^ STATS_PANELS.generalStats,
|
panels: state.stats.panels ^ STATS_PANELS.generalStats,
|
||||||
@ -130,26 +175,23 @@ export const StatsInner = memo(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<table>
|
<StatsRows>
|
||||||
<tbody>
|
<StatsRow heading>{t("stats.scene")}</StatsRow>
|
||||||
<tr>
|
<StatsRow columns={2}>
|
||||||
<th colSpan={2}>{t("stats.scene")}</th>
|
<div>{t("stats.shapes")}</div>
|
||||||
</tr>
|
<div>{elements.length}</div>
|
||||||
<tr>
|
</StatsRow>
|
||||||
<td>{t("stats.elements")}</td>
|
<StatsRow columns={2}>
|
||||||
<td>{elements.length}</td>
|
<div>{t("stats.width")}</div>
|
||||||
</tr>
|
<div>{sceneDimension.width}</div>
|
||||||
<tr>
|
</StatsRow>
|
||||||
<td>{t("stats.width")}</td>
|
<StatsRow columns={2}>
|
||||||
<td>{sceneDimension.width}</td>
|
<div>{t("stats.height")}</div>
|
||||||
</tr>
|
<div>{sceneDimension.height}</div>
|
||||||
<tr>
|
</StatsRow>
|
||||||
<td>{t("stats.height")}</td>
|
</StatsRows>
|
||||||
<td>{sceneDimension.height}</td>
|
|
||||||
</tr>
|
{renderCustomStats?.(elements, appState)}
|
||||||
{renderCustomStats?.(elements, appState)}
|
|
||||||
</tbody>
|
|
||||||
</table>
|
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
|
|
||||||
{selectedElements.length > 0 && (
|
{selectedElements.length > 0 && (
|
||||||
@ -167,7 +209,6 @@ export const StatsInner = memo(
|
|||||||
openTrigger={() =>
|
openTrigger={() =>
|
||||||
setAppState((state) => {
|
setAppState((state) => {
|
||||||
return {
|
return {
|
||||||
...state,
|
|
||||||
stats: {
|
stats: {
|
||||||
open: true,
|
open: true,
|
||||||
panels:
|
panels:
|
||||||
@ -177,117 +218,139 @@ export const StatsInner = memo(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
{singleElement && (
|
<StatsRows>
|
||||||
<div className="sectionContent">
|
{singleElement && (
|
||||||
<div className="elementType">
|
<>
|
||||||
{t(`element.${singleElement.type}`)}
|
<StatsRow heading data-testid="stats-element-type">
|
||||||
</div>
|
{t(`element.${singleElement.type}`)}
|
||||||
|
</StatsRow>
|
||||||
|
|
||||||
<div className="statsItem">
|
<StatsRow>
|
||||||
<Position
|
<Position
|
||||||
element={singleElement}
|
element={singleElement}
|
||||||
property="x"
|
property="x"
|
||||||
elementsMap={elementsMap}
|
elementsMap={elementsMap}
|
||||||
scene={scene}
|
scene={scene}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
/>
|
/>
|
||||||
<Position
|
</StatsRow>
|
||||||
element={singleElement}
|
<StatsRow>
|
||||||
property="y"
|
<Position
|
||||||
elementsMap={elementsMap}
|
element={singleElement}
|
||||||
scene={scene}
|
property="y"
|
||||||
appState={appState}
|
elementsMap={elementsMap}
|
||||||
/>
|
scene={scene}
|
||||||
<Dimension
|
appState={appState}
|
||||||
property="width"
|
/>
|
||||||
element={singleElement}
|
</StatsRow>
|
||||||
scene={scene}
|
<StatsRow>
|
||||||
appState={appState}
|
<Dimension
|
||||||
/>
|
property="width"
|
||||||
<Dimension
|
|
||||||
property="height"
|
|
||||||
element={singleElement}
|
|
||||||
scene={scene}
|
|
||||||
appState={appState}
|
|
||||||
/>
|
|
||||||
{!isElbowArrow(singleElement) && (
|
|
||||||
<Angle
|
|
||||||
property="angle"
|
|
||||||
element={singleElement}
|
element={singleElement}
|
||||||
scene={scene}
|
scene={scene}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
/>
|
/>
|
||||||
|
</StatsRow>
|
||||||
|
<StatsRow>
|
||||||
|
<Dimension
|
||||||
|
property="height"
|
||||||
|
element={singleElement}
|
||||||
|
scene={scene}
|
||||||
|
appState={appState}
|
||||||
|
/>
|
||||||
|
</StatsRow>
|
||||||
|
{!isElbowArrow(singleElement) && (
|
||||||
|
<StatsRow>
|
||||||
|
<Angle
|
||||||
|
property="angle"
|
||||||
|
element={singleElement}
|
||||||
|
scene={scene}
|
||||||
|
appState={appState}
|
||||||
|
/>
|
||||||
|
</StatsRow>
|
||||||
)}
|
)}
|
||||||
<FontSize
|
<StatsRow>
|
||||||
property="fontSize"
|
<FontSize
|
||||||
element={singleElement}
|
property="fontSize"
|
||||||
scene={scene}
|
element={singleElement}
|
||||||
appState={appState}
|
scene={scene}
|
||||||
/>
|
appState={appState}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</StatsRow>
|
||||||
)}
|
</>
|
||||||
|
)}
|
||||||
|
|
||||||
{multipleElements && (
|
{multipleElements && (
|
||||||
<div className="sectionContent">
|
<>
|
||||||
{elementsAreInSameGroup(multipleElements) && (
|
{elementsAreInSameGroup(multipleElements) && (
|
||||||
<div className="elementType">{t("element.group")}</div>
|
<StatsRow heading>{t("element.group")}</StatsRow>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div className="elementsCount">
|
<StatsRow columns={2} style={{ margin: "0.3125rem 0" }}>
|
||||||
<div>{t("stats.elements")}</div>
|
<div>{t("stats.shapes")}</div>
|
||||||
<div>{selectedElements.length}</div>
|
<div>{selectedElements.length}</div>
|
||||||
</div>
|
</StatsRow>
|
||||||
|
|
||||||
<div className="statsItem">
|
<StatsRow>
|
||||||
<MultiPosition
|
<MultiPosition
|
||||||
property="x"
|
property="x"
|
||||||
elements={multipleElements}
|
elements={multipleElements}
|
||||||
elementsMap={elementsMap}
|
elementsMap={elementsMap}
|
||||||
atomicUnits={atomicUnits}
|
atomicUnits={atomicUnits}
|
||||||
scene={scene}
|
scene={scene}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
/>
|
/>
|
||||||
<MultiPosition
|
</StatsRow>
|
||||||
property="y"
|
<StatsRow>
|
||||||
elements={multipleElements}
|
<MultiPosition
|
||||||
elementsMap={elementsMap}
|
property="y"
|
||||||
atomicUnits={atomicUnits}
|
elements={multipleElements}
|
||||||
scene={scene}
|
elementsMap={elementsMap}
|
||||||
appState={appState}
|
atomicUnits={atomicUnits}
|
||||||
/>
|
scene={scene}
|
||||||
<MultiDimension
|
appState={appState}
|
||||||
property="width"
|
/>
|
||||||
elements={multipleElements}
|
</StatsRow>
|
||||||
elementsMap={elementsMap}
|
<StatsRow>
|
||||||
atomicUnits={atomicUnits}
|
<MultiDimension
|
||||||
scene={scene}
|
property="width"
|
||||||
appState={appState}
|
elements={multipleElements}
|
||||||
/>
|
elementsMap={elementsMap}
|
||||||
<MultiDimension
|
atomicUnits={atomicUnits}
|
||||||
property="height"
|
scene={scene}
|
||||||
elements={multipleElements}
|
appState={appState}
|
||||||
elementsMap={elementsMap}
|
/>
|
||||||
atomicUnits={atomicUnits}
|
</StatsRow>
|
||||||
scene={scene}
|
<StatsRow>
|
||||||
appState={appState}
|
<MultiDimension
|
||||||
/>
|
property="height"
|
||||||
<MultiAngle
|
elements={multipleElements}
|
||||||
property="angle"
|
elementsMap={elementsMap}
|
||||||
elements={multipleElements}
|
atomicUnits={atomicUnits}
|
||||||
scene={scene}
|
scene={scene}
|
||||||
appState={appState}
|
appState={appState}
|
||||||
/>
|
/>
|
||||||
<MultiFontSize
|
</StatsRow>
|
||||||
property="fontSize"
|
<StatsRow>
|
||||||
elements={multipleElements}
|
<MultiAngle
|
||||||
scene={scene}
|
property="angle"
|
||||||
appState={appState}
|
elements={multipleElements}
|
||||||
elementsMap={elementsMap}
|
scene={scene}
|
||||||
/>
|
appState={appState}
|
||||||
</div>
|
/>
|
||||||
</div>
|
</StatsRow>
|
||||||
)}
|
<StatsRow>
|
||||||
|
<MultiFontSize
|
||||||
|
property="fontSize"
|
||||||
|
elements={multipleElements}
|
||||||
|
scene={scene}
|
||||||
|
appState={appState}
|
||||||
|
elementsMap={elementsMap}
|
||||||
|
/>
|
||||||
|
</StatsRow>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</StatsRows>
|
||||||
</Collapsible>
|
</Collapsible>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
@ -32,21 +32,6 @@ const renderStaticScene = vi.spyOn(StaticScene, "renderStaticScene");
|
|||||||
let stats: HTMLElement | null = null;
|
let stats: HTMLElement | null = null;
|
||||||
let elementStats: HTMLElement | null | undefined = null;
|
let elementStats: HTMLElement | null | undefined = null;
|
||||||
|
|
||||||
const getStatsProperty = (label: string) => {
|
|
||||||
const elementStats = UI.queryStats()?.querySelector("#elementStats");
|
|
||||||
|
|
||||||
if (elementStats) {
|
|
||||||
const properties = elementStats?.querySelector(".statsItem");
|
|
||||||
return (
|
|
||||||
properties?.querySelector?.(
|
|
||||||
`.drag-input-container[data-testid="${label}"]`,
|
|
||||||
) || null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const testInputProperty = (
|
const testInputProperty = (
|
||||||
element: ExcalidrawElement,
|
element: ExcalidrawElement,
|
||||||
property: "x" | "y" | "width" | "height" | "angle" | "fontSize",
|
property: "x" | "y" | "width" | "height" | "angle" | "fontSize",
|
||||||
@ -54,7 +39,7 @@ const testInputProperty = (
|
|||||||
initialValue: number,
|
initialValue: number,
|
||||||
nextValue: number,
|
nextValue: number,
|
||||||
) => {
|
) => {
|
||||||
const input = getStatsProperty(label)?.querySelector(
|
const input = UI.queryStatsProperty(label)?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(input).toBeDefined();
|
expect(input).toBeDefined();
|
||||||
@ -136,7 +121,7 @@ describe("binding with linear elements", () => {
|
|||||||
|
|
||||||
it("should remain bound to linear element on small position change", async () => {
|
it("should remain bound to linear element on small position change", async () => {
|
||||||
const linear = h.elements[1] as ExcalidrawLinearElement;
|
const linear = h.elements[1] as ExcalidrawLinearElement;
|
||||||
const inputX = getStatsProperty("X")?.querySelector(
|
const inputX = UI.queryStatsProperty("X")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
|
||||||
@ -148,7 +133,7 @@ describe("binding with linear elements", () => {
|
|||||||
|
|
||||||
it("should remain bound to linear element on small angle change", async () => {
|
it("should remain bound to linear element on small angle change", async () => {
|
||||||
const linear = h.elements[1] as ExcalidrawLinearElement;
|
const linear = h.elements[1] as ExcalidrawLinearElement;
|
||||||
const inputAngle = getStatsProperty("A")?.querySelector(
|
const inputAngle = UI.queryStatsProperty("A")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
|
||||||
@ -159,7 +144,7 @@ describe("binding with linear elements", () => {
|
|||||||
|
|
||||||
it("should unbind linear element on large position change", async () => {
|
it("should unbind linear element on large position change", async () => {
|
||||||
const linear = h.elements[1] as ExcalidrawLinearElement;
|
const linear = h.elements[1] as ExcalidrawLinearElement;
|
||||||
const inputX = getStatsProperty("X")?.querySelector(
|
const inputX = UI.queryStatsProperty("X")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
|
||||||
@ -171,7 +156,7 @@ describe("binding with linear elements", () => {
|
|||||||
|
|
||||||
it("should remain bound to linear element on small angle change", async () => {
|
it("should remain bound to linear element on small angle change", async () => {
|
||||||
const linear = h.elements[1] as ExcalidrawLinearElement;
|
const linear = h.elements[1] as ExcalidrawLinearElement;
|
||||||
const inputAngle = getStatsProperty("A")?.querySelector(
|
const inputAngle = UI.queryStatsProperty("A")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
|
||||||
@ -225,18 +210,14 @@ describe("stats for a generic element", () => {
|
|||||||
expect(title?.lastChild?.nodeValue)?.toBe(t("stats.elementProperties"));
|
expect(title?.lastChild?.nodeValue)?.toBe(t("stats.elementProperties"));
|
||||||
|
|
||||||
// element type
|
// element type
|
||||||
const elementType = elementStats?.querySelector(".elementType");
|
const elementType = queryByTestId(elementStats!, "stats-element-type");
|
||||||
expect(elementType).toBeDefined();
|
expect(elementType).toBeDefined();
|
||||||
expect(elementType?.lastChild?.nodeValue).toBe(t("element.rectangle"));
|
expect(elementType?.lastChild?.nodeValue).toBe(t("element.rectangle"));
|
||||||
|
|
||||||
// properties
|
// properties
|
||||||
const properties = elementStats?.querySelector(".statsItem");
|
|
||||||
expect(properties?.childNodes).toBeDefined();
|
|
||||||
["X", "Y", "W", "H", "A"].forEach((label) => () => {
|
["X", "Y", "W", "H", "A"].forEach((label) => () => {
|
||||||
expect(
|
expect(
|
||||||
properties?.querySelector?.(
|
stats!.querySelector?.(`.drag-input-container[data-testid="${label}"]`),
|
||||||
`.drag-input-container[data-testid="${label}"]`,
|
|
||||||
),
|
|
||||||
).toBeDefined();
|
).toBeDefined();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -257,7 +238,7 @@ describe("stats for a generic element", () => {
|
|||||||
const rectangle = h.elements[0];
|
const rectangle = h.elements[0];
|
||||||
const rectangleId = rectangle.id;
|
const rectangleId = rectangle.id;
|
||||||
|
|
||||||
const input = getStatsProperty("W")?.querySelector(
|
const input = UI.queryStatsProperty("W")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(input).toBeDefined();
|
expect(input).toBeDefined();
|
||||||
@ -287,11 +268,11 @@ describe("stats for a generic element", () => {
|
|||||||
rectangle.angle,
|
rectangle.angle,
|
||||||
);
|
);
|
||||||
|
|
||||||
const xInput = getStatsProperty("X")?.querySelector(
|
const xInput = UI.queryStatsProperty("X")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
|
||||||
const yInput = getStatsProperty("Y")?.querySelector(
|
const yInput = UI.queryStatsProperty("Y")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
|
||||||
@ -417,7 +398,7 @@ describe("stats for a non-generic element", () => {
|
|||||||
elementStats = stats?.querySelector("#elementStats");
|
elementStats = stats?.querySelector("#elementStats");
|
||||||
|
|
||||||
// can change font size
|
// can change font size
|
||||||
const input = getStatsProperty("F")?.querySelector(
|
const input = UI.queryStatsProperty("F")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(input).toBeDefined();
|
expect(input).toBeDefined();
|
||||||
@ -426,9 +407,9 @@ describe("stats for a non-generic element", () => {
|
|||||||
expect(text.fontSize).toBe(36);
|
expect(text.fontSize).toBe(36);
|
||||||
|
|
||||||
// cannot change width or height
|
// cannot change width or height
|
||||||
const width = getStatsProperty("W")?.querySelector(".drag-input");
|
const width = UI.queryStatsProperty("W")?.querySelector(".drag-input");
|
||||||
expect(width).toBeUndefined();
|
expect(width).toBeUndefined();
|
||||||
const height = getStatsProperty("H")?.querySelector(".drag-input");
|
const height = UI.queryStatsProperty("H")?.querySelector(".drag-input");
|
||||||
expect(height).toBeUndefined();
|
expect(height).toBeUndefined();
|
||||||
|
|
||||||
// min font size is 4
|
// min font size is 4
|
||||||
@ -456,7 +437,7 @@ describe("stats for a non-generic element", () => {
|
|||||||
expect(elementStats).toBeDefined();
|
expect(elementStats).toBeDefined();
|
||||||
|
|
||||||
// cannot change angle
|
// cannot change angle
|
||||||
const angle = getStatsProperty("A")?.querySelector(".drag-input");
|
const angle = UI.queryStatsProperty("A")?.querySelector(".drag-input");
|
||||||
expect(angle).toBeUndefined();
|
expect(angle).toBeUndefined();
|
||||||
|
|
||||||
// can change width or height
|
// can change width or height
|
||||||
@ -506,7 +487,7 @@ describe("stats for a non-generic element", () => {
|
|||||||
API.setElements([container, text]);
|
API.setElements([container, text]);
|
||||||
|
|
||||||
API.setSelectedElements([container]);
|
API.setSelectedElements([container]);
|
||||||
const fontSize = getStatsProperty("F")?.querySelector(
|
const fontSize = UI.queryStatsProperty("F")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(fontSize).toBeDefined();
|
expect(fontSize).toBeDefined();
|
||||||
@ -570,15 +551,15 @@ describe("stats for multiple elements", () => {
|
|||||||
|
|
||||||
elementStats = stats?.querySelector("#elementStats");
|
elementStats = stats?.querySelector("#elementStats");
|
||||||
|
|
||||||
const width = getStatsProperty("W")?.querySelector(
|
const width = UI.queryStatsProperty("W")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(width?.value).toBe("Mixed");
|
expect(width?.value).toBe("Mixed");
|
||||||
const height = getStatsProperty("H")?.querySelector(
|
const height = UI.queryStatsProperty("H")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(height?.value).toBe("Mixed");
|
expect(height?.value).toBe("Mixed");
|
||||||
const angle = getStatsProperty("A")?.querySelector(
|
const angle = UI.queryStatsProperty("A")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(angle.value).toBe("0");
|
expect(angle.value).toBe("0");
|
||||||
@ -629,25 +610,25 @@ describe("stats for multiple elements", () => {
|
|||||||
|
|
||||||
elementStats = stats?.querySelector("#elementStats");
|
elementStats = stats?.querySelector("#elementStats");
|
||||||
|
|
||||||
const width = getStatsProperty("W")?.querySelector(
|
const width = UI.queryStatsProperty("W")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(width).toBeDefined();
|
expect(width).toBeDefined();
|
||||||
expect(width.value).toBe("Mixed");
|
expect(width.value).toBe("Mixed");
|
||||||
|
|
||||||
const height = getStatsProperty("H")?.querySelector(
|
const height = UI.queryStatsProperty("H")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(height).toBeDefined();
|
expect(height).toBeDefined();
|
||||||
expect(height.value).toBe("Mixed");
|
expect(height.value).toBe("Mixed");
|
||||||
|
|
||||||
const angle = getStatsProperty("A")?.querySelector(
|
const angle = UI.queryStatsProperty("A")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(angle).toBeDefined();
|
expect(angle).toBeDefined();
|
||||||
expect(angle.value).toBe("0");
|
expect(angle.value).toBe("0");
|
||||||
|
|
||||||
const fontSize = getStatsProperty("F")?.querySelector(
|
const fontSize = UI.queryStatsProperty("F")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(fontSize).toBeDefined();
|
expect(fontSize).toBeDefined();
|
||||||
@ -692,7 +673,7 @@ describe("stats for multiple elements", () => {
|
|||||||
|
|
||||||
elementStats = stats?.querySelector("#elementStats");
|
elementStats = stats?.querySelector("#elementStats");
|
||||||
|
|
||||||
const x = getStatsProperty("X")?.querySelector(
|
const x = UI.queryStatsProperty("X")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
|
||||||
@ -705,7 +686,7 @@ describe("stats for multiple elements", () => {
|
|||||||
expect(h.elements[1].x).toBe(400);
|
expect(h.elements[1].x).toBe(400);
|
||||||
expect(x.value).toBe("300");
|
expect(x.value).toBe("300");
|
||||||
|
|
||||||
const y = getStatsProperty("Y")?.querySelector(
|
const y = UI.queryStatsProperty("Y")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
|
|
||||||
@ -718,13 +699,13 @@ describe("stats for multiple elements", () => {
|
|||||||
expect(h.elements[1].y).toBe(300);
|
expect(h.elements[1].y).toBe(300);
|
||||||
expect(y.value).toBe("200");
|
expect(y.value).toBe("200");
|
||||||
|
|
||||||
const width = getStatsProperty("W")?.querySelector(
|
const width = UI.queryStatsProperty("W")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(width).toBeDefined();
|
expect(width).toBeDefined();
|
||||||
expect(Number(width.value)).toBe(200);
|
expect(Number(width.value)).toBe(200);
|
||||||
|
|
||||||
const height = getStatsProperty("H")?.querySelector(
|
const height = UI.queryStatsProperty("H")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
expect(height).toBeDefined();
|
expect(height).toBeDefined();
|
||||||
|
@ -22,21 +22,6 @@ const { h } = window;
|
|||||||
|
|
||||||
const mouse = new Pointer("mouse");
|
const mouse = new Pointer("mouse");
|
||||||
|
|
||||||
const getStatsProperty = (label: string) => {
|
|
||||||
const elementStats = UI.queryStats()?.querySelector("#elementStats");
|
|
||||||
|
|
||||||
if (elementStats) {
|
|
||||||
const properties = elementStats?.querySelector(".statsItem");
|
|
||||||
return (
|
|
||||||
properties?.querySelector?.(
|
|
||||||
`.drag-input-container[data-testid="${label}"]`,
|
|
||||||
) || null
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
|
|
||||||
describe("elbow arrow routing", () => {
|
describe("elbow arrow routing", () => {
|
||||||
it("can properly generate orthogonal arrow points", () => {
|
it("can properly generate orthogonal arrow points", () => {
|
||||||
const scene = new Scene();
|
const scene = new Scene();
|
||||||
@ -193,7 +178,7 @@ describe("elbow arrow ui", () => {
|
|||||||
|
|
||||||
mouse.click(51, 51);
|
mouse.click(51, 51);
|
||||||
|
|
||||||
const inputAngle = getStatsProperty("A")?.querySelector(
|
const inputAngle = UI.queryStatsProperty("A")?.querySelector(
|
||||||
".drag-input",
|
".drag-input",
|
||||||
) as HTMLInputElement;
|
) as HTMLInputElement;
|
||||||
UI.updateInput(inputAngle, String("40"));
|
UI.updateInput(inputAngle, String("40"));
|
||||||
|
@ -271,6 +271,7 @@ export { MainMenu };
|
|||||||
export { useDevice } from "./components/App";
|
export { useDevice } from "./components/App";
|
||||||
export { WelcomeScreen };
|
export { WelcomeScreen };
|
||||||
export { LiveCollaborationTrigger };
|
export { LiveCollaborationTrigger };
|
||||||
|
export { Stats } from "./components/Stats";
|
||||||
|
|
||||||
export { DefaultSidebar } from "./components/DefaultSidebar";
|
export { DefaultSidebar } from "./components/DefaultSidebar";
|
||||||
export { TTDDialog } from "./components/TTDDialog/TTDDialog";
|
export { TTDDialog } from "./components/TTDDialog/TTDDialog";
|
||||||
|
@ -462,16 +462,15 @@
|
|||||||
},
|
},
|
||||||
"stats": {
|
"stats": {
|
||||||
"angle": "Angle",
|
"angle": "Angle",
|
||||||
"element": "Element",
|
"shapes": "Shapes",
|
||||||
"elements": "Elements",
|
|
||||||
"height": "Height",
|
"height": "Height",
|
||||||
"scene": "Scene",
|
"scene": "Scene",
|
||||||
"selected": "Selected",
|
"selected": "Selected",
|
||||||
"storage": "Storage",
|
"storage": "Storage",
|
||||||
"fullTitle": "Stats & Element properties",
|
"fullTitle": "Canvas & Shape properties",
|
||||||
"title": "Stats",
|
"title": "Properties",
|
||||||
"generalStats": "General stats",
|
"generalStats": "General",
|
||||||
"elementProperties": "Element properties",
|
"elementProperties": "Shape properties",
|
||||||
"total": "Total",
|
"total": "Total",
|
||||||
"version": "Version",
|
"version": "Version",
|
||||||
"versionCopy": "Click to copy",
|
"versionCopy": "Click to copy",
|
||||||
|
@ -579,7 +579,23 @@ export class UI {
|
|||||||
|
|
||||||
static queryStats = () => {
|
static queryStats = () => {
|
||||||
return GlobalTestState.renderResult.container.querySelector(
|
return GlobalTestState.renderResult.container.querySelector(
|
||||||
".Stats",
|
".exc-stats",
|
||||||
) as HTMLElement | null;
|
) as HTMLElement | null;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
static queryStatsProperty = (label: string) => {
|
||||||
|
const elementStats = UI.queryStats()?.querySelector("#elementStats");
|
||||||
|
|
||||||
|
expect(elementStats).not.toBeNull();
|
||||||
|
|
||||||
|
if (elementStats) {
|
||||||
|
return (
|
||||||
|
elementStats?.querySelector(
|
||||||
|
`.exc-stats__row .drag-input-container[data-testid="${label}"]`,
|
||||||
|
) || null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user