Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 22 additions & 2 deletions store/src/DataStore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,18 @@ export default class DataStore extends Store<IData> {
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"],
Expand Down Expand Up @@ -226,6 +238,9 @@ export default class DataStore extends Store<IData> {
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"]) => {
Expand Down Expand Up @@ -286,9 +301,13 @@ export default class DataStore extends Store<IData> {
}
);
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<IData> = { links };
if (selectedLink === id) {
update.selectedLink = null;
}
this.setStateAsync(update);
});
inBus.on("update-link", (ev: TMethodsConfig["update-link"]) => {
const { links } = this.getState();
Expand Down Expand Up @@ -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<ILink> };
["update-link"]: { id: TID; link: Partial<ILink> };
["delete-link"]: { id: TID };
Expand Down
3 changes: 3 additions & 0 deletions store/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ export interface IMarker {
export interface IDataConfig {
selected?: TID[];
activeTask?: TID;
selectedLink?: TID | null;
tasks: ITask[];
links: ILink[];
start: Date;
Expand Down Expand Up @@ -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<ILink>;
Expand Down
20 changes: 20 additions & 0 deletions svelte/demos/cases/LinkDeleteTest.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<script>
import { getData } from "../data";
import { Gantt } from "../../src/";

let { skinSettings } = $props();

const data = getData();
</script>

<div style="padding: 20px;">
<h2>连线删除功能测试</h2>
<p>点击连线可以选中它,选中的连线会显示删除按钮。点击删除按钮可以删除连线。</p>

<Gantt
{...skinSettings}
tasks={data.tasks}
links={data.links}
scales={data.scales}
/>
</div>
2 changes: 2 additions & 0 deletions svelte/demos/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"],
Expand Down Expand Up @@ -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"],
];
2 changes: 2 additions & 0 deletions svelte/src/components/Gantt.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
tasks = [],
selected = [],
activeTask = null,
selectedLink = null,
links = [],
scales = [
{ unit: "month", step: 1, format: "MMMM yyy" },
Expand Down Expand Up @@ -122,6 +123,7 @@
zoom,
selected,
activeTask,
selectedLink,
baselines,
autoScale,
unscheduledTasks,
Expand Down
9 changes: 9 additions & 0 deletions svelte/src/components/chart/Chart.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
context,
_markers: markers,
_scrollTask: rScrollTask,
selectedLink,
} = api.getReactiveState();

let scrollLeft = $state(),
Expand Down Expand Up @@ -145,14 +146,22 @@
ev.eventSource = "chart";
api.exec("hotkey", ev);
}

function clearLinkSelection() {
if ($selectedLink != null) api.exec("select-link", { id: null });
}
</script>

<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<div
class="wx-chart"
role="application"
tabindex="-1"
bind:this={chart}
onscroll={onScroll}
onwheel={onWheel}
onclick={clearLinkSelection}
bind:clientHeight={chartHeight}
use:hotkeys={{
keys: {
Expand Down
69 changes: 67 additions & 2 deletions svelte/src/components/chart/Links.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,51 @@
import { getContext } from "svelte";

const api = getContext("gantt-store");
const links = api.getReactiveState()._links;
const { _links: links, selectedLink, _selectedLink } = api.getReactiveState();

// 调试信息
// $: console.log("Links.svelte - selectedLink:", $selectedLink, "_selectedLink:", $_selectedLink);

function handleLinkClick(event, linkId) {
event.stopPropagation();
console.log("Clicking link:", linkId);
api.exec("select-link", { id: linkId });
}

function handleDeleteClick(event, linkId) {
event.stopPropagation();
api.exec("delete-link", { id: linkId });
}
</script>

<svg class="wx-links">
{#each $links as link (link.id)}
<polyline class="wx-line" points={link.$p} />
<g class="wx-link-group" class:wx-selected={$selectedLink === link.id}>
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<polyline
class="wx-line"
points={link.$p}
onclick={(e) => 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}`}
<!-- {console.log(debugInfo)} -->
<!-- svelte-ignore a11y_click_events_have_key_events -->
<!-- svelte-ignore a11y_no_noninteractive_element_interactions -->
<g class="wx-delete-button" transform="translate({midX}, {midY})" onclick={(e) => handleDeleteClick(e, link.id)} role="button" tabindex="0">
<circle cx="0" cy="0" r="10" fill="#ff4444" stroke="white" stroke-width="2" />
<text x="0" y="4" text-anchor="middle" fill="white" font-size="12" font-weight="bold">×</text>
</g>
{/if}
</g>
{/each}
</svg>

Expand All @@ -18,16 +57,42 @@
left: 0;
width: 100%;
height: 100%;
z-index: 10;
pointer-events: none;
}

.wx-link-group {
cursor: pointer;
pointer-events: auto;
}

.wx-line {
user-select: auto;
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;
}
</style>