refactor: improve goView import and editor selection
This commit is contained in:
parent
01b7a3a722
commit
903b0da44a
@ -156,7 +156,11 @@ export function Canvas(props: CanvasProps) {
|
|||||||
// (Left-click on background already clears unless additive via box-select.)
|
// (Left-click on background already clears unless additive via box-select.)
|
||||||
}
|
}
|
||||||
|
|
||||||
props.onOpenContextMenu({ clientX: e.clientX, clientY: e.clientY, worldX: p.x, worldY: p.y, targetId });
|
props.onOpenContextMenu(
|
||||||
|
targetId
|
||||||
|
? { kind: 'node', clientX: e.clientX, clientY: e.clientY, worldX: p.x, worldY: p.y, targetId }
|
||||||
|
: { kind: 'canvas', clientX: e.clientX, clientY: e.clientY, worldX: p.x, worldY: p.y },
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const onBackgroundPointerDown = (e: React.PointerEvent) => {
|
const onBackgroundPointerDown = (e: React.PointerEvent) => {
|
||||||
|
|||||||
@ -1,22 +1,32 @@
|
|||||||
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
import { useEffect, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
||||||
import { Typography } from 'antd';
|
import { Typography } from 'antd';
|
||||||
|
|
||||||
export type ContextMenuState = {
|
export type ContextMenuState =
|
||||||
|
| {
|
||||||
|
kind: 'canvas';
|
||||||
clientX: number;
|
clientX: number;
|
||||||
clientY: number;
|
clientY: number;
|
||||||
worldX: number;
|
worldX: number;
|
||||||
worldY: number;
|
worldY: number;
|
||||||
targetId?: string;
|
}
|
||||||
|
| {
|
||||||
|
kind: 'node';
|
||||||
|
clientX: number;
|
||||||
|
clientY: number;
|
||||||
|
worldX: number;
|
||||||
|
worldY: number;
|
||||||
|
targetId: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function ContextMenu(props: {
|
export function ContextMenu(props: {
|
||||||
state: ContextMenuState | null;
|
state: ContextMenuState | null;
|
||||||
selectionIds: string[];
|
selectionIds: string[];
|
||||||
selectionAllLocked: boolean;
|
selectionAllLocked: boolean;
|
||||||
selectionSomeLocked?: boolean;
|
selectionSomeLocked: boolean;
|
||||||
|
selectionHasUnlocked: boolean;
|
||||||
selectionAllHidden: boolean;
|
selectionAllHidden: boolean;
|
||||||
selectionSomeHidden?: boolean;
|
selectionSomeHidden: boolean;
|
||||||
hasAnyNodes?: boolean;
|
hasAnyNodes: boolean;
|
||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
onAddTextAt: (x: number, y: number) => void;
|
onAddTextAt: (x: number, y: number) => void;
|
||||||
onSelectSingle?: (id?: string) => void;
|
onSelectSingle?: (id?: string) => void;
|
||||||
@ -88,8 +98,10 @@ export function ContextMenu(props: {
|
|||||||
if (!ctx || !position) return null;
|
if (!ctx || !position) return null;
|
||||||
|
|
||||||
const hasSelection = props.selectionIds.length > 0;
|
const hasSelection = props.selectionIds.length > 0;
|
||||||
const hasTarget = !!ctx.targetId;
|
const canModifySelection = hasSelection && props.selectionHasUnlocked;
|
||||||
const targetInSelection = !!ctx.targetId && props.selectionIds.includes(ctx.targetId);
|
const hasTarget = ctx.kind === 'node';
|
||||||
|
const targetId = hasTarget ? ctx.targetId : undefined;
|
||||||
|
const targetInSelection = !!targetId && props.selectionIds.includes(targetId);
|
||||||
const canSelectSingle = !!props.onSelectSingle;
|
const canSelectSingle = !!props.onSelectSingle;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -124,7 +136,7 @@ export function ContextMenu(props: {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
label="Select Only"
|
label="Select Only"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onSelectSingle?.(ctx.targetId);
|
props.onSelectSingle?.(targetId);
|
||||||
onClose();
|
onClose();
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@ -154,7 +166,7 @@ export function ContextMenu(props: {
|
|||||||
|
|
||||||
<MenuItem
|
<MenuItem
|
||||||
label="Duplicate"
|
label="Duplicate"
|
||||||
disabled={!hasSelection || !props.onDuplicateSelected}
|
disabled={!canModifySelection || !props.onDuplicateSelected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onDuplicateSelected?.();
|
props.onDuplicateSelected?.();
|
||||||
onClose();
|
onClose();
|
||||||
@ -212,7 +224,7 @@ export function ContextMenu(props: {
|
|||||||
<MenuItem
|
<MenuItem
|
||||||
label="Delete"
|
label="Delete"
|
||||||
danger
|
danger
|
||||||
disabled={!hasSelection || !props.onDeleteSelected}
|
disabled={!canModifySelection || !props.onDeleteSelected}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
props.onDeleteSelected?.();
|
props.onDeleteSelected?.();
|
||||||
onClose();
|
onClose();
|
||||||
|
|||||||
@ -55,6 +55,7 @@ export function EditorApp() {
|
|||||||
|
|
||||||
const selectionAllLocked = selection.length > 0 && selection.every((n) => n.locked);
|
const selectionAllLocked = selection.length > 0 && selection.every((n) => n.locked);
|
||||||
const selectionSomeLocked = selection.length > 0 && selection.some((n) => n.locked) && !selectionAllLocked;
|
const selectionSomeLocked = selection.length > 0 && selection.some((n) => n.locked) && !selectionAllLocked;
|
||||||
|
const selectionHasUnlocked = selection.length > 0 && selection.some((n) => !n.locked);
|
||||||
|
|
||||||
const selectionAllHidden = selection.length > 0 && selection.every((n) => n.hidden);
|
const selectionAllHidden = selection.length > 0 && selection.every((n) => n.hidden);
|
||||||
const selectionSomeHidden = selection.length > 0 && selection.some((n) => n.hidden) && !selectionAllHidden;
|
const selectionSomeHidden = selection.length > 0 && selection.some((n) => n.hidden) && !selectionAllHidden;
|
||||||
@ -475,6 +476,7 @@ export function EditorApp() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
setCtxMenu({
|
setCtxMenu({
|
||||||
|
kind: 'node',
|
||||||
clientX: e.clientX,
|
clientX: e.clientX,
|
||||||
clientY: e.clientY,
|
clientY: e.clientY,
|
||||||
worldX: node.rect.x + node.rect.w / 2,
|
worldX: node.rect.x + node.rect.w / 2,
|
||||||
@ -512,6 +514,7 @@ export function EditorApp() {
|
|||||||
selectionIds={state.selection.ids}
|
selectionIds={state.selection.ids}
|
||||||
selectionAllLocked={selectionAllLocked}
|
selectionAllLocked={selectionAllLocked}
|
||||||
selectionSomeLocked={selectionSomeLocked}
|
selectionSomeLocked={selectionSomeLocked}
|
||||||
|
selectionHasUnlocked={selectionHasUnlocked}
|
||||||
selectionAllHidden={selectionAllHidden}
|
selectionAllHidden={selectionAllHidden}
|
||||||
selectionSomeHidden={selectionSomeHidden}
|
selectionSomeHidden={selectionSomeHidden}
|
||||||
hasAnyNodes={state.doc.screen.nodes.length > 0}
|
hasAnyNodes={state.doc.screen.nodes.length > 0}
|
||||||
|
|||||||
@ -287,15 +287,19 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
|||||||
case 'deleteSelected': {
|
case 'deleteSelected': {
|
||||||
if (!state.selection.ids.length) return state;
|
if (!state.selection.ids.length) return state;
|
||||||
const ids = new Set(state.selection.ids);
|
const ids = new Set(state.selection.ids);
|
||||||
|
const deletableIds = new Set(
|
||||||
|
state.doc.screen.nodes.filter((n) => ids.has(n.id) && !n.locked).map((n) => n.id),
|
||||||
|
);
|
||||||
|
if (!deletableIds.size) return state;
|
||||||
return {
|
return {
|
||||||
...historyPush(state),
|
...historyPush(state),
|
||||||
doc: {
|
doc: {
|
||||||
screen: {
|
screen: {
|
||||||
...state.doc.screen,
|
...state.doc.screen,
|
||||||
nodes: state.doc.screen.nodes.filter((n) => !ids.has(n.id)),
|
nodes: state.doc.screen.nodes.filter((n) => !deletableIds.has(n.id)),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
selection: { ids: [] },
|
selection: { ids: state.selection.ids.filter((id) => !deletableIds.has(id)) },
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -328,7 +332,7 @@ export function editorReducer(state: EditorRuntimeState, action: EditorAction):
|
|||||||
const clones: WidgetNode[] = [];
|
const clones: WidgetNode[] = [];
|
||||||
|
|
||||||
for (const n of state.doc.screen.nodes) {
|
for (const n of state.doc.screen.nodes) {
|
||||||
if (!ids.has(n.id)) continue;
|
if (!ids.has(n.id) || n.locked) continue;
|
||||||
const id = `${n.type}_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
const id = `${n.type}_${Date.now()}_${Math.random().toString(16).slice(2)}`;
|
||||||
clones.push({
|
clones.push({
|
||||||
...n,
|
...n,
|
||||||
|
|||||||
@ -521,17 +521,19 @@ export function convertGoViewProjectToScreen(input: GoViewProjectLike | GoViewSt
|
|||||||
nodes: [],
|
nodes: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const componentList =
|
const componentListRaw =
|
||||||
(input as GoViewStorageLike).componentList ??
|
(input as GoViewStorageLike).componentList ??
|
||||||
(input as GoViewProjectLike).componentList ??
|
(input as GoViewProjectLike).componentList ??
|
||||||
(data?.componentList as GoViewComponentLike[] | undefined) ??
|
(data?.componentList as GoViewComponentLike[] | undefined) ??
|
||||||
(state?.componentList as GoViewComponentLike[] | undefined) ??
|
(state?.componentList as GoViewComponentLike[] | undefined) ??
|
||||||
(project?.componentList as GoViewComponentLike[] | undefined) ??
|
(project?.componentList as GoViewComponentLike[] | undefined) ??
|
||||||
[];
|
[];
|
||||||
|
const componentList = Array.isArray(componentListRaw) ? componentListRaw : [];
|
||||||
|
|
||||||
const nodes: Array<TextWidgetNode | ImageWidgetNode | IframeWidgetNode | VideoWidgetNode> = [];
|
const nodes: Array<TextWidgetNode | ImageWidgetNode | IframeWidgetNode | VideoWidgetNode> = [];
|
||||||
|
|
||||||
for (const raw of componentList) {
|
for (const raw of componentList) {
|
||||||
|
if (!raw || typeof raw !== 'object') continue;
|
||||||
const c = unwrapComponent(raw);
|
const c = unwrapComponent(raw);
|
||||||
const option = normalizeOption(optionOf(c));
|
const option = normalizeOption(optionOf(c));
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user