Compare commits

..

No commits in common. "5ff0c2272417e5363ad1af852de973a4c5ced1b2" and "19279174e679ff97443b1887b7bf636eaf092140" have entirely different histories.

4 changed files with 27 additions and 21 deletions

View File

@ -5,7 +5,6 @@ import { Button, Space } from 'antd';
import type { ResizeHandle } from './types'; import type { ResizeHandle } from './types';
import { rectFromPoints } from './geometry'; import { rectFromPoints } from './geometry';
import type { ContextMenuState } from './ContextMenu'; import type { ContextMenuState } from './ContextMenu';
import { selectionKeyOf } from './selection';
export interface CanvasProps { export interface CanvasProps {
screen: Screen; screen: Screen;
@ -41,6 +40,12 @@ function isPrimaryButton(e: React.PointerEvent | PointerEvent): boolean {
return (e as PointerEvent).buttons === 1 || (e as PointerEvent).button === 0; return (e as PointerEvent).buttons === 1 || (e as PointerEvent).button === 0;
} }
function selectionKeyOf(ids: string[]): string {
// Keep stable regardless of selection order.
// This avoids false mismatches between context-menu state and reducer state.
return [...ids].sort().join('|');
}
export function Canvas(props: CanvasProps) { export function Canvas(props: CanvasProps) {
const ref = useRef<HTMLDivElement | null>(null); const ref = useRef<HTMLDivElement | null>(null);
const [box, setBox] = useState<{ x1: number; y1: number; x2: number; y2: number } | null>(null); const [box, setBox] = useState<{ x1: number; y1: number; x2: number; y2: number } | null>(null);
@ -436,18 +441,18 @@ function NodeView(props: {
boxSizing: 'border-box', boxSizing: 'border-box',
padding: `${node.props.paddingY ?? 0}px ${node.props.paddingX ?? 0}px`, padding: `${node.props.paddingY ?? 0}px ${node.props.paddingX ?? 0}px`,
overflow: 'hidden', overflow: 'hidden',
}}
>
<span
style={{
whiteSpace: 'pre-wrap',
fontSize: node.props.fontSize ?? 24,
color: node.props.color ?? '#fff',
fontWeight: node.props.fontWeight ?? 400,
letterSpacing: `${node.props.letterSpacing ?? 0}px`,
writingMode: writingModeStyle,
cursor: node.props.link ? 'pointer' : 'default',
}} }}
>
<span
style={{
whiteSpace: 'pre-wrap',
fontSize: node.props.fontSize ?? 24,
color: node.props.color ?? '#fff',
fontWeight: node.props.fontWeight ?? 400,
letterSpacing: `${node.props.letterSpacing ?? 0}px`,
writingMode: writingModeStyle,
cursor: node.props.link ? 'pointer' : 'default',
}}
onClick={() => { onClick={() => {
if (!node.props.link) return; if (!node.props.link) return;
const head = node.props.linkHead ?? 'http://'; const head = node.props.linkHead ?? 'http://';

View File

@ -1,7 +1,11 @@
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react'; import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
import { Typography } from 'antd'; import { Typography } from 'antd';
import { selectionKeyOf } from './selection'; function selectionKeyOf(ids: string[]): string {
// Keep stable regardless of selection order.
// This avoids false mismatches between context-menu state and reducer state.
return [...ids].sort().join('|');
}
export type ContextMenuState = export type ContextMenuState =
| { | {

View File

@ -14,7 +14,6 @@ import { bindEditorHotkeys } from './hotkeys';
import { Canvas } from './Canvas'; import { Canvas } from './Canvas';
import { ContextMenu, type ContextMenuState } from './ContextMenu'; import { ContextMenu, type ContextMenuState } from './ContextMenu';
import { Inspector } from './Inspector'; import { Inspector } from './Inspector';
import { selectionKeyOf } from './selection';
import { createInitialState, editorReducer, exportScreenJSON } from './store'; import { createInitialState, editorReducer, exportScreenJSON } from './store';
const { Header, Sider, Content } = Layout; const { Header, Sider, Content } = Layout;
@ -87,7 +86,10 @@ export function EditorApp() {
const closeContextMenu = useCallback(() => setCtxMenu(null), []); const closeContextMenu = useCallback(() => setCtxMenu(null), []);
// selectionKeyOf imported from ./selection const selectionKeyOf = useCallback((ids: string[]) => {
// Keep stable regardless of ordering.
return [...ids].sort().join('|');
}, []);
const ctxMenuSyncedRef = useRef(false); const ctxMenuSyncedRef = useRef(false);
@ -101,7 +103,7 @@ export function EditorApp() {
} }
dispatch(action); dispatch(action);
}, },
[ctxMenu, dispatch, state.selection.ids], [ctxMenu, dispatch, selectionKeyOf, state.selection.ids],
); );
useEffect(() => { useEffect(() => {

View File

@ -1,5 +0,0 @@
export function selectionKeyOf(ids: string[]): string {
// Keep stable regardless of selection order.
// This avoids false mismatches between context-menu state and reducer state.
return [...ids].sort().join('|');
}