file & variable renaming

This commit is contained in:
dwelle 2025-04-30 15:42:27 +02:00
parent 3dc24a9437
commit e636f84bb6
6 changed files with 111 additions and 138 deletions

View file

@ -119,7 +119,7 @@ export const CLASSES = {
SHAPE_ACTIONS_MENU: "App-menu__left", SHAPE_ACTIONS_MENU: "App-menu__left",
ZOOM_ACTIONS: "zoom-actions", ZOOM_ACTIONS: "zoom-actions",
SEARCH_MENU_INPUT_WRAPPER: "layer-ui__search-inputWrapper", SEARCH_MENU_INPUT_WRAPPER: "layer-ui__search-inputWrapper",
SHAPE_SWITCH_PANEL_CLASSNAME: "ShapeSwitch__Panel", CONVERT_ELEMENT_TYPE_POPUP: "ConvertElementTypePopup",
}; };
export const CJK_HAND_DRAWN_FALLBACK_FONT = "Xiaolai"; export const CJK_HAND_DRAWN_FALLBACK_FONT = "Xiaolai";

View file

@ -1,9 +1,9 @@
import type { ExcalidrawElement } from "@excalidraw/element/types"; import type { ExcalidrawElement } from "@excalidraw/element/types";
import { import {
getSwitchCategoryFromElements, getConversionTypeFromElements,
shapeSwitchAtom, convertElementTypePopupAtom,
} from "../components/ShapeSwitch"; } from "../components/ConvertElementTypePopup";
import { editorJotaiStore } from "../editor-jotai"; import { editorJotaiStore } from "../editor-jotai";
import { CaptureUpdateAction } from "../store"; import { CaptureUpdateAction } from "../store";
@ -20,7 +20,7 @@ export const actionToggleShapeSwitch = register({
}, },
keywords: ["change", "switch", "swap"], keywords: ["change", "switch", "swap"],
perform(elements, appState, _, app) { perform(elements, appState, _, app) {
editorJotaiStore.set(shapeSwitchAtom, { editorJotaiStore.set(convertElementTypePopupAtom, {
type: "panel", type: "panel",
}); });
@ -30,5 +30,5 @@ export const actionToggleShapeSwitch = register({
}, },
checked: (appState) => appState.gridModeEnabled, checked: (appState) => appState.gridModeEnabled,
predicate: (elements, appState, props) => predicate: (elements, appState, props) =>
getSwitchCategoryFromElements(elements as ExcalidrawElement[]) !== null, getConversionTypeFromElements(elements as ExcalidrawElement[]) !== null,
}); });

View file

@ -468,11 +468,11 @@ import { LassoTrail } from "../lasso";
import { EraserTrail } from "../eraser"; import { EraserTrail } from "../eraser";
import ShapeSwitch, { import ConvertElementTypePopup, {
getSwitchCategoryFromElements, getConversionTypeFromElements,
shapeSwitchAtom, convertElementTypePopupAtom,
switchShapes, convertElementTypes,
} from "./ShapeSwitch"; } from "./ConvertElementTypePopup";
import { activeConfirmDialogAtom } from "./ActiveConfirmDialog"; import { activeConfirmDialogAtom } from "./ActiveConfirmDialog";
import BraveMeasureTextError from "./BraveMeasureTextError"; import BraveMeasureTextError from "./BraveMeasureTextError";
@ -1599,7 +1599,7 @@ class App extends React.Component<AppProps, AppState> {
const firstSelectedElement = selectedElements[0]; const firstSelectedElement = selectedElements[0];
const showShapeSwitchPanel = const showShapeSwitchPanel =
editorJotaiStore.get(shapeSwitchAtom)?.type === "panel"; editorJotaiStore.get(convertElementTypePopupAtom)?.type === "panel";
return ( return (
<div <div
@ -1875,7 +1875,9 @@ class App extends React.Component<AppProps, AppState> {
/> />
)} )}
{this.renderFrameNames()} {this.renderFrameNames()}
{showShapeSwitchPanel && <ShapeSwitch app={this} />} {showShapeSwitchPanel && (
<ConvertElementTypePopup app={this} />
)}
</ExcalidrawActionManagerContext.Provider> </ExcalidrawActionManagerContext.Provider>
{this.renderEmbeddables()} {this.renderEmbeddables()}
</ExcalidrawElementsContext.Provider> </ExcalidrawElementsContext.Provider>
@ -4178,31 +4180,33 @@ class App extends React.Component<AppProps, AppState> {
// Shape switching // Shape switching
if (event.key === KEYS.ESCAPE) { if (event.key === KEYS.ESCAPE) {
this.updateEditorAtom(shapeSwitchAtom, null); this.updateEditorAtom(convertElementTypePopupAtom, null);
} else if ( } else if (
event.key === KEYS.TAB && event.key === KEYS.TAB &&
(document.activeElement === this.excalidrawContainerRef?.current || (document.activeElement === this.excalidrawContainerRef?.current ||
document.activeElement?.classList.contains( document.activeElement?.classList.contains(
CLASSES.SHAPE_SWITCH_PANEL_CLASSNAME, CLASSES.CONVERT_ELEMENT_TYPE_POPUP,
)) ))
) { ) {
event.preventDefault(); event.preventDefault();
const switchCategory = const conversionType =
getSwitchCategoryFromElements(selectedElements); getConversionTypeFromElements(selectedElements);
if (editorJotaiStore.get(shapeSwitchAtom)?.type === "panel") { if (
editorJotaiStore.get(convertElementTypePopupAtom)?.type === "panel"
) {
if ( if (
switchShapes(this, { convertElementTypes(this, {
switchCategory, conversionType,
direction: event.shiftKey ? "left" : "right", direction: event.shiftKey ? "left" : "right",
}) })
) { ) {
this.store.shouldCaptureIncrement(); this.store.shouldCaptureIncrement();
} }
} }
if (switchCategory) { if (conversionType) {
this.updateEditorAtom(shapeSwitchAtom, { this.updateEditorAtom(convertElementTypePopupAtom, {
type: "panel", type: "panel",
}); });
} }
@ -6418,8 +6422,8 @@ class App extends React.Component<AppProps, AppState> {
this.updateEditorAtom(searchItemInFocusAtom, null); this.updateEditorAtom(searchItemInFocusAtom, null);
} }
if (editorJotaiStore.get(shapeSwitchAtom)) { if (editorJotaiStore.get(convertElementTypePopupAtom)) {
this.updateEditorAtom(shapeSwitchAtom, null); this.updateEditorAtom(convertElementTypePopupAtom, null);
} }
// since contextMenu options are potentially evaluated on each render, // since contextMenu options are potentially evaluated on each render,

View file

@ -0,0 +1,18 @@
@import "../css//variables.module.scss";
.excalidraw {
.ConvertElementTypePopup {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.2rem;
border-radius: 0.5rem;
background: var(--island-bg-color);
box-shadow: var(--shadow-island);
padding: 0.5rem;
&:focus {
outline: none;
}
}
}

View file

@ -76,7 +76,7 @@ import {
import { trackEvent } from "../analytics"; import { trackEvent } from "../analytics";
import { atom, editorJotaiStore, useSetAtom } from "../editor-jotai"; import { atom, editorJotaiStore, useSetAtom } from "../editor-jotai";
import "./ShapeSwitch.scss"; import "./ConvertElementTypePopup.scss";
import { ToolButton } from "./ToolButton"; import { ToolButton } from "./ToolButton";
import { import {
DiamondIcon, DiamondIcon,
@ -124,12 +124,12 @@ const isConvertibleLinearType = (
elementType === "arrow" || elementType === "arrow" ||
CONVERTIBLE_LINEAR_TYPES.has(elementType as ConvertibleLinearTypes); CONVERTIBLE_LINEAR_TYPES.has(elementType as ConvertibleLinearTypes);
export const shapeSwitchAtom = atom<{ export const convertElementTypePopupAtom = atom<{
type: "panel"; type: "panel";
} | null>(null); } | null>(null);
// NOTE doesn't need to be an atom. Review once we integrate with properties panel. // NOTE doesn't need to be an atom. Review once we integrate with properties panel.
export const shapeSwitchFontSizeAtom = atom<{ export const fontSize_conversionCacheAtom = atom<{
[id: string]: { [id: string]: {
fontSize: number; fontSize: number;
elementType: ConvertibleGenericTypes; elementType: ConvertibleGenericTypes;
@ -137,7 +137,7 @@ export const shapeSwitchFontSizeAtom = atom<{
} | null>(null); } | null>(null);
// NOTE doesn't need to be an atom. Review once we integrate with properties panel. // NOTE doesn't need to be an atom. Review once we integrate with properties panel.
export const shapeSwitchLinearAtom = atom<{ export const linearElement_conversionCacheAtom = atom<{
[id: string]: { [id: string]: {
properties: properties:
| Partial<ExcalidrawLinearElement> | Partial<ExcalidrawLinearElement>
@ -146,40 +146,40 @@ export const shapeSwitchLinearAtom = atom<{
}; };
} | null>(null); } | null>(null);
const ShapeSwitch = ({ app }: { app: App }) => { const ConvertElementTypePopup = ({ app }: { app: App }) => {
const setShapeSwitchFontSize = useSetAtom(shapeSwitchFontSizeAtom); const setFontSizeCache = useSetAtom(fontSize_conversionCacheAtom);
const setShapeSwitchLinear = useSetAtom(shapeSwitchLinearAtom); const setLinearElementCache = useSetAtom(linearElement_conversionCacheAtom);
const selectedElements = app.scene.getSelectedElements(app.state); const selectedElements = app.scene.getSelectedElements(app.state);
const elementsCategoryRef = useRef<SwitchShapeCategory>(null); const elementsCategoryRef = useRef<ConversionType>(null);
// close shape switch panel if selecting different "types" of elements // close shape switch panel if selecting different "types" of elements
useEffect(() => { useEffect(() => {
if (selectedElements.length === 0) { if (selectedElements.length === 0) {
app.updateEditorAtom(shapeSwitchAtom, null); app.updateEditorAtom(convertElementTypePopupAtom, null);
return; return;
} }
const switchCategory = getSwitchCategoryFromElements(selectedElements); const conversionType = getConversionTypeFromElements(selectedElements);
if (switchCategory && !elementsCategoryRef.current) { if (conversionType && !elementsCategoryRef.current) {
elementsCategoryRef.current = switchCategory; elementsCategoryRef.current = conversionType;
} else if ( } else if (
(elementsCategoryRef.current && !switchCategory) || (elementsCategoryRef.current && !conversionType) ||
(elementsCategoryRef.current && (elementsCategoryRef.current &&
switchCategory !== elementsCategoryRef.current) conversionType !== elementsCategoryRef.current)
) { ) {
app.updateEditorAtom(shapeSwitchAtom, null); app.updateEditorAtom(convertElementTypePopupAtom, null);
elementsCategoryRef.current = null; elementsCategoryRef.current = null;
} }
}, [selectedElements, app]); }, [selectedElements, app]);
useEffect(() => { useEffect(() => {
return () => { return () => {
setShapeSwitchFontSize(null); setFontSizeCache(null);
setShapeSwitchLinear(null); setLinearElementCache(null);
}; };
}, [setShapeSwitchFontSize, setShapeSwitchLinear]); }, [setFontSizeCache, setLinearElementCache]);
return <Panel app={app} elements={selectedElements} />; return <Panel app={app} elements={selectedElements} />;
}; };
@ -191,15 +191,15 @@ const Panel = ({
app: App; app: App;
elements: ExcalidrawElement[]; elements: ExcalidrawElement[];
}) => { }) => {
const switchCategory = getSwitchCategoryFromElements(elements); const conversionType = getConversionTypeFromElements(elements);
const generic = switchCategory === "generic"; const generic = conversionType === "generic";
const linear = switchCategory === "linear"; const linear = conversionType === "linear";
const genericElements = useMemo(() => { const genericElements = useMemo(() => {
return generic ? getGenericSwitchableElements(elements) : []; return generic ? filterGenericConvetibleElements(elements) : [];
}, [generic, elements]); }, [generic, elements]);
const linearElements = useMemo(() => { const linearElements = useMemo(() => {
return linear ? getLinearSwitchableElements(elements) : []; return linear ? filterLinearConvertibleElements(elements) : [];
}, [linear, elements]); }, [linear, elements]);
const sameType = generic const sameType = generic
@ -257,7 +257,7 @@ const Panel = ({
}, [genericElements, linearElements, app.scene, app.state]); }, [genericElements, linearElements, app.scene, app.state]);
useEffect(() => { useEffect(() => {
if (editorJotaiStore.get(shapeSwitchLinearAtom)) { if (editorJotaiStore.get(linearElement_conversionCacheAtom)) {
return; return;
} }
@ -274,8 +274,8 @@ const Panel = ({
? getElbowArrowProperties(linearElement) ? getElbowArrowProperties(linearElement)
: {}; : {};
editorJotaiStore.set(shapeSwitchLinearAtom, { editorJotaiStore.set(linearElement_conversionCacheAtom, {
...editorJotaiStore.get(shapeSwitchLinearAtom), ...editorJotaiStore.get(linearElement_conversionCacheAtom),
[linearElement.id]: { [linearElement.id]: {
properties: cachedProperties, properties: cachedProperties,
initialType, initialType,
@ -285,7 +285,7 @@ const Panel = ({
}, [linearElements]); }, [linearElements]);
useEffect(() => { useEffect(() => {
if (editorJotaiStore.get(shapeSwitchFontSizeAtom)) { if (editorJotaiStore.get(fontSize_conversionCacheAtom)) {
return; return;
} }
@ -295,8 +295,8 @@ const Panel = ({
app.scene.getNonDeletedElementsMap(), app.scene.getNonDeletedElementsMap(),
); );
if (boundText) { if (boundText) {
editorJotaiStore.set(shapeSwitchFontSizeAtom, { editorJotaiStore.set(fontSize_conversionCacheAtom, {
...editorJotaiStore.get(shapeSwitchFontSizeAtom), ...editorJotaiStore.get(fontSize_conversionCacheAtom),
[element.id]: { [element.id]: {
fontSize: boundText.fontSize, fontSize: boundText.fontSize,
elementType: element.type as ConvertibleGenericTypes, elementType: element.type as ConvertibleGenericTypes,
@ -335,7 +335,7 @@ const Panel = ({
left: `${panelPosition.x - app.state.offsetLeft - GAP_HORIZONTAL}px`, left: `${panelPosition.x - app.state.offsetLeft - GAP_HORIZONTAL}px`,
zIndex: 2, zIndex: 2,
}} }}
className={CLASSES.SHAPE_SWITCH_PANEL_CLASSNAME} className={CLASSES.CONVERT_ELEMENT_TYPE_POPUP}
> >
{SHAPES.map(([type, icon]) => { {SHAPES.map(([type, icon]) => {
const isSelected = const isSelected =
@ -350,17 +350,17 @@ const Panel = ({
type="radio" type="radio"
icon={icon} icon={icon}
checked={isSelected} checked={isSelected}
name="shape-switch-option" name="convertElementType-option"
title={type} title={type}
keyBindingLabel={""} keyBindingLabel={""}
aria-label={type} aria-label={type}
data-testid={`toolbar-${type}`} data-testid={`toolbar-${type}`}
onChange={() => { onChange={() => {
if (app.state.activeTool.type !== type) { if (app.state.activeTool.type !== type) {
trackEvent("shape-switch", type, "ui"); trackEvent("convertElementType", type, "ui");
} }
switchShapes(app, { convertElementTypes(app, {
switchCategory, conversionType,
nextType: type as nextType: type as
| ConvertibleGenericTypes | ConvertibleGenericTypes
| ConvertibleLinearTypes, | ConvertibleLinearTypes,
@ -420,21 +420,21 @@ export const adjustBoundTextSize = (
redrawTextBoundingBox(boundText, container, scene); redrawTextBoundingBox(boundText, container, scene);
}; };
type SwitchShapeCategory = "generic" | "linear" | null; type ConversionType = "generic" | "linear" | null;
export const switchShapes = ( export const convertElementTypes = (
app: App, app: App,
{ {
switchCategory, conversionType,
nextType, nextType,
direction = "right", direction = "right",
}: { }: {
switchCategory: SwitchShapeCategory; conversionType: ConversionType;
nextType?: ConvertibleTypes; nextType?: ConvertibleTypes;
direction?: "left" | "right"; direction?: "left" | "right";
}, },
): boolean => { ): boolean => {
if (!switchCategory) { if (!conversionType) {
return false; return false;
} }
@ -447,16 +447,16 @@ export const switchShapes = (
const advancement = direction === "right" ? 1 : -1; const advancement = direction === "right" ? 1 : -1;
if (switchCategory === "generic") { if (conversionType === "generic") {
const selectedGenericSwitchableElements = const convertibleGenericElements =
getGenericSwitchableElements(selectedElements); filterGenericConvetibleElements(selectedElements);
const sameType = selectedGenericSwitchableElements.every( const sameType = convertibleGenericElements.every(
(element) => element.type === selectedGenericSwitchableElements[0].type, (element) => element.type === convertibleGenericElements[0].type,
); );
const index = sameType const index = sameType
? GENERIC_TYPES.indexOf(selectedGenericSwitchableElements[0].type) ? GENERIC_TYPES.indexOf(convertibleGenericElements[0].type)
: -1; : -1;
nextType = nextType =
@ -468,7 +468,7 @@ export const switchShapes = (
if (nextType && isConvertibleGenericType(nextType)) { if (nextType && isConvertibleGenericType(nextType)) {
const convertedElements: Record<string, ExcalidrawElement> = {}; const convertedElements: Record<string, ExcalidrawElement> = {};
for (const element of selectedGenericSwitchableElements) { for (const element of convertibleGenericElements) {
const convertedElement = convertElementType(element, nextType, app); const convertedElement = convertElementType(element, nextType, app);
convertedElements[convertedElement.id] = convertedElement; convertedElements[convertedElement.id] = convertedElement;
} }
@ -492,12 +492,12 @@ export const switchShapes = (
); );
if (boundText) { if (boundText) {
if ( if (
editorJotaiStore.get(shapeSwitchFontSizeAtom)?.[element.id] editorJotaiStore.get(fontSize_conversionCacheAtom)?.[element.id]
?.elementType === nextType ?.elementType === nextType
) { ) {
mutateElement(boundText, app.scene.getNonDeletedElementsMap(), { mutateElement(boundText, app.scene.getNonDeletedElementsMap(), {
fontSize: fontSize:
editorJotaiStore.get(shapeSwitchFontSizeAtom)?.[element.id] editorJotaiStore.get(fontSize_conversionCacheAtom)?.[element.id]
?.fontSize ?? boundText.fontSize, ?.fontSize ?? boundText.fontSize,
}); });
} }
@ -521,13 +521,13 @@ export const switchShapes = (
} }
} }
if (switchCategory === "linear") { if (conversionType === "linear") {
const selectedLinearSwitchableElements = getLinearSwitchableElements( const convertibleLinearElements = filterLinearConvertibleElements(
selectedElements, selectedElements,
) as ExcalidrawLinearElement[]; ) as ExcalidrawLinearElement[];
const arrowType = getArrowType(selectedLinearSwitchableElements[0]); const arrowType = getArrowType(convertibleLinearElements[0]);
const sameType = selectedLinearSwitchableElements.every( const sameType = convertibleLinearElements.every(
(element) => getArrowType(element) === arrowType, (element) => getArrowType(element) === arrowType,
); );
@ -540,9 +540,11 @@ export const switchShapes = (
if (nextType && isConvertibleLinearType(nextType)) { if (nextType && isConvertibleLinearType(nextType)) {
const convertedElements: Record<string, ExcalidrawElement> = {}; const convertedElements: Record<string, ExcalidrawElement> = {};
for (const element of selectedLinearSwitchableElements) { for (const element of convertibleLinearElements) {
const { properties, initialType } = const { properties, initialType } =
editorJotaiStore.get(shapeSwitchLinearAtom)?.[element.id] || {}; editorJotaiStore.get(linearElement_conversionCacheAtom)?.[
element.id
] || {};
// If the initial type is not elbow, and when we switch to elbow, // If the initial type is not elbow, and when we switch to elbow,
// the linear line might be "bent" and the points would likely be different. // the linear line might be "bent" and the points would likely be different.
@ -597,9 +599,9 @@ export const switchShapes = (
app.scene.replaceAllElements(nextElements); app.scene.replaceAllElements(nextElements);
for (const element of Object.values(convertedElements)) { for (const element of Object.values(convertedElements)) {
const cachedLinear = editorJotaiStore.get(shapeSwitchLinearAtom)?.[ const cachedLinear = editorJotaiStore.get(
element.id linearElement_conversionCacheAtom,
]; )?.[element.id];
if (cachedLinear) { if (cachedLinear) {
const { properties, initialType } = cachedLinear; const { properties, initialType } = cachedLinear;
@ -642,7 +644,7 @@ export const switchShapes = (
} }
} }
} }
const convertedSelectedLinearElements = getLinearSwitchableElements( const convertedSelectedLinearElements = filterLinearConvertibleElements(
app.scene.getSelectedElements(app.state), app.scene.getSelectedElements(app.state),
); );
@ -664,9 +666,9 @@ export const switchShapes = (
return true; return true;
}; };
export const getSwitchCategoryFromElements = ( export const getConversionTypeFromElements = (
elements: ExcalidrawElement[], elements: ExcalidrawElement[],
): SwitchShapeCategory => { ): ConversionType => {
if (elements.length === 0) { if (elements.length === 0) {
return null; return null;
} }
@ -776,14 +778,14 @@ const getElbowArrowProperties = (
return {}; return {};
}; };
const getGenericSwitchableElements = (elements: ExcalidrawElement[]) => const filterGenericConvetibleElements = (elements: ExcalidrawElement[]) =>
elements.filter((element) => isConvertibleGenericType(element.type)) as Array< elements.filter((element) => isConvertibleGenericType(element.type)) as Array<
| ExcalidrawRectangleElement | ExcalidrawRectangleElement
| ExcalidrawDiamondElement | ExcalidrawDiamondElement
| ExcalidrawEllipseElement | ExcalidrawEllipseElement
>; >;
const getLinearSwitchableElements = (elements: ExcalidrawElement[]) => const filterLinearConvertibleElements = (elements: ExcalidrawElement[]) =>
elements.filter((element) => elements.filter((element) =>
isEligibleLinearElement(element), isEligibleLinearElement(element),
) as ExcalidrawLinearElement[]; ) as ExcalidrawLinearElement[];
@ -1049,4 +1051,4 @@ const isValidConversion = (
return false; return false;
}; };
export default ShapeSwitch; export default ConvertElementTypePopup;

View file

@ -1,51 +0,0 @@
@import "../css//variables.module.scss";
@keyframes disappear {
from {
opacity: 1;
}
to {
opacity: 0;
}
}
.excalidraw {
.ShapeSwitch__Hint {
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
.key {
background: var(--color-primary-light);
padding: 0.4rem 0.8rem;
font-weight: bold;
border-radius: 0.5rem;
box-shadow: 0 0 0.5rem rgba(0, 0, 0, 0.1);
}
.text {
margin-left: 0.5rem;
}
}
.animation {
opacity: 1;
animation: disappear 2s ease-out;
}
.ShapeSwitch__Panel {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 0.2rem;
border-radius: 0.5rem;
background: var(--island-bg-color);
box-shadow: var(--shadow-island);
padding: 0.5rem;
&:focus {
outline: none;
}
}
}