让节点删除默认落到选中节点所在的树
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 historyRedo = session.history.redo;
|
||||
const selectedNodeId = session.input.selectedNodeId;
|
||||
const activeImageId = session.input.activeImageId;
|
||||
const deleteNode = session.input.deleteNode;
|
||||
|
||||
useEffect(() => {
|
||||
const onKeyDown = (event: KeyboardEvent) =>
|
||||
handleUiEditorKeyDown(event, {
|
||||
selectedNodeId,
|
||||
activeImageId,
|
||||
deleteNode,
|
||||
historyUndo,
|
||||
historyRedo,
|
||||
});
|
||||
window.addEventListener('keydown', onKeyDown);
|
||||
return () => window.removeEventListener('keydown', onKeyDown);
|
||||
}, [activeImageId, deleteNode, historyRedo, historyUndo, selectedNodeId]);
|
||||
}, [deleteNode, historyRedo, historyUndo, selectedNodeId]);
|
||||
|
||||
async function save(afterReturn = false) {
|
||||
const result = await session.save.save();
|
||||
|
||||
@@ -5,8 +5,8 @@ type DeleteResult = { ok: boolean } | undefined;
|
||||
|
||||
export type UiEditorKeyboardActions = {
|
||||
selectedNodeId: NodeId | null;
|
||||
activeImageId: UIDesignImageId | null;
|
||||
deleteNode: (nodeId: NodeId, treeId: UIDesignImageId) => DeleteResult;
|
||||
/** 省略 treeId 时由调用方按选中节点定位所在 UI 树。 */
|
||||
deleteNode: (nodeId: NodeId, treeId?: UIDesignImageId | null) => DeleteResult;
|
||||
historyUndo: () => boolean;
|
||||
historyRedo: () => boolean;
|
||||
};
|
||||
@@ -50,11 +50,8 @@ export function handleUiEditorKeyDown(
|
||||
!event.shiftKey &&
|
||||
!isInteractiveTarget(event.target)
|
||||
) {
|
||||
if (actions.selectedNodeId && actions.activeImageId) {
|
||||
const result = actions.deleteNode(
|
||||
actions.selectedNodeId,
|
||||
actions.activeImageId,
|
||||
);
|
||||
if (actions.selectedNodeId) {
|
||||
const result = actions.deleteNode(actions.selectedNodeId);
|
||||
if (result?.ok) {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
|
||||
@@ -875,7 +875,12 @@ export function useUiEditorSession(
|
||||
}
|
||||
|
||||
const deleteNode = useCallback(
|
||||
(nodeId: NodeId, treeId = activeImageId) => {
|
||||
(
|
||||
nodeId: NodeId,
|
||||
// 不给 treeId 时落到选中节点真正所在的树:选中节点可以来自非激活界面图,
|
||||
// 按 activeImageId 找会静默删不掉。
|
||||
treeId = treeForSelectedNode?.src_ui_design ?? activeImageId,
|
||||
) => {
|
||||
if (!treeId) return;
|
||||
const tree = editorUiTrees.find(
|
||||
(candidate) => candidate.src_ui_design === treeId,
|
||||
@@ -892,7 +897,13 @@ export function useUiEditorSession(
|
||||
}
|
||||
return result;
|
||||
},
|
||||
[activeImageId, editorDeleteNode, editorUiTrees, selectedNodeId],
|
||||
[
|
||||
activeImageId,
|
||||
editorDeleteNode,
|
||||
editorUiTrees,
|
||||
selectedNodeId,
|
||||
treeForSelectedNode,
|
||||
],
|
||||
);
|
||||
|
||||
function selectSprite(id: SpriteAssetId) {
|
||||
|
||||
@@ -25,7 +25,6 @@ describe('ui editor keyboard shortcuts', () => {
|
||||
const listener = (event: KeyboardEvent) =>
|
||||
handleUiEditorKeyDown(event, {
|
||||
selectedNodeId: null,
|
||||
activeImageId: null,
|
||||
deleteNode: vi.fn(),
|
||||
historyUndo,
|
||||
historyRedo,
|
||||
@@ -47,7 +46,6 @@ describe('ui editor keyboard shortcuts', () => {
|
||||
const listener = (event: KeyboardEvent) =>
|
||||
handleUiEditorKeyDown(event, {
|
||||
selectedNodeId: null,
|
||||
activeImageId: null,
|
||||
deleteNode: vi.fn(),
|
||||
historyUndo,
|
||||
historyRedo,
|
||||
@@ -70,7 +68,6 @@ describe('ui editor keyboard shortcuts', () => {
|
||||
const listener = (event: KeyboardEvent) =>
|
||||
handleUiEditorKeyDown(event, {
|
||||
selectedNodeId: 'node-1',
|
||||
activeImageId: 'page-1',
|
||||
deleteNode,
|
||||
historyUndo: vi.fn(() => true),
|
||||
historyRedo: vi.fn(() => true),
|
||||
@@ -93,7 +90,6 @@ describe('ui editor keyboard shortcuts', () => {
|
||||
const listener = (event: KeyboardEvent) =>
|
||||
handleUiEditorKeyDown(event, {
|
||||
selectedNodeId: 'node-1',
|
||||
activeImageId: 'page-1',
|
||||
deleteNode,
|
||||
historyUndo: vi.fn(() => true),
|
||||
historyRedo: vi.fn(() => true),
|
||||
@@ -103,7 +99,7 @@ describe('ui editor keyboard shortcuts', () => {
|
||||
fireEvent.click(zoomIn);
|
||||
fireEvent.keyDown(window, { key: 'Delete' });
|
||||
|
||||
expect(deleteNode).toHaveBeenCalledWith('node-1', 'page-1');
|
||||
expect(deleteNode).toHaveBeenCalledWith('node-1');
|
||||
window.removeEventListener('keydown', listener);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -596,6 +596,30 @@ describe('UiEditorPage', () => {
|
||||
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 () => {
|
||||
const { result } = await renderLoadedSession(stateWithPages(['page']));
|
||||
const rootId = 'page-root';
|
||||
|
||||
Reference in New Issue
Block a user