ct-gantt-core 1.0.5 → 1.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +83 -80
- package/dist/index.cjs +273 -9
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +136 -1
- package/dist/index.d.ts +136 -1
- package/dist/index.js +256 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,80 +1,83 @@
|
|
|
1
|
-
# ct-gantt-core
|
|
2
|
-
|
|
3
|
-
框架无关的甘特图数据、布局、依赖排程和命令式引擎。该包不包含界面,可用于 Vue、React、原生 JavaScript、服务端计算或自定义渲染器。
|
|
4
|
-
|
|
5
|
-
## 安装
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
pnpm add ct-gantt-core
|
|
9
|
-
```
|
|
10
|
-
|
|
11
|
-
也可以使用 `npm install ct-gantt-core` 或 `yarn add ct-gantt-core`。
|
|
12
|
-
|
|
13
|
-
## 最小示例
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
import { GanttEngine, type GanttTask } from "ct-gantt-core"
|
|
17
|
-
|
|
18
|
-
const tasks: GanttTask[] = [
|
|
19
|
-
{
|
|
20
|
-
id: "task-1",
|
|
21
|
-
name: "需求分析",
|
|
22
|
-
type: "task",
|
|
23
|
-
plan: { start: "2026-07-01", end: "2026-07-05" },
|
|
24
|
-
actual: { start: "2026-07-01", end: "2026-07-06", progress: 60 }
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
|
|
28
|
-
const engine = new GanttEngine({
|
|
29
|
-
tasks,
|
|
30
|
-
config: { viewMode: "day", columnWidth: 30 }
|
|
31
|
-
})
|
|
32
|
-
|
|
33
|
-
const layout = engine.getLayout()
|
|
34
|
-
if (layout.ok) {
|
|
35
|
-
console.log(layout.data)
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
engine.setTask("task-1", { progress: 80 })
|
|
39
|
-
engine.destroy()
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
## 依赖排程
|
|
43
|
-
|
|
44
|
-
```ts
|
|
45
|
-
import { scheduleByDependencies, type GanttLink } from "ct-gantt-core"
|
|
46
|
-
|
|
47
|
-
const links: GanttLink[] = [
|
|
48
|
-
{ id: "design-to-dev", sourceId: "design", targetId: "dev", type: "FS" }
|
|
49
|
-
]
|
|
50
|
-
|
|
51
|
-
const result = scheduleByDependencies(tasks, links)
|
|
52
|
-
if (result.ok) {
|
|
53
|
-
console.log(result.data)
|
|
54
|
-
}
|
|
55
|
-
```
|
|
56
|
-
|
|
57
|
-
支持 `FS`、`SS`、`FF`、`SF` 四种依赖类型,以及自然日或工作日间隔。
|
|
58
|
-
|
|
59
|
-
## 主要导出
|
|
60
|
-
|
|
61
|
-
| API | 用途 |
|
|
62
|
-
| --- | --- |
|
|
63
|
-
| `GanttEngine` | 管理任务、依赖、折叠、预览、布局和视口命令 |
|
|
64
|
-
| `computeLayout` | 计算任务条位置和尺寸 |
|
|
65
|
-
| `computeTimeScale` |
|
|
66
|
-
| `
|
|
67
|
-
| `
|
|
68
|
-
| `
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
71
|
-
| `
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
|
|
1
|
+
# ct-gantt-core
|
|
2
|
+
|
|
3
|
+
框架无关的甘特图数据、布局、依赖排程和命令式引擎。该包不包含界面,可用于 Vue、React、原生 JavaScript、服务端计算或自定义渲染器。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pnpm add ct-gantt-core
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
也可以使用 `npm install ct-gantt-core` 或 `yarn add ct-gantt-core`。
|
|
12
|
+
|
|
13
|
+
## 最小示例
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { GanttEngine, type GanttTask } from "ct-gantt-core"
|
|
17
|
+
|
|
18
|
+
const tasks: GanttTask[] = [
|
|
19
|
+
{
|
|
20
|
+
id: "task-1",
|
|
21
|
+
name: "需求分析",
|
|
22
|
+
type: "task",
|
|
23
|
+
plan: { start: "2026-07-01", end: "2026-07-05" },
|
|
24
|
+
actual: { start: "2026-07-01", end: "2026-07-06", progress: 60 }
|
|
25
|
+
}
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const engine = new GanttEngine({
|
|
29
|
+
tasks,
|
|
30
|
+
config: { viewMode: "day", columnWidth: 30 }
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
const layout = engine.getLayout()
|
|
34
|
+
if (layout.ok) {
|
|
35
|
+
console.log(layout.data)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
engine.setTask("task-1", { progress: 80 })
|
|
39
|
+
engine.destroy()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## 依赖排程
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
import { scheduleByDependencies, type GanttLink } from "ct-gantt-core"
|
|
46
|
+
|
|
47
|
+
const links: GanttLink[] = [
|
|
48
|
+
{ id: "design-to-dev", sourceId: "design", targetId: "dev", type: "FS" }
|
|
49
|
+
]
|
|
50
|
+
|
|
51
|
+
const result = scheduleByDependencies(tasks, links)
|
|
52
|
+
if (result.ok) {
|
|
53
|
+
console.log(result.data)
|
|
54
|
+
}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
支持 `FS`、`SS`、`FF`、`SF` 四种依赖类型,以及自然日或工作日间隔。
|
|
58
|
+
|
|
59
|
+
## 主要导出
|
|
60
|
+
|
|
61
|
+
| API | 用途 |
|
|
62
|
+
| --- | --- |
|
|
63
|
+
| `GanttEngine` | 管理任务、依赖、折叠、预览、布局和视口命令 |
|
|
64
|
+
| `computeLayout` | 计算任务条位置和尺寸 |
|
|
65
|
+
| `computeTimeScale` | 计算时间刻度(day/week/month/quarter/year) |
|
|
66
|
+
| `computeHourScale` | 计算小时级时间刻度(每天 24 个小时列,顶行日期 + 底部整点小时) |
|
|
67
|
+
| `computeResourceLayout` | 资源视图布局:资源行 + 占用色条(支持 day/hour 粒度与车道堆叠) |
|
|
68
|
+
| `resolveUsageTimes` | 将资源占用解析为布局用起止时间(hour 粒度下纯日期占用含结束日整天) |
|
|
69
|
+
| `scheduleByDependencies` | 按依赖关系调整任务日期 |
|
|
70
|
+
| `computeImpact` | 分析任务变更的影响和约束冲突 |
|
|
71
|
+
| `checkCyclicDependency` | 检查循环依赖 |
|
|
72
|
+
| `normalizeLinks` | 统一任务内依赖与独立依赖数据 |
|
|
73
|
+
| `flattenTasks` | 将阶段树转换为可渲染行 |
|
|
74
|
+
| `toDate`、`toDateTime`、`addDays`、`addHours`、`diffDays`、`diffHours`、`formatDate`、`formatDateTime` | 日期工具(`toDateTime`/`diffHours` 保留时间,用于小时级计算) |
|
|
75
|
+
|
|
76
|
+
## 数据说明
|
|
77
|
+
|
|
78
|
+
- `plan` 表示计划日期。
|
|
79
|
+
- `actual` 表示实际日期和完成进度。
|
|
80
|
+
- `summary` 表示阶段,`task` 表示普通任务,`milestone` 表示任务型里程碑。
|
|
81
|
+
- Core 不负责绘制界面;需要现成 Vue 界面时请安装 `ct-gantt-vue`。
|
|
82
|
+
|
|
83
|
+
完整类型、配置和示例请查看[项目文档](https://github.com/moonlight-219/ganttu#readme)。
|
package/dist/index.cjs
CHANGED
|
@@ -21,29 +21,79 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
21
21
|
var index_exports = {};
|
|
22
22
|
__export(index_exports, {
|
|
23
23
|
DAY_MS: () => DAY_MS,
|
|
24
|
+
DEFAULT_HOUR_COLORS: () => DEFAULT_HOUR_COLORS,
|
|
25
|
+
DEFAULT_TASK_COLORS: () => DEFAULT_TASK_COLORS,
|
|
26
|
+
EDITOR_COLOR_OPTIONS: () => EDITOR_COLOR_OPTIONS,
|
|
24
27
|
GanttEngine: () => GanttEngine,
|
|
28
|
+
HOUR_MS: () => HOUR_MS,
|
|
29
|
+
OWNER_COLOR_PALETTE: () => OWNER_COLOR_PALETTE,
|
|
30
|
+
RESOURCE_COLOR_PALETTE: () => RESOURCE_COLOR_PALETTE,
|
|
25
31
|
addDays: () => addDays,
|
|
32
|
+
addHours: () => addHours,
|
|
26
33
|
checkCyclicDependency: () => checkCyclicDependency,
|
|
34
|
+
computeHourScale: () => computeHourScale,
|
|
27
35
|
computeImpact: () => computeImpact,
|
|
28
36
|
computeLayout: () => computeLayout,
|
|
37
|
+
computeResourceLayout: () => computeResourceLayout,
|
|
29
38
|
computeTimeScale: () => computeTimeScale,
|
|
30
39
|
createGanttEngine: () => createGanttEngine,
|
|
31
40
|
defaultConfig: () => defaultConfig,
|
|
32
41
|
diffDays: () => diffDays,
|
|
42
|
+
diffHours: () => diffHours,
|
|
33
43
|
flattenTasks: () => flattenTasks,
|
|
34
44
|
formatDate: () => formatDate,
|
|
45
|
+
formatDateTime: () => formatDateTime,
|
|
46
|
+
hasTimeComponent: () => hasTimeComponent,
|
|
35
47
|
inclusiveDays: () => inclusiveDays,
|
|
36
48
|
isValidDate: () => isValidDate,
|
|
37
49
|
mergeTaskPatch: () => mergeTaskPatch,
|
|
38
50
|
normalizeLinks: () => normalizeLinks,
|
|
51
|
+
resolveResourceColor: () => resolveResourceColor,
|
|
52
|
+
resolveUsageColor: () => resolveUsageColor,
|
|
53
|
+
resolveUsageTimes: () => resolveUsageTimes,
|
|
39
54
|
resultInvalidDate: () => resultInvalidDate,
|
|
40
55
|
scheduleByDependencies: () => scheduleByDependencies,
|
|
41
56
|
shiftTask: () => shiftTask,
|
|
42
57
|
startOfDay: () => startOfDay,
|
|
43
|
-
toDate: () => toDate
|
|
58
|
+
toDate: () => toDate,
|
|
59
|
+
toDateTime: () => toDateTime
|
|
44
60
|
});
|
|
45
61
|
module.exports = __toCommonJS(index_exports);
|
|
46
62
|
|
|
63
|
+
// src/styles/colors.ts
|
|
64
|
+
var DEFAULT_TASK_COLORS = {
|
|
65
|
+
task: "#4d77b3",
|
|
66
|
+
// 普通任务实际条
|
|
67
|
+
summary: "#475467",
|
|
68
|
+
// 阶段条
|
|
69
|
+
milestone: "#b57e4a",
|
|
70
|
+
// 里程碑菱形 / 参考线默认
|
|
71
|
+
plan: "#cbd5e1",
|
|
72
|
+
// 计划条外部底色
|
|
73
|
+
progress: "#367d73"
|
|
74
|
+
// 计划条内部进度色(任务未设置 planColor 时)
|
|
75
|
+
};
|
|
76
|
+
var DEFAULT_HOUR_COLORS = {
|
|
77
|
+
plan: "#cbd5e1",
|
|
78
|
+
// 计划条外部底色
|
|
79
|
+
progress: "#367d73",
|
|
80
|
+
// 计划条内部进度色(任务未设置 planColor 时)
|
|
81
|
+
milestone: "#b57e4a"
|
|
82
|
+
// 里程碑菱形 / 参考线默认
|
|
83
|
+
};
|
|
84
|
+
var RESOURCE_COLOR_PALETTE = ["#f10755", "#9b87c4", "#509b81", "#c18f67", "#5e7fb0"];
|
|
85
|
+
var EDITOR_COLOR_OPTIONS = [
|
|
86
|
+
"#4d77b3",
|
|
87
|
+
"#8f77c5",
|
|
88
|
+
"#c16794",
|
|
89
|
+
"#b57e4a",
|
|
90
|
+
"#b3a24d",
|
|
91
|
+
"#47ae8f",
|
|
92
|
+
"#3f99a6",
|
|
93
|
+
"#64748b"
|
|
94
|
+
];
|
|
95
|
+
var OWNER_COLOR_PALETTE = ["#c2708b", "#9b87c4", "#509b81", "#c18f67", "#5e7fb0"];
|
|
96
|
+
|
|
47
97
|
// src/types/index.ts
|
|
48
98
|
var defaultConfig = {
|
|
49
99
|
viewMode: "month",
|
|
@@ -64,13 +114,9 @@ var defaultConfig = {
|
|
|
64
114
|
enableLinkCreation: true,
|
|
65
115
|
showLinkRejectionNotice: true,
|
|
66
116
|
virtualScroll: true,
|
|
67
|
-
taskColors: {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
milestone: "#d97706",
|
|
71
|
-
plan: "#cbd5e1",
|
|
72
|
-
progress: "#0f766e"
|
|
73
|
-
},
|
|
117
|
+
taskColors: { ...DEFAULT_TASK_COLORS },
|
|
118
|
+
timeUnit: "day",
|
|
119
|
+
hourWidth: 12,
|
|
74
120
|
autoSchedule: true,
|
|
75
121
|
editable: true
|
|
76
122
|
};
|
|
@@ -211,6 +257,7 @@ function flattenTasks(tasks, collapsedIds = /* @__PURE__ */ new Set()) {
|
|
|
211
257
|
|
|
212
258
|
// src/utils/date.ts
|
|
213
259
|
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
260
|
+
var HOUR_MS = 60 * 60 * 1e3;
|
|
214
261
|
function toDate(value) {
|
|
215
262
|
if (value instanceof Date) {
|
|
216
263
|
return startOfDay(value);
|
|
@@ -221,6 +268,21 @@ function toDate(value) {
|
|
|
221
268
|
}
|
|
222
269
|
return startOfDay(new Date(value));
|
|
223
270
|
}
|
|
271
|
+
function hasTimeComponent(value) {
|
|
272
|
+
return typeof value === "string" && /T\d|:\d/.test(value);
|
|
273
|
+
}
|
|
274
|
+
function toDateTime(value) {
|
|
275
|
+
if (value instanceof Date) {
|
|
276
|
+
return new Date(value.getTime());
|
|
277
|
+
}
|
|
278
|
+
if (hasTimeComponent(value)) {
|
|
279
|
+
const date = new Date(value.replace(" ", "T"));
|
|
280
|
+
if (!Number.isNaN(date.getTime())) {
|
|
281
|
+
return date;
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
return toDate(value);
|
|
285
|
+
}
|
|
224
286
|
function startOfDay(date) {
|
|
225
287
|
return new Date(date.getFullYear(), date.getMonth(), date.getDate());
|
|
226
288
|
}
|
|
@@ -232,6 +294,12 @@ function addDays(date, days) {
|
|
|
232
294
|
function diffDays(start, end) {
|
|
233
295
|
return Math.round((toDate(end).getTime() - toDate(start).getTime()) / DAY_MS);
|
|
234
296
|
}
|
|
297
|
+
function addHours(date, hours) {
|
|
298
|
+
return new Date(toDateTime(date).getTime() + hours * HOUR_MS);
|
|
299
|
+
}
|
|
300
|
+
function diffHours(start, end) {
|
|
301
|
+
return (toDateTime(end).getTime() - toDateTime(start).getTime()) / HOUR_MS;
|
|
302
|
+
}
|
|
235
303
|
function inclusiveDays(start, end) {
|
|
236
304
|
return Math.max(0, diffDays(start, end) + 1);
|
|
237
305
|
}
|
|
@@ -246,6 +314,12 @@ function formatDate(date) {
|
|
|
246
314
|
const day = String(value.getDate()).padStart(2, "0");
|
|
247
315
|
return `${year}-${month}-${day}`;
|
|
248
316
|
}
|
|
317
|
+
function formatDateTime(date) {
|
|
318
|
+
const value = toDateTime(date);
|
|
319
|
+
const hours = String(value.getHours()).padStart(2, "0");
|
|
320
|
+
const minutes = String(value.getMinutes()).padStart(2, "0");
|
|
321
|
+
return `${formatDate(value)} ${hours}:${minutes}`;
|
|
322
|
+
}
|
|
249
323
|
function resultInvalidDate(message, details) {
|
|
250
324
|
return {
|
|
251
325
|
ok: false,
|
|
@@ -309,6 +383,34 @@ function nextUnit(date, viewMode) {
|
|
|
309
383
|
return addDays(date, 1);
|
|
310
384
|
}
|
|
311
385
|
|
|
386
|
+
// src/services/computeHourScale.ts
|
|
387
|
+
function computeHourScale(start, end, hourWidth) {
|
|
388
|
+
const rangeStart = startOfDay(start);
|
|
389
|
+
const rangeEnd = startOfDay(end);
|
|
390
|
+
const totalDays = Math.max(1, inclusiveDays(rangeStart, rangeEnd));
|
|
391
|
+
const days = [];
|
|
392
|
+
const hours = [];
|
|
393
|
+
for (let d = 0; d < totalDays; d += 1) {
|
|
394
|
+
const dayStart = addDays(rangeStart, d);
|
|
395
|
+
days.push({
|
|
396
|
+
start: dayStart,
|
|
397
|
+
end: addHours(dayStart, 24),
|
|
398
|
+
left: d * 24 * hourWidth,
|
|
399
|
+
width: 24 * hourWidth
|
|
400
|
+
});
|
|
401
|
+
for (let h = 0; h < 24; h += 1) {
|
|
402
|
+
const hourStart = addHours(dayStart, h);
|
|
403
|
+
hours.push({
|
|
404
|
+
start: hourStart,
|
|
405
|
+
end: addHours(hourStart, 1),
|
|
406
|
+
left: (d * 24 + h) * hourWidth,
|
|
407
|
+
width: hourWidth
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
return { days, hours, start: rangeStart, end: rangeEnd };
|
|
412
|
+
}
|
|
413
|
+
|
|
312
414
|
// src/engines/scheduling.ts
|
|
313
415
|
function scheduleByDependencies(tasks, links) {
|
|
314
416
|
const cycle = checkCyclicDependency(links);
|
|
@@ -550,6 +652,152 @@ function computeImpact(taskId, patch, tasks, links, config) {
|
|
|
550
652
|
return { ok: true, data: { changed, conflicts } };
|
|
551
653
|
}
|
|
552
654
|
|
|
655
|
+
// src/engines/computeResourceLayout.ts
|
|
656
|
+
function colorHash(source) {
|
|
657
|
+
return [...source].reduce((sum, char) => sum + (char.codePointAt(0) ?? 0), 0);
|
|
658
|
+
}
|
|
659
|
+
function resolveResourceColor(resource, resourceColors) {
|
|
660
|
+
if (resource.color) {
|
|
661
|
+
return resource.color;
|
|
662
|
+
}
|
|
663
|
+
if (resourceColors?.[resource.id]) {
|
|
664
|
+
return resourceColors[resource.id];
|
|
665
|
+
}
|
|
666
|
+
const source = resource.name || resource.id;
|
|
667
|
+
return RESOURCE_COLOR_PALETTE[colorHash(source) % RESOURCE_COLOR_PALETTE.length];
|
|
668
|
+
}
|
|
669
|
+
function resolveUsageColor(usage, resource, resourceColors) {
|
|
670
|
+
return usage.color || resolveResourceColor(resource, resourceColors);
|
|
671
|
+
}
|
|
672
|
+
function resolveUsageTimes(usage, timeUnit) {
|
|
673
|
+
if (timeUnit === "hour") {
|
|
674
|
+
const start = toDateTime(usage.start);
|
|
675
|
+
const endRaw = toDateTime(usage.end);
|
|
676
|
+
return { start, end: hasTimeComponent(usage.end) ? endRaw : addHours(endRaw, 24) };
|
|
677
|
+
}
|
|
678
|
+
return { start: toDate(usage.start), end: toDate(usage.end) };
|
|
679
|
+
}
|
|
680
|
+
function computeResourceLayout(resources, usages, config, viewport) {
|
|
681
|
+
const mergedConfig = { ...defaultConfig, ...config };
|
|
682
|
+
const timeUnit = mergedConfig.timeUnit ?? "day";
|
|
683
|
+
const hourMode = timeUnit === "hour";
|
|
684
|
+
const hourW = mergedConfig.hourWidth ?? 12;
|
|
685
|
+
for (const usage of usages) {
|
|
686
|
+
const { start, end } = resolveUsageTimes(usage, timeUnit);
|
|
687
|
+
if (!isValidDate(start) || !isValidDate(end)) {
|
|
688
|
+
return {
|
|
689
|
+
ok: false,
|
|
690
|
+
error: {
|
|
691
|
+
code: "INVALID_DATE",
|
|
692
|
+
message: `Resource usage ${usage.id} has invalid dates.`
|
|
693
|
+
}
|
|
694
|
+
};
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
const orderedIds = [];
|
|
698
|
+
const seen = /* @__PURE__ */ new Set();
|
|
699
|
+
for (const resource of resources) {
|
|
700
|
+
if (!seen.has(resource.id)) {
|
|
701
|
+
seen.add(resource.id);
|
|
702
|
+
orderedIds.push(resource.id);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
for (const usage of usages) {
|
|
706
|
+
if (!seen.has(usage.resourceId)) {
|
|
707
|
+
seen.add(usage.resourceId);
|
|
708
|
+
orderedIds.push(usage.resourceId);
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
const resourceById = new Map(resources.map((resource) => [resource.id, resource]));
|
|
712
|
+
const usagesByResource = /* @__PURE__ */ new Map();
|
|
713
|
+
for (const usage of usages) {
|
|
714
|
+
usagesByResource.set(usage.resourceId, [...usagesByResource.get(usage.resourceId) ?? [], usage]);
|
|
715
|
+
}
|
|
716
|
+
const rows = [];
|
|
717
|
+
const usageLayouts = [];
|
|
718
|
+
const laneIndexByUsage = /* @__PURE__ */ new Map();
|
|
719
|
+
orderedIds.forEach((resourceId, rowIndex) => {
|
|
720
|
+
const resource = resourceById.get(resourceId) ?? {
|
|
721
|
+
id: resourceId,
|
|
722
|
+
name: resourceId
|
|
723
|
+
};
|
|
724
|
+
const grouped = [...usagesByResource.get(resourceId) ?? []].sort((a, b) => {
|
|
725
|
+
const as = resolveUsageTimes(a, timeUnit);
|
|
726
|
+
const bs = resolveUsageTimes(b, timeUnit);
|
|
727
|
+
return as.start.getTime() - bs.start.getTime() || as.end.getTime() - bs.end.getTime();
|
|
728
|
+
});
|
|
729
|
+
const laneEnds = [];
|
|
730
|
+
for (const usage of grouped) {
|
|
731
|
+
const { start, end } = resolveUsageTimes(usage, timeUnit);
|
|
732
|
+
let laneIndex = -1;
|
|
733
|
+
for (let k = 0; k < laneEnds.length; k += 1) {
|
|
734
|
+
const laneEnd = laneEnds[k];
|
|
735
|
+
if (laneEnd === null || isLaneFree(laneEnd, start, hourMode)) {
|
|
736
|
+
laneIndex = k;
|
|
737
|
+
break;
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
if (laneIndex === -1) {
|
|
741
|
+
laneIndex = laneEnds.length;
|
|
742
|
+
laneEnds.push(null);
|
|
743
|
+
}
|
|
744
|
+
laneEnds[laneIndex] = end;
|
|
745
|
+
laneIndexByUsage.set(usage.id, laneIndex);
|
|
746
|
+
}
|
|
747
|
+
rows.push({
|
|
748
|
+
resourceId,
|
|
749
|
+
rowIndex,
|
|
750
|
+
laneCount: laneEnds.length,
|
|
751
|
+
color: resolveResourceColor(resource, mergedConfig.resourceColors),
|
|
752
|
+
usageIds: grouped.map((usage) => usage.id)
|
|
753
|
+
});
|
|
754
|
+
});
|
|
755
|
+
const usageById = new Map(usages.map((usage) => [usage.id, usage]));
|
|
756
|
+
const visibleStart = viewport ? Math.max(0, Math.floor(viewport.scrollTop / mergedConfig.rowHeight) - 8) : 0;
|
|
757
|
+
const visibleEnd = viewport ? Math.ceil((viewport.scrollTop + viewport.clientHeight) / mergedConfig.rowHeight) + 8 : Number.POSITIVE_INFINITY;
|
|
758
|
+
const firstDate = hourMode ? startOfDay(
|
|
759
|
+
mergedConfig.visibleRange ? toDateTime(mergedConfig.visibleRange.start) : usages.length ? minDate2(usages.map((usage) => resolveUsageTimes(usage, "hour").start)) : toDateTime(/* @__PURE__ */ new Date())
|
|
760
|
+
) : mergedConfig.visibleRange ? toDate(mergedConfig.visibleRange.start) : usages.length ? minDate2(usages.map((usage) => toDate(usage.start))) : toDate(/* @__PURE__ */ new Date());
|
|
761
|
+
for (const row of rows) {
|
|
762
|
+
if (row.rowIndex < visibleStart || row.rowIndex > visibleEnd) {
|
|
763
|
+
continue;
|
|
764
|
+
}
|
|
765
|
+
const resource = resourceById.get(row.resourceId) ?? { id: row.resourceId, name: row.resourceId };
|
|
766
|
+
const laneCount = Math.max(1, row.laneCount);
|
|
767
|
+
const slot = mergedConfig.rowHeight / laneCount;
|
|
768
|
+
const barHeight = Math.max(6, Math.min(14, slot - 4));
|
|
769
|
+
for (const usageId of row.usageIds) {
|
|
770
|
+
const usage = usageById.get(usageId);
|
|
771
|
+
if (!usage) {
|
|
772
|
+
continue;
|
|
773
|
+
}
|
|
774
|
+
const { start, end } = resolveUsageTimes(usage, timeUnit);
|
|
775
|
+
const laneIndex = laneIndexByUsage.get(usageId) ?? 0;
|
|
776
|
+
const laneTop = laneIndex * slot + (slot - barHeight) / 2;
|
|
777
|
+
const left = hourMode ? diffHours(firstDate, start) * hourW : diffDays(firstDate, start) * mergedConfig.columnWidth;
|
|
778
|
+
const width = hourMode ? Math.max(hourW, diffHours(start, end) * hourW) : Math.max(mergedConfig.columnWidth, inclusiveDays(start, end) * mergedConfig.columnWidth);
|
|
779
|
+
usageLayouts.push({
|
|
780
|
+
usageId,
|
|
781
|
+
resourceId: row.resourceId,
|
|
782
|
+
rowIndex: row.rowIndex,
|
|
783
|
+
laneIndex,
|
|
784
|
+
laneCount,
|
|
785
|
+
left,
|
|
786
|
+
width,
|
|
787
|
+
top: mergedConfig.headerHeight + row.rowIndex * mergedConfig.rowHeight + laneTop,
|
|
788
|
+
height: barHeight
|
|
789
|
+
});
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return { ok: true, data: { rows, usages: usageLayouts } };
|
|
793
|
+
}
|
|
794
|
+
function isLaneFree(laneEnd, start, hourMode) {
|
|
795
|
+
return hourMode ? diffHours(laneEnd, start) >= 0 : diffDays(laneEnd, start) > 0;
|
|
796
|
+
}
|
|
797
|
+
function minDate2(dates) {
|
|
798
|
+
return new Date(Math.min(...dates.map((date) => date.getTime())));
|
|
799
|
+
}
|
|
800
|
+
|
|
553
801
|
// src/engine/GanttEngine.ts
|
|
554
802
|
function mergeTaskPatch(task, patch) {
|
|
555
803
|
return {
|
|
@@ -919,25 +1167,41 @@ function createGanttEngine(options = {}) {
|
|
|
919
1167
|
// Annotate the CommonJS export names for ESM import in node:
|
|
920
1168
|
0 && (module.exports = {
|
|
921
1169
|
DAY_MS,
|
|
1170
|
+
DEFAULT_HOUR_COLORS,
|
|
1171
|
+
DEFAULT_TASK_COLORS,
|
|
1172
|
+
EDITOR_COLOR_OPTIONS,
|
|
922
1173
|
GanttEngine,
|
|
1174
|
+
HOUR_MS,
|
|
1175
|
+
OWNER_COLOR_PALETTE,
|
|
1176
|
+
RESOURCE_COLOR_PALETTE,
|
|
923
1177
|
addDays,
|
|
1178
|
+
addHours,
|
|
924
1179
|
checkCyclicDependency,
|
|
1180
|
+
computeHourScale,
|
|
925
1181
|
computeImpact,
|
|
926
1182
|
computeLayout,
|
|
1183
|
+
computeResourceLayout,
|
|
927
1184
|
computeTimeScale,
|
|
928
1185
|
createGanttEngine,
|
|
929
1186
|
defaultConfig,
|
|
930
1187
|
diffDays,
|
|
1188
|
+
diffHours,
|
|
931
1189
|
flattenTasks,
|
|
932
1190
|
formatDate,
|
|
1191
|
+
formatDateTime,
|
|
1192
|
+
hasTimeComponent,
|
|
933
1193
|
inclusiveDays,
|
|
934
1194
|
isValidDate,
|
|
935
1195
|
mergeTaskPatch,
|
|
936
1196
|
normalizeLinks,
|
|
1197
|
+
resolveResourceColor,
|
|
1198
|
+
resolveUsageColor,
|
|
1199
|
+
resolveUsageTimes,
|
|
937
1200
|
resultInvalidDate,
|
|
938
1201
|
scheduleByDependencies,
|
|
939
1202
|
shiftTask,
|
|
940
1203
|
startOfDay,
|
|
941
|
-
toDate
|
|
1204
|
+
toDate,
|
|
1205
|
+
toDateTime
|
|
942
1206
|
});
|
|
943
1207
|
//# sourceMappingURL=index.cjs.map
|