让节点删除默认落到选中节点所在的树
src/view/ui-editor/useUiEditorPage.ts 的 deleteNode 默认 treeId 改为 treeForSelectedNode?.src_ui_design ?? activeImageId:选中节点可以来自非激活界面图,Inspector 删除按钮不给 treeId 时会静默删不掉 src/view/ui-editor/uiEditorKeyboardShortcuts.ts 与 view/ui-editor/index.tsx 去掉键盘删除强制传 activeImageId 的接线,treeId 改为可选,同时删掉已无消费者的 activeImageId 入参 tests/uiEditorPage.test.ts 补「非激活树里的节点默认也能删除」用例,uiEditorKeyboardShortcuts.test.ts 断言改为只传节点 id
This commit is contained in:
@@ -114,21 +114,19 @@ export default function UiEditorPage({
|
|||||||
const historyUndo = session.history.undo;
|
const historyUndo = session.history.undo;
|
||||||
const historyRedo = session.history.redo;
|
const historyRedo = session.history.redo;
|
||||||
const selectedNodeId = session.input.selectedNodeId;
|
const selectedNodeId = session.input.selectedNodeId;
|
||||||
const activeImageId = session.input.activeImageId;
|
|
||||||
const deleteNode = session.input.deleteNode;
|
const deleteNode = session.input.deleteNode;
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const onKeyDown = (event: KeyboardEvent) =>
|
const onKeyDown = (event: KeyboardEvent) =>
|
||||||
handleUiEditorKeyDown(event, {
|
handleUiEditorKeyDown(event, {
|
||||||
selectedNodeId,
|
selectedNodeId,
|
||||||
activeImageId,
|
|
||||||
deleteNode,
|
deleteNode,
|
||||||
historyUndo,
|
historyUndo,
|
||||||
historyRedo,
|
historyRedo,
|
||||||
});
|
});
|
||||||
window.addEventListener('keydown', onKeyDown);
|
window.addEventListener('keydown', onKeyDown);
|
||||||
return () => window.removeEventListener('keydown', onKeyDown);
|
return () => window.removeEventListener('keydown', onKeyDown);
|
||||||
}, [activeImageId, deleteNode, historyRedo, historyUndo, selectedNodeId]);
|
}, [deleteNode, historyRedo, historyUndo, selectedNodeId]);
|
||||||
|
|
||||||
async function save(afterReturn = false) {
|
async function save(afterReturn = false) {
|
||||||
const result = await session.save.save();
|
const result = await session.save.save();
|
||||||
|
|||||||
@@ -5,8 +5,8 @@ type DeleteResult = { ok: boolean } | undefined;
|
|||||||
|
|
||||||
export type UiEditorKeyboardActions = {
|
export type UiEditorKeyboardActions = {
|
||||||
selectedNodeId: NodeId | null;
|
selectedNodeId: NodeId | null;
|
||||||
activeImageId: UIDesignImageId | null;
|
/** 省略 treeId 时由调用方按选中节点定位所在 UI 树。 */
|
||||||
deleteNode: (nodeId: NodeId, treeId: UIDesignImageId) => DeleteResult;
|
deleteNode: (nodeId: NodeId, treeId?: UIDesignImageId | null) => DeleteResult;
|
||||||
historyUndo: () => boolean;
|
historyUndo: () => boolean;
|
||||||
historyRedo: () => boolean;
|
historyRedo: () => boolean;
|
||||||
};
|
};
|
||||||
@@ -50,11 +50,8 @@ export function handleUiEditorKeyDown(
|
|||||||
!event.shiftKey &&
|
!event.shiftKey &&
|
||||||
!isInteractiveTarget(event.target)
|
!isInteractiveTarget(event.target)
|
||||||
) {
|
) {
|
||||||
if (actions.selectedNodeId && actions.activeImageId) {
|
if (actions.selectedNodeId) {
|
||||||
const result = actions.deleteNode(
|
const result = actions.deleteNode(actions.selectedNodeId);
|
||||||
actions.selectedNodeId,
|
|
||||||
actions.activeImageId,
|
|
||||||
);
|
|
||||||
if (result?.ok) {
|
if (result?.ok) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|||||||
@@ -875,7 +875,12 @@ export function useUiEditorSession(
|
|||||||
}
|
}
|
||||||
|
|
||||||
const deleteNode = useCallback(
|
const deleteNode = useCallback(
|
||||||
(nodeId: NodeId, treeId = activeImageId) => {
|
(
|
||||||
|
nodeId: NodeId,
|
||||||
|
// 不给 treeId 时落到选中节点真正所在的树:选中节点可以来自非激活界面图,
|
||||||
|
// 按 activeImageId 找会静默删不掉。
|
||||||
|
treeId = treeForSelectedNode?.src_ui_design ?? activeImageId,
|
||||||
|
) => {
|
||||||
if (!treeId) return;
|
if (!treeId) return;
|
||||||
const tree = editorUiTrees.find(
|
const tree = editorUiTrees.find(
|
||||||
(candidate) => candidate.src_ui_design === treeId,
|
(candidate) => candidate.src_ui_design === treeId,
|
||||||
@@ -892,7 +897,13 @@ export function useUiEditorSession(
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
},
|
},
|
||||||
[activeImageId, editorDeleteNode, editorUiTrees, selectedNodeId],
|
[
|
||||||
|
activeImageId,
|
||||||
|
editorDeleteNode,
|
||||||
|
editorUiTrees,
|
||||||
|
selectedNodeId,
|
||||||
|
treeForSelectedNode,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
function selectSprite(id: SpriteAssetId) {
|
function selectSprite(id: SpriteAssetId) {
|
||||||
|
|||||||
@@ -25,7 +25,6 @@ describe('ui editor keyboard shortcuts', () => {
|
|||||||
const listener = (event: KeyboardEvent) =>
|
const listener = (event: KeyboardEvent) =>
|
||||||
handleUiEditorKeyDown(event, {
|
handleUiEditorKeyDown(event, {
|
||||||
selectedNodeId: null,
|
selectedNodeId: null,
|
||||||
activeImageId: null,
|
|
||||||
deleteNode: vi.fn(),
|
deleteNode: vi.fn(),
|
||||||
historyUndo,
|
historyUndo,
|
||||||
historyRedo,
|
historyRedo,
|
||||||
@@ -47,7 +46,6 @@ describe('ui editor keyboard shortcuts', () => {
|
|||||||
const listener = (event: KeyboardEvent) =>
|
const listener = (event: KeyboardEvent) =>
|
||||||
handleUiEditorKeyDown(event, {
|
handleUiEditorKeyDown(event, {
|
||||||
selectedNodeId: null,
|
selectedNodeId: null,
|
||||||
activeImageId: null,
|
|
||||||
deleteNode: vi.fn(),
|
deleteNode: vi.fn(),
|
||||||
historyUndo,
|
historyUndo,
|
||||||
historyRedo,
|
historyRedo,
|
||||||
@@ -70,7 +68,6 @@ describe('ui editor keyboard shortcuts', () => {
|
|||||||
const listener = (event: KeyboardEvent) =>
|
const listener = (event: KeyboardEvent) =>
|
||||||
handleUiEditorKeyDown(event, {
|
handleUiEditorKeyDown(event, {
|
||||||
selectedNodeId: 'node-1',
|
selectedNodeId: 'node-1',
|
||||||
activeImageId: 'page-1',
|
|
||||||
deleteNode,
|
deleteNode,
|
||||||
historyUndo: vi.fn(() => true),
|
historyUndo: vi.fn(() => true),
|
||||||
historyRedo: vi.fn(() => true),
|
historyRedo: vi.fn(() => true),
|
||||||
@@ -93,7 +90,6 @@ describe('ui editor keyboard shortcuts', () => {
|
|||||||
const listener = (event: KeyboardEvent) =>
|
const listener = (event: KeyboardEvent) =>
|
||||||
handleUiEditorKeyDown(event, {
|
handleUiEditorKeyDown(event, {
|
||||||
selectedNodeId: 'node-1',
|
selectedNodeId: 'node-1',
|
||||||
activeImageId: 'page-1',
|
|
||||||
deleteNode,
|
deleteNode,
|
||||||
historyUndo: vi.fn(() => true),
|
historyUndo: vi.fn(() => true),
|
||||||
historyRedo: vi.fn(() => true),
|
historyRedo: vi.fn(() => true),
|
||||||
@@ -103,7 +99,7 @@ describe('ui editor keyboard shortcuts', () => {
|
|||||||
fireEvent.click(zoomIn);
|
fireEvent.click(zoomIn);
|
||||||
fireEvent.keyDown(window, { key: 'Delete' });
|
fireEvent.keyDown(window, { key: 'Delete' });
|
||||||
|
|
||||||
expect(deleteNode).toHaveBeenCalledWith('node-1', 'page-1');
|
expect(deleteNode).toHaveBeenCalledWith('node-1');
|
||||||
window.removeEventListener('keydown', listener);
|
window.removeEventListener('keydown', listener);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -596,6 +596,30 @@ describe('UiEditorPage', () => {
|
|||||||
expect(result.current.canvas.hiddenNodeIds.size).toBe(0);
|
expect(result.current.canvas.hiddenNodeIds.size).toBe(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('deletes an inspector node from its own tree without an explicit tree id', async () => {
|
||||||
|
const { result } = await renderLoadedSession(
|
||||||
|
stateWithPages(['page-a', 'page-b']),
|
||||||
|
);
|
||||||
|
let otherTreeNodeId: string | undefined;
|
||||||
|
act(() => {
|
||||||
|
const inserted = result.current.input.insertNode('page-b-root', 'page-b');
|
||||||
|
if (inserted?.ok) otherTreeNodeId = inserted.value;
|
||||||
|
});
|
||||||
|
expect(otherTreeNodeId).toBeTruthy();
|
||||||
|
|
||||||
|
// 选中非激活界面图里的节点:Inspector 删除按钮不给 treeId,必须落到它所在的树。
|
||||||
|
act(() => result.current.input.selectDesignImage('page-a'));
|
||||||
|
act(() => result.current.input.selectNode(otherTreeNodeId!));
|
||||||
|
act(() => result.current.inspector.deleteNode(otherTreeNodeId!));
|
||||||
|
|
||||||
|
const otherTree = result.current.canvas.uiTrees.find(
|
||||||
|
(tree) => tree.src_ui_design === 'page-b',
|
||||||
|
);
|
||||||
|
expect(otherTree?.root.children.map((child) => child.id)).not.toContain(
|
||||||
|
otherTreeNodeId,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('clears selection when deleting a node removes the selected descendant', async () => {
|
it('clears selection when deleting a node removes the selected descendant', async () => {
|
||||||
const { result } = await renderLoadedSession(stateWithPages(['page']));
|
const { result } = await renderLoadedSession(stateWithPages(['page']));
|
||||||
const rootId = 'page-root';
|
const rootId = 'page-root';
|
||||||
|
|||||||
Reference in New Issue
Block a user