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 type { NonDeletedExcalidrawElement } from "../packages/excalidraw/element/types";
|
||||
import type { UIAppState } from "../packages/excalidraw/types";
|
||||
import { Stats } from "../packages/excalidraw";
|
||||
|
||||
type StorageSizes = { scene: number; total: number };
|
||||
|
||||
@ -51,39 +52,33 @@ const CustomStats = (props: Props) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<tr>
|
||||
<th colSpan={2}>{t("stats.storage")}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("stats.scene")}</td>
|
||||
<td>{nFormatter(storageSizes.scene, 1)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>{t("stats.total")}</td>
|
||||
<td>{nFormatter(storageSizes.total, 1)}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th colSpan={2}>{t("stats.version")}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<td
|
||||
colSpan={2}
|
||||
style={{ textAlign: "center", cursor: "pointer" }}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await copyTextToSystemClipboard(getVersion());
|
||||
props.setToast(t("toast.copyToClipboard"));
|
||||
} catch {}
|
||||
}}
|
||||
title={t("stats.versionCopy")}
|
||||
>
|
||||
{timestamp}
|
||||
<br />
|
||||
{hash}
|
||||
</td>
|
||||
</tr>
|
||||
</>
|
||||
<Stats.StatsRows order={-1}>
|
||||
<Stats.StatsRow heading>{t("stats.version")}</Stats.StatsRow>
|
||||
<Stats.StatsRow
|
||||
style={{ textAlign: "center", cursor: "pointer" }}
|
||||
onClick={async () => {
|
||||
try {
|
||||
await copyTextToSystemClipboard(getVersion());
|
||||
props.setToast(t("toast.copyToClipboard"));
|
||||
} catch {}
|
||||
}}
|
||||
title={t("stats.versionCopy")}
|
||||
>
|
||||
{timestamp}
|
||||
<br />
|
||||
{hash}
|
||||
</Stats.StatsRow>
|
||||
|
||||
<Stats.StatsRow heading>{t("stats.storage")}</Stats.StatsRow>
|
||||
<Stats.StatsRow columns={2}>
|
||||
<div>{t("stats.scene")}</div>
|
||||
<div>{nFormatter(storageSizes.scene, 1)}</div>
|
||||
</Stats.StatsRow>
|
||||
<Stats.StatsRow columns={2}>
|
||||
<div>{t("stats.total")}</div>
|
||||
<div>{nFormatter(storageSizes.total, 1)}</div>
|
||||
</Stats.StatsRow>
|
||||
</Stats.StatsRows>
|
||||
);
|
||||
};
|
||||
|
||||
|
@ -39,6 +39,8 @@ Please add the latest change on the top under the correct section.
|
||||
|
||||
### 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)
|
||||
|
||||
| | Before `commitToHistory` | After `storeAction` | Notes |
|
||||
|
@ -27,99 +27,6 @@
|
||||
& > * {
|
||||
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 {
|
||||
|
@ -31,7 +31,11 @@ const Collapsible = ({
|
||||
{label}
|
||||
<InlineIcon icon={open ? collapseUpIcon : collapseDownIcon} />
|
||||
</div>
|
||||
{open && <>{children}</>}
|
||||
{open && (
|
||||
<div style={{ display: "flex", flexDirection: "column" }}>
|
||||
{children}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -5,7 +5,7 @@
|
||||
|
||||
&:focus-within {
|
||||
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);
|
||||
|
||||
: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"] & {
|
||||
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-left: 0;
|
||||
}
|
||||
@ -55,11 +55,11 @@
|
||||
letter-spacing: 0.4px;
|
||||
|
||||
: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"] & {
|
||||
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-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 Dimension from "./Dimension";
|
||||
import Angle from "./Angle";
|
||||
|
||||
import FontSize from "./FontSize";
|
||||
import MultiDimension from "./MultiDimension";
|
||||
import { elementsAreInSameGroup } from "../../groups";
|
||||
@ -22,6 +21,9 @@ import { useExcalidrawAppState, useExcalidrawSetAppState } from "../App";
|
||||
import { getAtomicUnits } from "./utils";
|
||||
import { STATS_PANELS } from "../../constants";
|
||||
import { isElbowArrow } from "../../element/typeChecks";
|
||||
import clsx from "clsx";
|
||||
|
||||
import "./Stats.scss";
|
||||
|
||||
interface StatsProps {
|
||||
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(
|
||||
({
|
||||
scene,
|
||||
@ -106,7 +152,7 @@ export const StatsInner = memo(
|
||||
}, [selectedElements, appState]);
|
||||
|
||||
return (
|
||||
<div className="Stats">
|
||||
<div className="exc-stats">
|
||||
<Island padding={3}>
|
||||
<div className="title">
|
||||
<h2>{t("stats.title")}</h2>
|
||||
@ -121,7 +167,6 @@ export const StatsInner = memo(
|
||||
openTrigger={() =>
|
||||
setAppState((state) => {
|
||||
return {
|
||||
...state,
|
||||
stats: {
|
||||
open: true,
|
||||
panels: state.stats.panels ^ STATS_PANELS.generalStats,
|
||||
@ -130,26 +175,23 @@ export const StatsInner = memo(
|
||||
})
|
||||
}
|
||||
>
|
||||
<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>
|
||||
{renderCustomStats?.(elements, appState)}
|
||||
</tbody>
|
||||
</table>
|
||||
<StatsRows>
|
||||
<StatsRow heading>{t("stats.scene")}</StatsRow>
|
||||
<StatsRow columns={2}>
|
||||
<div>{t("stats.shapes")}</div>
|
||||
<div>{elements.length}</div>
|
||||
</StatsRow>
|
||||
<StatsRow columns={2}>
|
||||
<div>{t("stats.width")}</div>
|
||||
<div>{sceneDimension.width}</div>
|
||||
</StatsRow>
|
||||
<StatsRow columns={2}>
|
||||
<div>{t("stats.height")}</div>
|
||||
<div>{sceneDimension.height}</div>
|
||||
</StatsRow>
|
||||
</StatsRows>
|
||||
|
||||
{renderCustomStats?.(elements, appState)}
|
||||
</Collapsible>
|
||||
|
||||
{selectedElements.length > 0 && (
|
||||
@ -167,7 +209,6 @@ export const StatsInner = memo(
|
||||
openTrigger={() =>
|
||||
setAppState((state) => {
|
||||
return {
|
||||
...state,
|
||||
stats: {
|
||||
open: true,
|
||||
panels:
|
||||
@ -177,117 +218,139 @@ export const StatsInner = memo(
|
||||
})
|
||||
}
|
||||
>
|
||||
{singleElement && (
|
||||
<div className="sectionContent">
|
||||
<div className="elementType">
|
||||
{t(`element.${singleElement.type}`)}
|
||||
</div>
|
||||
<StatsRows>
|
||||
{singleElement && (
|
||||
<>
|
||||
<StatsRow heading data-testid="stats-element-type">
|
||||
{t(`element.${singleElement.type}`)}
|
||||
</StatsRow>
|
||||
|
||||
<div className="statsItem">
|
||||
<Position
|
||||
element={singleElement}
|
||||
property="x"
|
||||
elementsMap={elementsMap}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
<Position
|
||||
element={singleElement}
|
||||
property="y"
|
||||
elementsMap={elementsMap}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
<Dimension
|
||||
property="width"
|
||||
element={singleElement}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
<Dimension
|
||||
property="height"
|
||||
element={singleElement}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
{!isElbowArrow(singleElement) && (
|
||||
<Angle
|
||||
property="angle"
|
||||
<StatsRow>
|
||||
<Position
|
||||
element={singleElement}
|
||||
property="x"
|
||||
elementsMap={elementsMap}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</StatsRow>
|
||||
<StatsRow>
|
||||
<Position
|
||||
element={singleElement}
|
||||
property="y"
|
||||
elementsMap={elementsMap}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</StatsRow>
|
||||
<StatsRow>
|
||||
<Dimension
|
||||
property="width"
|
||||
element={singleElement}
|
||||
scene={scene}
|
||||
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
|
||||
property="fontSize"
|
||||
element={singleElement}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<StatsRow>
|
||||
<FontSize
|
||||
property="fontSize"
|
||||
element={singleElement}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</StatsRow>
|
||||
</>
|
||||
)}
|
||||
|
||||
{multipleElements && (
|
||||
<div className="sectionContent">
|
||||
{elementsAreInSameGroup(multipleElements) && (
|
||||
<div className="elementType">{t("element.group")}</div>
|
||||
)}
|
||||
{multipleElements && (
|
||||
<>
|
||||
{elementsAreInSameGroup(multipleElements) && (
|
||||
<StatsRow heading>{t("element.group")}</StatsRow>
|
||||
)}
|
||||
|
||||
<div className="elementsCount">
|
||||
<div>{t("stats.elements")}</div>
|
||||
<div>{selectedElements.length}</div>
|
||||
</div>
|
||||
<StatsRow columns={2} style={{ margin: "0.3125rem 0" }}>
|
||||
<div>{t("stats.shapes")}</div>
|
||||
<div>{selectedElements.length}</div>
|
||||
</StatsRow>
|
||||
|
||||
<div className="statsItem">
|
||||
<MultiPosition
|
||||
property="x"
|
||||
elements={multipleElements}
|
||||
elementsMap={elementsMap}
|
||||
atomicUnits={atomicUnits}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
<MultiPosition
|
||||
property="y"
|
||||
elements={multipleElements}
|
||||
elementsMap={elementsMap}
|
||||
atomicUnits={atomicUnits}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
<MultiDimension
|
||||
property="width"
|
||||
elements={multipleElements}
|
||||
elementsMap={elementsMap}
|
||||
atomicUnits={atomicUnits}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
<MultiDimension
|
||||
property="height"
|
||||
elements={multipleElements}
|
||||
elementsMap={elementsMap}
|
||||
atomicUnits={atomicUnits}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
<MultiAngle
|
||||
property="angle"
|
||||
elements={multipleElements}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
<MultiFontSize
|
||||
property="fontSize"
|
||||
elements={multipleElements}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
elementsMap={elementsMap}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
<StatsRow>
|
||||
<MultiPosition
|
||||
property="x"
|
||||
elements={multipleElements}
|
||||
elementsMap={elementsMap}
|
||||
atomicUnits={atomicUnits}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</StatsRow>
|
||||
<StatsRow>
|
||||
<MultiPosition
|
||||
property="y"
|
||||
elements={multipleElements}
|
||||
elementsMap={elementsMap}
|
||||
atomicUnits={atomicUnits}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</StatsRow>
|
||||
<StatsRow>
|
||||
<MultiDimension
|
||||
property="width"
|
||||
elements={multipleElements}
|
||||
elementsMap={elementsMap}
|
||||
atomicUnits={atomicUnits}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</StatsRow>
|
||||
<StatsRow>
|
||||
<MultiDimension
|
||||
property="height"
|
||||
elements={multipleElements}
|
||||
elementsMap={elementsMap}
|
||||
atomicUnits={atomicUnits}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</StatsRow>
|
||||
<StatsRow>
|
||||
<MultiAngle
|
||||
property="angle"
|
||||
elements={multipleElements}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
/>
|
||||
</StatsRow>
|
||||
<StatsRow>
|
||||
<MultiFontSize
|
||||
property="fontSize"
|
||||
elements={multipleElements}
|
||||
scene={scene}
|
||||
appState={appState}
|
||||
elementsMap={elementsMap}
|
||||
/>
|
||||
</StatsRow>
|
||||
</>
|
||||
)}
|
||||
</StatsRows>
|
||||
</Collapsible>
|
||||
</div>
|
||||
)}
|
||||
|
@ -32,21 +32,6 @@ const renderStaticScene = vi.spyOn(StaticScene, "renderStaticScene");
|
||||
let stats: HTMLElement | null = 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 = (
|
||||
element: ExcalidrawElement,
|
||||
property: "x" | "y" | "width" | "height" | "angle" | "fontSize",
|
||||
@ -54,7 +39,7 @@ const testInputProperty = (
|
||||
initialValue: number,
|
||||
nextValue: number,
|
||||
) => {
|
||||
const input = getStatsProperty(label)?.querySelector(
|
||||
const input = UI.queryStatsProperty(label)?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(input).toBeDefined();
|
||||
@ -136,7 +121,7 @@ describe("binding with linear elements", () => {
|
||||
|
||||
it("should remain bound to linear element on small position change", async () => {
|
||||
const linear = h.elements[1] as ExcalidrawLinearElement;
|
||||
const inputX = getStatsProperty("X")?.querySelector(
|
||||
const inputX = UI.queryStatsProperty("X")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
@ -148,7 +133,7 @@ describe("binding with linear elements", () => {
|
||||
|
||||
it("should remain bound to linear element on small angle change", async () => {
|
||||
const linear = h.elements[1] as ExcalidrawLinearElement;
|
||||
const inputAngle = getStatsProperty("A")?.querySelector(
|
||||
const inputAngle = UI.queryStatsProperty("A")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
@ -159,7 +144,7 @@ describe("binding with linear elements", () => {
|
||||
|
||||
it("should unbind linear element on large position change", async () => {
|
||||
const linear = h.elements[1] as ExcalidrawLinearElement;
|
||||
const inputX = getStatsProperty("X")?.querySelector(
|
||||
const inputX = UI.queryStatsProperty("X")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
@ -171,7 +156,7 @@ describe("binding with linear elements", () => {
|
||||
|
||||
it("should remain bound to linear element on small angle change", async () => {
|
||||
const linear = h.elements[1] as ExcalidrawLinearElement;
|
||||
const inputAngle = getStatsProperty("A")?.querySelector(
|
||||
const inputAngle = UI.queryStatsProperty("A")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
@ -225,18 +210,14 @@ describe("stats for a generic element", () => {
|
||||
expect(title?.lastChild?.nodeValue)?.toBe(t("stats.elementProperties"));
|
||||
|
||||
// element type
|
||||
const elementType = elementStats?.querySelector(".elementType");
|
||||
const elementType = queryByTestId(elementStats!, "stats-element-type");
|
||||
expect(elementType).toBeDefined();
|
||||
expect(elementType?.lastChild?.nodeValue).toBe(t("element.rectangle"));
|
||||
|
||||
// properties
|
||||
const properties = elementStats?.querySelector(".statsItem");
|
||||
expect(properties?.childNodes).toBeDefined();
|
||||
["X", "Y", "W", "H", "A"].forEach((label) => () => {
|
||||
expect(
|
||||
properties?.querySelector?.(
|
||||
`.drag-input-container[data-testid="${label}"]`,
|
||||
),
|
||||
stats!.querySelector?.(`.drag-input-container[data-testid="${label}"]`),
|
||||
).toBeDefined();
|
||||
});
|
||||
});
|
||||
@ -257,7 +238,7 @@ describe("stats for a generic element", () => {
|
||||
const rectangle = h.elements[0];
|
||||
const rectangleId = rectangle.id;
|
||||
|
||||
const input = getStatsProperty("W")?.querySelector(
|
||||
const input = UI.queryStatsProperty("W")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(input).toBeDefined();
|
||||
@ -287,11 +268,11 @@ describe("stats for a generic element", () => {
|
||||
rectangle.angle,
|
||||
);
|
||||
|
||||
const xInput = getStatsProperty("X")?.querySelector(
|
||||
const xInput = UI.queryStatsProperty("X")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
const yInput = getStatsProperty("Y")?.querySelector(
|
||||
const yInput = UI.queryStatsProperty("Y")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
@ -417,7 +398,7 @@ describe("stats for a non-generic element", () => {
|
||||
elementStats = stats?.querySelector("#elementStats");
|
||||
|
||||
// can change font size
|
||||
const input = getStatsProperty("F")?.querySelector(
|
||||
const input = UI.queryStatsProperty("F")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(input).toBeDefined();
|
||||
@ -426,9 +407,9 @@ describe("stats for a non-generic element", () => {
|
||||
expect(text.fontSize).toBe(36);
|
||||
|
||||
// cannot change width or height
|
||||
const width = getStatsProperty("W")?.querySelector(".drag-input");
|
||||
const width = UI.queryStatsProperty("W")?.querySelector(".drag-input");
|
||||
expect(width).toBeUndefined();
|
||||
const height = getStatsProperty("H")?.querySelector(".drag-input");
|
||||
const height = UI.queryStatsProperty("H")?.querySelector(".drag-input");
|
||||
expect(height).toBeUndefined();
|
||||
|
||||
// min font size is 4
|
||||
@ -456,7 +437,7 @@ describe("stats for a non-generic element", () => {
|
||||
expect(elementStats).toBeDefined();
|
||||
|
||||
// cannot change angle
|
||||
const angle = getStatsProperty("A")?.querySelector(".drag-input");
|
||||
const angle = UI.queryStatsProperty("A")?.querySelector(".drag-input");
|
||||
expect(angle).toBeUndefined();
|
||||
|
||||
// can change width or height
|
||||
@ -506,7 +487,7 @@ describe("stats for a non-generic element", () => {
|
||||
API.setElements([container, text]);
|
||||
|
||||
API.setSelectedElements([container]);
|
||||
const fontSize = getStatsProperty("F")?.querySelector(
|
||||
const fontSize = UI.queryStatsProperty("F")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(fontSize).toBeDefined();
|
||||
@ -570,15 +551,15 @@ describe("stats for multiple elements", () => {
|
||||
|
||||
elementStats = stats?.querySelector("#elementStats");
|
||||
|
||||
const width = getStatsProperty("W")?.querySelector(
|
||||
const width = UI.queryStatsProperty("W")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(width?.value).toBe("Mixed");
|
||||
const height = getStatsProperty("H")?.querySelector(
|
||||
const height = UI.queryStatsProperty("H")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(height?.value).toBe("Mixed");
|
||||
const angle = getStatsProperty("A")?.querySelector(
|
||||
const angle = UI.queryStatsProperty("A")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(angle.value).toBe("0");
|
||||
@ -629,25 +610,25 @@ describe("stats for multiple elements", () => {
|
||||
|
||||
elementStats = stats?.querySelector("#elementStats");
|
||||
|
||||
const width = getStatsProperty("W")?.querySelector(
|
||||
const width = UI.queryStatsProperty("W")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(width).toBeDefined();
|
||||
expect(width.value).toBe("Mixed");
|
||||
|
||||
const height = getStatsProperty("H")?.querySelector(
|
||||
const height = UI.queryStatsProperty("H")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(height).toBeDefined();
|
||||
expect(height.value).toBe("Mixed");
|
||||
|
||||
const angle = getStatsProperty("A")?.querySelector(
|
||||
const angle = UI.queryStatsProperty("A")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(angle).toBeDefined();
|
||||
expect(angle.value).toBe("0");
|
||||
|
||||
const fontSize = getStatsProperty("F")?.querySelector(
|
||||
const fontSize = UI.queryStatsProperty("F")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(fontSize).toBeDefined();
|
||||
@ -692,7 +673,7 @@ describe("stats for multiple elements", () => {
|
||||
|
||||
elementStats = stats?.querySelector("#elementStats");
|
||||
|
||||
const x = getStatsProperty("X")?.querySelector(
|
||||
const x = UI.queryStatsProperty("X")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
@ -705,7 +686,7 @@ describe("stats for multiple elements", () => {
|
||||
expect(h.elements[1].x).toBe(400);
|
||||
expect(x.value).toBe("300");
|
||||
|
||||
const y = getStatsProperty("Y")?.querySelector(
|
||||
const y = UI.queryStatsProperty("Y")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
|
||||
@ -718,13 +699,13 @@ describe("stats for multiple elements", () => {
|
||||
expect(h.elements[1].y).toBe(300);
|
||||
expect(y.value).toBe("200");
|
||||
|
||||
const width = getStatsProperty("W")?.querySelector(
|
||||
const width = UI.queryStatsProperty("W")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(width).toBeDefined();
|
||||
expect(Number(width.value)).toBe(200);
|
||||
|
||||
const height = getStatsProperty("H")?.querySelector(
|
||||
const height = UI.queryStatsProperty("H")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
expect(height).toBeDefined();
|
||||
|
@ -22,21 +22,6 @@ const { h } = window;
|
||||
|
||||
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", () => {
|
||||
it("can properly generate orthogonal arrow points", () => {
|
||||
const scene = new Scene();
|
||||
@ -193,7 +178,7 @@ describe("elbow arrow ui", () => {
|
||||
|
||||
mouse.click(51, 51);
|
||||
|
||||
const inputAngle = getStatsProperty("A")?.querySelector(
|
||||
const inputAngle = UI.queryStatsProperty("A")?.querySelector(
|
||||
".drag-input",
|
||||
) as HTMLInputElement;
|
||||
UI.updateInput(inputAngle, String("40"));
|
||||
|
@ -271,6 +271,7 @@ export { MainMenu };
|
||||
export { useDevice } from "./components/App";
|
||||
export { WelcomeScreen };
|
||||
export { LiveCollaborationTrigger };
|
||||
export { Stats } from "./components/Stats";
|
||||
|
||||
export { DefaultSidebar } from "./components/DefaultSidebar";
|
||||
export { TTDDialog } from "./components/TTDDialog/TTDDialog";
|
||||
|
@ -462,16 +462,15 @@
|
||||
},
|
||||
"stats": {
|
||||
"angle": "Angle",
|
||||
"element": "Element",
|
||||
"elements": "Elements",
|
||||
"shapes": "Shapes",
|
||||
"height": "Height",
|
||||
"scene": "Scene",
|
||||
"selected": "Selected",
|
||||
"storage": "Storage",
|
||||
"fullTitle": "Stats & Element properties",
|
||||
"title": "Stats",
|
||||
"generalStats": "General stats",
|
||||
"elementProperties": "Element properties",
|
||||
"fullTitle": "Canvas & Shape properties",
|
||||
"title": "Properties",
|
||||
"generalStats": "General",
|
||||
"elementProperties": "Shape properties",
|
||||
"total": "Total",
|
||||
"version": "Version",
|
||||
"versionCopy": "Click to copy",
|
||||
|
@ -579,7 +579,23 @@ export class UI {
|
||||
|
||||
static queryStats = () => {
|
||||
return GlobalTestState.renderResult.container.querySelector(
|
||||
".Stats",
|
||||
".exc-stats",
|
||||
) 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