editor: add grid + snap toggles
This commit is contained in:
parent
a9e6d4b0d0
commit
2edcb214ad
@ -14,6 +14,7 @@ export interface CanvasProps {
|
||||
scale: number;
|
||||
panX: number;
|
||||
panY: number;
|
||||
gridEnabled: boolean;
|
||||
guides: { xs: number[]; ys: number[] };
|
||||
onSelectSingle(id?: string): void;
|
||||
onToggleSelect(id: string): void;
|
||||
@ -276,6 +277,19 @@ export function Canvas(props: CanvasProps) {
|
||||
position: 'relative',
|
||||
}}
|
||||
>
|
||||
{props.gridEnabled ? (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
inset: 0,
|
||||
backgroundImage:
|
||||
'linear-gradient(to right, rgba(255,255,255,0.06) 1px, transparent 1px), linear-gradient(to bottom, rgba(255,255,255,0.06) 1px, transparent 1px)',
|
||||
backgroundSize: '20px 20px',
|
||||
pointerEvents: 'none',
|
||||
}}
|
||||
/>
|
||||
) : null}
|
||||
|
||||
{/* guides (snap lines) */}
|
||||
{props.guides.xs.map((x) => (
|
||||
<div
|
||||
|
||||
@ -213,6 +213,31 @@ export function EditorApp() {
|
||||
</Space>
|
||||
|
||||
<Space size={8}>
|
||||
<Button
|
||||
size="small"
|
||||
type={state.canvas.gridEnabled ? 'default' : 'text'}
|
||||
onClick={() => dispatch({ type: 'toggleGrid' })}
|
||||
style={
|
||||
state.canvas.gridEnabled
|
||||
? { background: 'rgba(59,130,246,0.16)', color: '#e2e8f0', border: '1px solid rgba(59,130,246,0.35)' }
|
||||
: { color: '#94a3b8' }
|
||||
}
|
||||
>
|
||||
Grid
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
type={state.canvas.snapEnabled ? 'default' : 'text'}
|
||||
onClick={() => dispatch({ type: 'toggleSnap' })}
|
||||
style={
|
||||
state.canvas.snapEnabled
|
||||
? { background: 'rgba(34,197,94,0.12)', color: '#e2e8f0', border: '1px solid rgba(34,197,94,0.30)' }
|
||||
: { color: '#94a3b8' }
|
||||
}
|
||||
>
|
||||
Snap
|
||||
</Button>
|
||||
<Divider type="vertical" style={{ borderColor: 'rgba(255,255,255,0.10)' }} />
|
||||
<Button size="small" style={{ background: 'rgba(255,255,255,0.06)', color: '#e2e8f0', border: 'none' }}>
|
||||
Preview
|
||||
</Button>
|
||||
@ -446,6 +471,7 @@ export function EditorApp() {
|
||||
scale={state.canvas.scale}
|
||||
panX={state.canvas.panX}
|
||||
panY={state.canvas.panY}
|
||||
gridEnabled={state.canvas.gridEnabled}
|
||||
guides={state.canvas.guides}
|
||||
onSelectSingle={(id) => dispatch({ type: 'selectSingle', id })}
|
||||
onToggleSelect={(id) => dispatch({ type: 'toggleSelect', id })}
|
||||
|
||||
@ -34,6 +34,8 @@ export type EditorAction =
|
||||
| { type: 'setScale'; scale: number }
|
||||
| { type: 'panBy'; dx: number; dy: number }
|
||||
| { type: 'zoomAt'; scale: number; anchor: { x: number; y: number } }
|
||||
| { type: 'toggleGrid' }
|
||||
| { type: 'toggleSnap' }
|
||||
| { type: 'beginPan'; start: { screenX: number; screenY: number } }
|
||||
| { type: 'updatePan'; current: { screenX: number; screenY: number } }
|
||||
| { type: 'endPan' }
|
||||
@ -271,6 +273,8 @@ export function createInitialState(): EditorRuntimeState {
|
||||
scale: 1,
|
||||
panX: 0,
|
||||
panY: 0,
|
||||
gridEnabled: false,
|
||||
snapEnabled: true,
|
||||
guides: { xs: [], ys: [] },
|
||||
isDragging: false,
|
||||
isPanning: false,
|
||||
@ -366,6 +370,28 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
||||
};
|
||||
}
|
||||
|
||||
case 'toggleGrid': {
|
||||
return {
|
||||
...state,
|
||||
canvas: {
|
||||
...state.canvas,
|
||||
gridEnabled: !state.canvas.gridEnabled,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'toggleSnap': {
|
||||
// Clearing guides prevents stale guide lines when turning snap off mid-drag.
|
||||
return {
|
||||
...state,
|
||||
canvas: {
|
||||
...state.canvas,
|
||||
snapEnabled: !state.canvas.snapEnabled,
|
||||
guides: { xs: [], ys: [] },
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
case 'updateWidgetRect': {
|
||||
const target = state.doc.screen.nodes.find((n) => n.id === action.id);
|
||||
if (!target) return state;
|
||||
@ -874,8 +900,8 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
||||
|
||||
const bounds = action.bounds;
|
||||
|
||||
// goView-like: only snap when a single node is selected.
|
||||
const canSnap = state.selection.ids.length === 1;
|
||||
// goView-like: only snap when enabled and a single node is selected.
|
||||
const canSnap = state.canvas.snapEnabled && state.selection.ids.length === 1;
|
||||
const movingId = state.selection.ids[0];
|
||||
|
||||
const others = canSnap
|
||||
@ -1058,8 +1084,8 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
||||
h: Math.max(0, newH),
|
||||
};
|
||||
|
||||
// goView-like: only snap when resizing a single selected node.
|
||||
const canSnap = state.selection.ids.length === 1;
|
||||
// goView-like: only snap when enabled and resizing a single selected node.
|
||||
const canSnap = state.canvas.snapEnabled && state.selection.ids.length === 1;
|
||||
const movingId = drag.targetId;
|
||||
|
||||
const others = canSnap
|
||||
|
||||
@ -16,6 +16,8 @@ export interface EditorCanvasState {
|
||||
scale: number;
|
||||
panX: number;
|
||||
panY: number;
|
||||
gridEnabled: boolean;
|
||||
snapEnabled: boolean;
|
||||
guides: {
|
||||
xs: number[];
|
||||
ys: number[];
|
||||
|
||||
Loading…
Reference in New Issue
Block a user