From cb19c8e47a3060ca38412995fbe66b321168e6bc Mon Sep 17 00:00:00 2001 From: guohao Date: Tue, 9 Sep 2025 15:30:41 +0800 Subject: [PATCH] feat: add way to remove links. --- store/src/DataStore.ts | 24 ++++++++- store/src/types.ts | 3 ++ svelte/demos/cases/LinkDeleteTest.svelte | 20 +++++++ svelte/demos/routes.js | 2 + svelte/src/components/Gantt.svelte | 2 + svelte/src/components/chart/Chart.svelte | 9 ++++ svelte/src/components/chart/Links.svelte | 69 +++++++++++++++++++++++- 7 files changed, 125 insertions(+), 4 deletions(-) create mode 100644 svelte/demos/cases/LinkDeleteTest.svelte diff --git a/store/src/DataStore.ts b/store/src/DataStore.ts index 8981a3e..bb02d91 100644 --- a/store/src/DataStore.ts +++ b/store/src/DataStore.ts @@ -188,6 +188,18 @@ export default class DataStore extends Store { this.setState({ _selected }, ctx); }, }, + // selectedLink + { + in: ["links", "selectedLink"], + out: ["_selectedLink"], + exec: (ctx: TDataConfig) => { + const { links, selectedLink, _links } = this.getState(); + this.setState( + { _selectedLink: selectedLink ? _links.find(l => l.id === selectedLink) || null : null }, + ctx + ); + }, + }, // restore config cellWidth on scale change { in: ["start", "end"], @@ -226,6 +238,9 @@ export default class DataStore extends Store { inBus.on("show-editor", ({ id }: TMethodsConfig["show-editor"]) => { this.setStateAsync({ activeTask: id }); }); + inBus.on("select-link", ({ id }: TMethodsConfig["select-link"]) => { + this.setStateAsync({ selectedLink: id }); + }); inBus.on( "select-task", ({ id, toggle, range, show }: TMethodsConfig["select-task"]) => { @@ -286,9 +301,13 @@ export default class DataStore extends Store { } ); inBus.on("delete-link", ({ id }: TMethodsConfig["delete-link"]) => { - const { links } = this.getState(); + const { links, selectedLink } = this.getState(); links.remove(id); - this.setStateAsync({ links }); + const update: Partial = { links }; + if (selectedLink === id) { + update.selectedLink = null; + } + this.setStateAsync(update); }); inBus.on("update-link", (ev: TMethodsConfig["update-link"]) => { const { links } = this.getState(); @@ -1270,6 +1289,7 @@ export type IDataMethodsConfig = CombineTypes< ["indent-task"]: { id: TID; mode: boolean }; ["show-editor"]: { id: TID }; + ["select-link"]: { id: TID | null }; ["add-link"]: { id?: TID; link: Partial }; ["update-link"]: { id: TID; link: Partial }; ["delete-link"]: { id: TID }; diff --git a/store/src/types.ts b/store/src/types.ts index 7ea8ef9..b5b0077 100644 --- a/store/src/types.ts +++ b/store/src/types.ts @@ -107,6 +107,7 @@ export interface IMarker { export interface IDataConfig { selected?: TID[]; activeTask?: TID; + selectedLink?: TID | null; tasks: ITask[]; links: ILink[]; start: Date; @@ -138,6 +139,8 @@ export interface IData { _selected: ITask[]; activeTask: TID; _activeTask: ITask; + selectedLink: TID | null; + _selectedLink: IGanttLink | null; tasks: GanttDataTree; _tasks: IParsedTask[]; links: DataArray; diff --git a/svelte/demos/cases/LinkDeleteTest.svelte b/svelte/demos/cases/LinkDeleteTest.svelte new file mode 100644 index 0000000..d90ca01 --- /dev/null +++ b/svelte/demos/cases/LinkDeleteTest.svelte @@ -0,0 +1,20 @@ + + +
+

连线删除功能测试

+

点击连线可以选中它,选中的连线会显示删除按钮。点击删除按钮可以删除连线。

+ + +
diff --git a/svelte/demos/routes.js b/svelte/demos/routes.js index 0a8fa1b..14360b0 100644 --- a/svelte/demos/routes.js +++ b/svelte/demos/routes.js @@ -51,6 +51,7 @@ import HeaderMenu from "./cases/GridHeaderMenu.svelte"; import GridInlineEditors from "./cases/GridInlineEditors.svelte"; import GanttEditorReadonly from "./cases/GanttEditorReadonly.svelte"; import GanttEditorValidation from "./cases/GanttEditorValidation.svelte"; +import LinkDeleteTest from "./cases/LinkDeleteTest.svelte"; export const links = [ ["/base/:skin", "Basic Gantt", BasicInit, "BasicInit"], @@ -243,4 +244,5 @@ export const links = [ ], ["/editor-readonly/:skin", "Editor: readonly", GanttEditorReadonly], ["/editor-validation/:skin", "Editor: validation", GanttEditorValidation], + ["/link-delete-test/:skin", "Editor: delete link", LinkDeleteTest, "LinkDeleteTest"], ]; diff --git a/svelte/src/components/Gantt.svelte b/svelte/src/components/Gantt.svelte index f17fae2..43d6c8c 100644 --- a/svelte/src/components/Gantt.svelte +++ b/svelte/src/components/Gantt.svelte @@ -25,6 +25,7 @@ tasks = [], selected = [], activeTask = null, + selectedLink = null, links = [], scales = [ { unit: "month", step: 1, format: "MMMM yyy" }, @@ -122,6 +123,7 @@ zoom, selected, activeTask, + selectedLink, baselines, autoScale, unscheduledTasks, diff --git a/svelte/src/components/chart/Chart.svelte b/svelte/src/components/chart/Chart.svelte index a183051..7507b7c 100644 --- a/svelte/src/components/chart/Chart.svelte +++ b/svelte/src/components/chart/Chart.svelte @@ -29,6 +29,7 @@ context, _markers: markers, _scrollTask: rScrollTask, + selectedLink, } = api.getReactiveState(); let scrollLeft = $state(), @@ -145,14 +146,22 @@ ev.eventSource = "chart"; api.exec("hotkey", ev); } + + function clearLinkSelection() { + if ($selectedLink != null) api.exec("select-link", { id: null }); + } + +
{#each $links as link (link.id)} - + + + + handleLinkClick(e, link.id)} + role="button" + tabindex="0" + /> + {#if $selectedLink === link.id} + {@const coords = link.$p.split(',').map(Number)} + {@const midIndex = Math.floor(coords.length / 4)} + {@const midX = coords[midIndex * 2] || coords[0]} + {@const midY = coords[midIndex * 2 + 1] || coords[1]} + {@const shouldShow = $selectedLink === link.id} + {@const debugInfo = `Link: ${link.id}, Selected: ${$selectedLink}, Should show: ${shouldShow}, Coords: ${midX},${midY}`} + + + + handleDeleteClick(e, link.id)} role="button" tabindex="0"> + + × + + {/if} + {/each} @@ -18,6 +57,13 @@ left: 0; width: 100%; height: 100%; + z-index: 10; + pointer-events: none; + } + + .wx-link-group { + cursor: pointer; + pointer-events: auto; } .wx-line { @@ -25,9 +71,28 @@ pointer-events: stroke; position: relative; cursor: pointer; + outline: none; stroke: var(--wx-gantt-link-color); stroke-width: 2; z-index: 0; fill: transparent; } + + .wx-link-group.wx-selected .wx-line { + stroke: var(--wx-color-primary); + stroke-width: 3; + } + + .wx-delete-button { + cursor: pointer; + pointer-events: auto; + z-index: 20; + outline: none; + } + + .wx-delete-button:hover circle { + fill: #cc0000; + transform: scale(1.1); + transition: all 0.2s ease; + }