@diagrammo/dgmo 0.7.0 → 0.7.1
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/dist/cli.cjs +178 -178
- package/dist/index.cjs +218 -87
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +218 -87
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/c4/parser.ts +3 -2
- package/src/d3.ts +7 -9
- package/src/er/parser.ts +5 -3
- package/src/gantt/calculator.ts +51 -11
- package/src/gantt/parser.ts +26 -20
- package/src/gantt/renderer.ts +177 -51
- package/src/org/parser.ts +7 -5
- package/src/sequence/parser.ts +10 -9
- package/src/sitemap/parser.ts +5 -3
- package/src/utils/parsing.ts +23 -12
package/dist/index.js
CHANGED
|
@@ -1495,24 +1495,26 @@ function parseSeriesNames(value, lines, lineIndex, palette) {
|
|
|
1495
1495
|
}
|
|
1496
1496
|
return { series, names, nameColors, newIndex };
|
|
1497
1497
|
}
|
|
1498
|
-
function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map()) {
|
|
1498
|
+
function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map(), warnMultiplePipes) {
|
|
1499
|
+
if (segments.length > 2 && warnMultiplePipes) {
|
|
1500
|
+
warnMultiplePipes();
|
|
1501
|
+
}
|
|
1499
1502
|
const metadata = {};
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
}
|
|
1503
|
+
const raw = segments.slice(1).join(",");
|
|
1504
|
+
for (const part of raw.split(",")) {
|
|
1505
|
+
const trimmedPart = part.trim();
|
|
1506
|
+
if (!trimmedPart) continue;
|
|
1507
|
+
const colonIdx = trimmedPart.indexOf(":");
|
|
1508
|
+
if (colonIdx > 0) {
|
|
1509
|
+
const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
|
|
1510
|
+
const key = aliasMap.get(rawKey) ?? rawKey;
|
|
1511
|
+
const value = trimmedPart.substring(colonIdx + 1).trim();
|
|
1512
|
+
metadata[key] = value;
|
|
1511
1513
|
}
|
|
1512
1514
|
}
|
|
1513
1515
|
return metadata;
|
|
1514
1516
|
}
|
|
1515
|
-
var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
|
|
1517
|
+
var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE, MULTIPLE_PIPE_WARNING;
|
|
1516
1518
|
var init_parsing = __esm({
|
|
1517
1519
|
"src/utils/parsing.ts"() {
|
|
1518
1520
|
"use strict";
|
|
@@ -1521,6 +1523,7 @@ var init_parsing = __esm({
|
|
|
1521
1523
|
CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
|
|
1522
1524
|
TITLE_RE = /^title\s*:\s*(.+)/i;
|
|
1523
1525
|
OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
|
|
1526
|
+
MULTIPLE_PIPE_WARNING = 'Use a single "|" to start metadata, then separate items with commas.';
|
|
1524
1527
|
}
|
|
1525
1528
|
});
|
|
1526
1529
|
|
|
@@ -2021,12 +2024,13 @@ function parseSequenceDgmo(content) {
|
|
|
2021
2024
|
const participantGroupMap = /* @__PURE__ */ new Map();
|
|
2022
2025
|
let currentTagGroup = null;
|
|
2023
2026
|
const aliasMap = /* @__PURE__ */ new Map();
|
|
2024
|
-
const splitPipe = (text) => {
|
|
2027
|
+
const splitPipe = (text, ln) => {
|
|
2025
2028
|
const idx = text.indexOf("|");
|
|
2026
2029
|
if (idx < 0) return { core: text };
|
|
2027
2030
|
const core = text.substring(0, idx).trimEnd();
|
|
2028
2031
|
const segments = text.substring(idx).split("|");
|
|
2029
|
-
const
|
|
2032
|
+
const warnFn = ln != null ? () => pushWarning(ln, MULTIPLE_PIPE_WARNING) : void 0;
|
|
2033
|
+
const meta = parsePipeMetadata(segments, aliasMap, warnFn);
|
|
2030
2034
|
return Object.keys(meta).length > 0 ? { core, meta } : { core };
|
|
2031
2035
|
};
|
|
2032
2036
|
const blockStack = [];
|
|
@@ -2055,7 +2059,7 @@ function parseSequenceDgmo(content) {
|
|
|
2055
2059
|
if (gpipeIdx >= 0) {
|
|
2056
2060
|
const nameAndColor = groupName.substring(0, gpipeIdx).trimEnd();
|
|
2057
2061
|
const segments = groupName.substring(gpipeIdx).split("|");
|
|
2058
|
-
const meta = parsePipeMetadata(segments, aliasMap);
|
|
2062
|
+
const meta = parsePipeMetadata(segments, aliasMap, () => pushWarning(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
2059
2063
|
if (Object.keys(meta).length > 0) groupMeta = meta;
|
|
2060
2064
|
const colorSuffix = nameAndColor.match(/^(.+?)\(([^)]+)\)$/);
|
|
2061
2065
|
if (colorSuffix) {
|
|
@@ -2178,7 +2182,7 @@ function parseSequenceDgmo(content) {
|
|
|
2178
2182
|
continue;
|
|
2179
2183
|
}
|
|
2180
2184
|
}
|
|
2181
|
-
const { core: isACore, meta: isAMeta } = splitPipe(trimmed);
|
|
2185
|
+
const { core: isACore, meta: isAMeta } = splitPipe(trimmed, lineNumber);
|
|
2182
2186
|
const isAMatch = isACore.match(IS_A_PATTERN);
|
|
2183
2187
|
if (isAMatch) {
|
|
2184
2188
|
contentStarted = true;
|
|
@@ -2215,7 +2219,7 @@ function parseSequenceDgmo(content) {
|
|
|
2215
2219
|
}
|
|
2216
2220
|
continue;
|
|
2217
2221
|
}
|
|
2218
|
-
const { core: posCore, meta: posMeta } = splitPipe(trimmed);
|
|
2222
|
+
const { core: posCore, meta: posMeta } = splitPipe(trimmed, lineNumber);
|
|
2219
2223
|
const posOnlyMatch = posCore.match(POSITION_ONLY_PATTERN);
|
|
2220
2224
|
if (posOnlyMatch) {
|
|
2221
2225
|
contentStarted = true;
|
|
@@ -2242,7 +2246,7 @@ function parseSequenceDgmo(content) {
|
|
|
2242
2246
|
}
|
|
2243
2247
|
continue;
|
|
2244
2248
|
}
|
|
2245
|
-
const { core: colorCore, meta: colorMeta } = splitPipe(trimmed);
|
|
2249
|
+
const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
|
|
2246
2250
|
const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
|
|
2247
2251
|
if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
|
|
2248
2252
|
const id = coloredMatch[1];
|
|
@@ -2270,7 +2274,7 @@ function parseSequenceDgmo(content) {
|
|
|
2270
2274
|
continue;
|
|
2271
2275
|
}
|
|
2272
2276
|
{
|
|
2273
|
-
const { core: bareCore, meta: bareMeta } = splitPipe(trimmed);
|
|
2277
|
+
const { core: bareCore, meta: bareMeta } = splitPipe(trimmed, lineNumber);
|
|
2274
2278
|
const inGroup = activeGroup && measureIndent(raw) > 0;
|
|
2275
2279
|
if (/^\S+$/.test(bareCore) && !ARROW_PATTERN.test(bareCore) && (inGroup || !contentStarted || bareMeta)) {
|
|
2276
2280
|
contentStarted = true;
|
|
@@ -2306,7 +2310,7 @@ function parseSequenceDgmo(content) {
|
|
|
2306
2310
|
}
|
|
2307
2311
|
blockStack.pop();
|
|
2308
2312
|
}
|
|
2309
|
-
const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed);
|
|
2313
|
+
const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed, lineNumber);
|
|
2310
2314
|
const asyncPrefixMatch = arrowCore.match(/^async\s+(.+)$/i);
|
|
2311
2315
|
if (asyncPrefixMatch && ARROW_PATTERN.test(asyncPrefixMatch[1])) {
|
|
2312
2316
|
pushError(lineNumber, "Use ~> for async messages: A ~> B: message");
|
|
@@ -3757,7 +3761,12 @@ function parseERDiagram(content, palette) {
|
|
|
3757
3761
|
table.lineNumber = lineNumber;
|
|
3758
3762
|
const pipeStr = tableDecl[3]?.trim();
|
|
3759
3763
|
if (pipeStr) {
|
|
3760
|
-
const
|
|
3764
|
+
const pipeSegments = pipeStr.split("|");
|
|
3765
|
+
const meta = parsePipeMetadata(
|
|
3766
|
+
["", ...pipeSegments],
|
|
3767
|
+
aliasMap,
|
|
3768
|
+
() => result.diagnostics.push(makeDgmoError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"))
|
|
3769
|
+
);
|
|
3761
3770
|
Object.assign(table.metadata, meta);
|
|
3762
3771
|
}
|
|
3763
3772
|
currentTable = table;
|
|
@@ -5694,13 +5703,13 @@ function parseOrg(content, palette) {
|
|
|
5694
5703
|
}
|
|
5695
5704
|
} else if (metadataMatch && indentStack.length === 0) {
|
|
5696
5705
|
if (indent === 0) {
|
|
5697
|
-
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
5706
|
+
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
5698
5707
|
attachNode(node, indent, indentStack, result);
|
|
5699
5708
|
} else {
|
|
5700
5709
|
pushError(lineNumber, "Metadata has no parent node");
|
|
5701
5710
|
}
|
|
5702
5711
|
} else {
|
|
5703
|
-
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
5712
|
+
const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
5704
5713
|
attachNode(node, indent, indentStack, result);
|
|
5705
5714
|
}
|
|
5706
5715
|
}
|
|
@@ -5722,11 +5731,11 @@ function parseOrg(content, palette) {
|
|
|
5722
5731
|
}
|
|
5723
5732
|
return result;
|
|
5724
5733
|
}
|
|
5725
|
-
function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
|
|
5734
|
+
function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
|
|
5726
5735
|
const segments = trimmed.split("|").map((s) => s.trim());
|
|
5727
5736
|
let rawLabel = segments[0];
|
|
5728
5737
|
const { label, color } = extractColor(rawLabel, palette);
|
|
5729
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
5738
|
+
const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
|
|
5730
5739
|
return {
|
|
5731
5740
|
id: `node-${counter}`,
|
|
5732
5741
|
label,
|
|
@@ -6268,7 +6277,7 @@ function parseC4(content, palette) {
|
|
|
6268
6277
|
}
|
|
6269
6278
|
const segments = trimmed.split("|").map((s) => s.trim());
|
|
6270
6279
|
const nodeName = segments[0];
|
|
6271
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
6280
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
|
|
6272
6281
|
const shape = inferC4Shape(nodeName, metadata.tech ?? metadata.technology);
|
|
6273
6282
|
const dNode = {
|
|
6274
6283
|
name: nodeName,
|
|
@@ -6421,7 +6430,7 @@ function parseC4(content, palette) {
|
|
|
6421
6430
|
}
|
|
6422
6431
|
namePart = namePart.substring(0, isAMatch.index).trim();
|
|
6423
6432
|
}
|
|
6424
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
6433
|
+
const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
|
|
6425
6434
|
const shape = explicitShape ?? inferC4Shape(namePart, metadata.tech ?? metadata.technology);
|
|
6426
6435
|
const element = {
|
|
6427
6436
|
name: namePart,
|
|
@@ -7208,14 +7217,14 @@ function parseSitemap(content, palette) {
|
|
|
7208
7217
|
}
|
|
7209
7218
|
} else if (metadataMatch && indentStack.length === 0) {
|
|
7210
7219
|
if (indent === 0) {
|
|
7211
|
-
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
7220
|
+
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
7212
7221
|
attachNode2(node, indent, indentStack, result);
|
|
7213
7222
|
labelToNode.set(node.label.toLowerCase(), node);
|
|
7214
7223
|
} else {
|
|
7215
7224
|
pushError(lineNumber, "Metadata has no parent node");
|
|
7216
7225
|
}
|
|
7217
7226
|
} else {
|
|
7218
|
-
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
|
|
7227
|
+
const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
|
|
7219
7228
|
attachNode2(node, indent, indentStack, result);
|
|
7220
7229
|
labelToNode.set(node.label.toLowerCase(), node);
|
|
7221
7230
|
}
|
|
@@ -7257,11 +7266,11 @@ function parseSitemap(content, palette) {
|
|
|
7257
7266
|
}
|
|
7258
7267
|
return result;
|
|
7259
7268
|
}
|
|
7260
|
-
function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
|
|
7269
|
+
function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
|
|
7261
7270
|
const segments = trimmed.split("|").map((s) => s.trim());
|
|
7262
7271
|
const rawLabel = segments[0];
|
|
7263
7272
|
const { label, color } = extractColor(rawLabel, palette);
|
|
7264
|
-
const metadata = parsePipeMetadata(segments, aliasMap);
|
|
7273
|
+
const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
|
|
7265
7274
|
return {
|
|
7266
7275
|
id: `node-${counter}`,
|
|
7267
7276
|
label,
|
|
@@ -7969,6 +7978,9 @@ function parseGantt(content, palette) {
|
|
|
7969
7978
|
const warn = (line10, message) => {
|
|
7970
7979
|
diagnostics.push(makeDgmoError(line10, message, "warning"));
|
|
7971
7980
|
};
|
|
7981
|
+
const softError = (line10, message) => {
|
|
7982
|
+
diagnostics.push(makeDgmoError(line10, message, "error"));
|
|
7983
|
+
};
|
|
7972
7984
|
const aliasMap = /* @__PURE__ */ new Map();
|
|
7973
7985
|
const blockStack = [];
|
|
7974
7986
|
const currentContainer = () => {
|
|
@@ -8102,10 +8114,10 @@ function parseGantt(content, palette) {
|
|
|
8102
8114
|
const targetName = depParts[0].trim();
|
|
8103
8115
|
let offset;
|
|
8104
8116
|
if (depParts.length > 1) {
|
|
8105
|
-
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
|
|
8117
|
+
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
8106
8118
|
if (meta.lag || meta.lead) {
|
|
8107
8119
|
const key = meta.lag ? "lag" : "lead";
|
|
8108
|
-
|
|
8120
|
+
softError(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
|
|
8109
8121
|
}
|
|
8110
8122
|
if (meta.offset) {
|
|
8111
8123
|
const raw = meta.offset;
|
|
@@ -8244,16 +8256,18 @@ function parseGantt(content, palette) {
|
|
|
8244
8256
|
const groupMatch = line10.match(GROUP_RE2);
|
|
8245
8257
|
if (groupMatch) {
|
|
8246
8258
|
if (blockStack.length > 0 && blockStack[blockStack.length - 1].containerType === "task") {
|
|
8247
|
-
|
|
8259
|
+
softError(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
|
|
8260
|
+
continue;
|
|
8248
8261
|
}
|
|
8249
8262
|
const afterBrackets = groupMatch[2].trim();
|
|
8250
8263
|
const segments = afterBrackets ? afterBrackets.split("|") : [];
|
|
8251
8264
|
let metadata = {};
|
|
8252
8265
|
let color = null;
|
|
8266
|
+
const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_WARNING);
|
|
8253
8267
|
if (segments.length > 0 && segments[0].trim()) {
|
|
8254
|
-
metadata = parsePipeMetadata(["", ...segments], aliasMap);
|
|
8268
|
+
metadata = parsePipeMetadata(["", ...segments], aliasMap, pipeWarn);
|
|
8255
8269
|
} else if (segments.length > 1) {
|
|
8256
|
-
metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap);
|
|
8270
|
+
metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap, pipeWarn);
|
|
8257
8271
|
}
|
|
8258
8272
|
const nameExtracted = extractColor(groupMatch[1], palette);
|
|
8259
8273
|
if (nameExtracted.color) {
|
|
@@ -8284,7 +8298,6 @@ function parseGantt(content, palette) {
|
|
|
8284
8298
|
const uncertain = !!timelineDurMatch[4];
|
|
8285
8299
|
const labelRaw = timelineDurMatch[5];
|
|
8286
8300
|
const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber, startDate);
|
|
8287
|
-
if (result.error) return result;
|
|
8288
8301
|
const taskNode = { kind: "task", ...task };
|
|
8289
8302
|
currentContainer().push(taskNode);
|
|
8290
8303
|
lastTaskNode = taskNode;
|
|
@@ -8298,7 +8311,6 @@ function parseGantt(content, palette) {
|
|
|
8298
8311
|
const uncertain = !!durMatch[3];
|
|
8299
8312
|
const labelRaw = durMatch[4];
|
|
8300
8313
|
const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber);
|
|
8301
|
-
if (result.error) return result;
|
|
8302
8314
|
const taskNode = { kind: "task", ...task };
|
|
8303
8315
|
currentContainer().push(taskNode);
|
|
8304
8316
|
lastTaskNode = taskNode;
|
|
@@ -8315,7 +8327,6 @@ function parseGantt(content, palette) {
|
|
|
8315
8327
|
lineNumber,
|
|
8316
8328
|
explicitDateMatch[1]
|
|
8317
8329
|
);
|
|
8318
|
-
if (result.error) return result;
|
|
8319
8330
|
const taskNode = { kind: "task", ...task };
|
|
8320
8331
|
currentContainer().push(taskNode);
|
|
8321
8332
|
lastTaskNode = taskNode;
|
|
@@ -8325,13 +8336,14 @@ function parseGantt(content, palette) {
|
|
|
8325
8336
|
const depMatch = line10.match(DEPENDENCY_RE);
|
|
8326
8337
|
if (depMatch) {
|
|
8327
8338
|
if (!lastTaskNode) {
|
|
8328
|
-
|
|
8339
|
+
softError(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
|
|
8340
|
+
continue;
|
|
8329
8341
|
}
|
|
8330
8342
|
const depParts = depMatch[1].split("|");
|
|
8331
8343
|
const targetName = depParts[0].trim();
|
|
8332
8344
|
let offset;
|
|
8333
8345
|
if (depParts.length > 1) {
|
|
8334
|
-
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
|
|
8346
|
+
const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
|
|
8335
8347
|
if (meta.lag || meta.lead) {
|
|
8336
8348
|
const key = meta.lag ? "lag" : "lead";
|
|
8337
8349
|
warn(lineNumber, `"${key}" is deprecated \u2014 use "offset: ${meta[key]}" instead.${key === "lead" ? ' Negate the value for lead behavior: "offset: -...".' : ""}`);
|
|
@@ -8351,7 +8363,8 @@ function parseGantt(content, palette) {
|
|
|
8351
8363
|
lastTaskNode.dependencies.push({ targetName, offset, lineNumber });
|
|
8352
8364
|
continue;
|
|
8353
8365
|
}
|
|
8354
|
-
|
|
8366
|
+
softError(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
|
|
8367
|
+
continue;
|
|
8355
8368
|
}
|
|
8356
8369
|
if (currentTagGroup) {
|
|
8357
8370
|
result.tagGroups.push(currentTagGroup);
|
|
@@ -8365,16 +8378,16 @@ function parseGantt(content, palette) {
|
|
|
8365
8378
|
const segments = labelRaw.split("|");
|
|
8366
8379
|
const label = segments[0].trim();
|
|
8367
8380
|
if (label.toLowerCase() === "parallel") {
|
|
8368
|
-
|
|
8381
|
+
softError(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
|
|
8369
8382
|
}
|
|
8370
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap) : {};
|
|
8383
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap, () => warn(ln, MULTIPLE_PIPE_WARNING)) : {};
|
|
8371
8384
|
let progress = null;
|
|
8372
8385
|
if (metadata.progress) {
|
|
8373
8386
|
progress = parseFloat(metadata.progress);
|
|
8374
8387
|
delete metadata.progress;
|
|
8375
8388
|
}
|
|
8376
|
-
for (
|
|
8377
|
-
const seg =
|
|
8389
|
+
for (const part of segments.slice(1).join(",").split(",")) {
|
|
8390
|
+
const seg = part.trim();
|
|
8378
8391
|
const progressMatch = seg.match(/^(\d+)%$/);
|
|
8379
8392
|
if (progressMatch) {
|
|
8380
8393
|
progress = parseInt(progressMatch[1], 10);
|
|
@@ -8382,7 +8395,7 @@ function parseGantt(content, palette) {
|
|
|
8382
8395
|
}
|
|
8383
8396
|
if (metadata.lag || metadata.lead) {
|
|
8384
8397
|
const key = metadata.lag ? "lag" : "lead";
|
|
8385
|
-
|
|
8398
|
+
softError(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
|
|
8386
8399
|
}
|
|
8387
8400
|
let taskOffset;
|
|
8388
8401
|
if (metadata.offset) {
|
|
@@ -8469,7 +8482,7 @@ var init_parser10 = __esm({
|
|
|
8469
8482
|
DEPENDENCY_RE = /^->\s*(.+)$/;
|
|
8470
8483
|
COMMENT_RE = /^\/\//;
|
|
8471
8484
|
ERA_RE = /^era\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*->\s*(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
|
|
8472
|
-
MARKER_RE = /^marker
|
|
8485
|
+
MARKER_RE = /^marker:\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s+(.+)$/i;
|
|
8473
8486
|
HOLIDAY_DATE_RE = /^(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
|
|
8474
8487
|
HOLIDAY_RANGE_RE = /^(\d{4}-\d{2}-\d{2})\s*->\s*(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
|
|
8475
8488
|
WORKWEEK_RE = /^workweek:\s*(.+)$/i;
|
|
@@ -18882,11 +18895,8 @@ function calculateSchedule(parsed) {
|
|
|
18882
18895
|
for (const dep of task.dependencies) {
|
|
18883
18896
|
const resolved = resolveTaskName(dep.targetName, allTasks);
|
|
18884
18897
|
if (isResolverError(resolved)) {
|
|
18885
|
-
|
|
18886
|
-
|
|
18887
|
-
} else {
|
|
18888
|
-
return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
|
|
18889
|
-
}
|
|
18898
|
+
warn(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
|
|
18899
|
+
continue;
|
|
18890
18900
|
}
|
|
18891
18901
|
const targetNode = taskMap.get(resolved.task.id);
|
|
18892
18902
|
if (targetNode) {
|
|
@@ -18901,14 +18911,26 @@ function calculateSchedule(parsed) {
|
|
|
18901
18911
|
}
|
|
18902
18912
|
}
|
|
18903
18913
|
}
|
|
18904
|
-
|
|
18914
|
+
let sortedIds = topologicalSort(taskMap);
|
|
18905
18915
|
if (!sortedIds) {
|
|
18906
18916
|
const cycle = findCycle(taskMap);
|
|
18907
18917
|
const cycleStr = cycle.map((id) => taskMap.get(id).task.label).join(" \u2192 ");
|
|
18908
|
-
|
|
18918
|
+
warn(
|
|
18909
18919
|
taskMap.get(cycle[0]).task.lineNumber,
|
|
18910
|
-
`Circular dependency detected: ${cycleStr}
|
|
18920
|
+
`Circular dependency detected: ${cycleStr}. The cycle-creating dependency was dropped.`
|
|
18911
18921
|
);
|
|
18922
|
+
breakCycle(cycle, taskMap, depOffsetMap);
|
|
18923
|
+
sortedIds = topologicalSort(taskMap);
|
|
18924
|
+
let safety = 10;
|
|
18925
|
+
while (!sortedIds && safety-- > 0) {
|
|
18926
|
+
const nextCycle = findCycle(taskMap);
|
|
18927
|
+
if (nextCycle.length === 0) break;
|
|
18928
|
+
breakCycle(nextCycle, taskMap, depOffsetMap);
|
|
18929
|
+
sortedIds = topologicalSort(taskMap);
|
|
18930
|
+
}
|
|
18931
|
+
if (!sortedIds) {
|
|
18932
|
+
sortedIds = [...taskMap.keys()];
|
|
18933
|
+
}
|
|
18912
18934
|
}
|
|
18913
18935
|
for (const taskId of sortedIds) {
|
|
18914
18936
|
const node = taskMap.get(taskId);
|
|
@@ -19192,6 +19214,19 @@ function findCycle(taskMap) {
|
|
|
19192
19214
|
return null;
|
|
19193
19215
|
}
|
|
19194
19216
|
}
|
|
19217
|
+
function breakCycle(cycle, taskMap, depOffsetMap) {
|
|
19218
|
+
if (cycle.length < 3) return;
|
|
19219
|
+
const fromId = cycle[cycle.length - 2];
|
|
19220
|
+
const toId = cycle[0];
|
|
19221
|
+
const toNode = taskMap.get(toId);
|
|
19222
|
+
if (toNode) {
|
|
19223
|
+
const idx = toNode.predecessors.indexOf(fromId);
|
|
19224
|
+
if (idx !== -1) {
|
|
19225
|
+
toNode.predecessors.splice(idx, 1);
|
|
19226
|
+
depOffsetMap.delete(`${fromId}->${toId}`);
|
|
19227
|
+
}
|
|
19228
|
+
}
|
|
19229
|
+
}
|
|
19195
19230
|
function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
|
|
19196
19231
|
if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
|
|
19197
19232
|
const latestEnd = /* @__PURE__ */ new Map();
|
|
@@ -19280,10 +19315,10 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
|
|
|
19280
19315
|
if (!resolved?.startDate || !resolved?.endDate) continue;
|
|
19281
19316
|
if (resolved.startDate.getTime() < minStart) minStart = resolved.startDate.getTime();
|
|
19282
19317
|
if (resolved.endDate.getTime() > maxEnd) maxEnd = resolved.endDate.getTime();
|
|
19318
|
+
const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
|
|
19319
|
+
totalDuration += dur;
|
|
19283
19320
|
if (task.progress !== null) {
|
|
19284
|
-
const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
|
|
19285
19321
|
totalProgress += task.progress * dur;
|
|
19286
|
-
totalDuration += dur;
|
|
19287
19322
|
hasProgress = true;
|
|
19288
19323
|
}
|
|
19289
19324
|
}
|
|
@@ -19328,7 +19363,7 @@ import * as d3Scale from "d3-scale";
|
|
|
19328
19363
|
import * as d3Selection10 from "d3-selection";
|
|
19329
19364
|
function renderGantt(container, resolved, palette, isDark, options, exportDims) {
|
|
19330
19365
|
container.innerHTML = "";
|
|
19331
|
-
if (resolved.
|
|
19366
|
+
if (resolved.tasks.length === 0) return;
|
|
19332
19367
|
const onClickItem = options?.onClickItem;
|
|
19333
19368
|
const collapsedGroups = options?.collapsedGroups;
|
|
19334
19369
|
const onToggleGroup = options?.onToggleGroup;
|
|
@@ -19358,9 +19393,10 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19358
19393
|
const titleHeight = title ? 50 : 20;
|
|
19359
19394
|
const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
|
|
19360
19395
|
const topDateLabelReserve = 22;
|
|
19396
|
+
const CONTENT_TOP_PAD = 16;
|
|
19361
19397
|
const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve;
|
|
19362
19398
|
const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
|
|
19363
|
-
const innerHeight = contentH;
|
|
19399
|
+
const innerHeight = CONTENT_TOP_PAD + contentH;
|
|
19364
19400
|
const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
|
|
19365
19401
|
const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
|
|
19366
19402
|
const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
|
|
@@ -19398,7 +19434,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19398
19434
|
},
|
|
19399
19435
|
currentSwimlaneGroup,
|
|
19400
19436
|
onSwimlaneChange,
|
|
19401
|
-
viewMode
|
|
19437
|
+
viewMode,
|
|
19438
|
+
resolved.tasks
|
|
19402
19439
|
);
|
|
19403
19440
|
}
|
|
19404
19441
|
}
|
|
@@ -19444,7 +19481,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19444
19481
|
}
|
|
19445
19482
|
}
|
|
19446
19483
|
}
|
|
19447
|
-
let yOffset =
|
|
19484
|
+
let yOffset = CONTENT_TOP_PAD;
|
|
19448
19485
|
for (const row of rows) {
|
|
19449
19486
|
if (row.type === "lane-header") {
|
|
19450
19487
|
const laneColor = row.laneColor === "#999999" ? palette.textMuted : row.laneColor;
|
|
@@ -19493,7 +19530,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19493
19530
|
const isCollapsed = collapsedGroups?.has(group.name) ?? false;
|
|
19494
19531
|
const indent = " ".repeat(group.depth);
|
|
19495
19532
|
const toggleIcon = isCollapsed ? "\u25BA" : "\u25BC";
|
|
19496
|
-
const
|
|
19533
|
+
const tagColor = resolveTagColor(group.metadata, resolved.tagGroups, currentActiveGroup, true);
|
|
19534
|
+
const groupColor = tagColor && tagColor !== "#999999" ? tagColor : group.color || palette.textMuted;
|
|
19497
19535
|
const labelG = svg.append("g").attr("class", "gantt-group-label").attr("data-group", group.name).attr("data-line-number", String(group.lineNumber)).style("cursor", onToggleGroup ? "pointer" : "default").on("click", () => {
|
|
19498
19536
|
if (onToggleGroup) onToggleGroup(group.name);
|
|
19499
19537
|
}).on("mouseenter", () => {
|
|
@@ -19549,7 +19587,11 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19549
19587
|
const taskLabel = svg.append("text").attr("class", "gantt-task-label").attr("x", taskLabelX).attr("y", marginTop + yOffset + BAR_H / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", "11px").attr("fill", palette.text).attr("data-line-number", String(task.lineNumber)).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").text(task.label).on("click", () => {
|
|
19550
19588
|
if (onClickItem) onClickItem(task.lineNumber);
|
|
19551
19589
|
}).on("mouseenter", () => {
|
|
19552
|
-
|
|
19590
|
+
if (rt.isMilestone) {
|
|
19591
|
+
highlightMilestone(g, svg, task.id);
|
|
19592
|
+
} else {
|
|
19593
|
+
highlightTask(g, svg, task.id);
|
|
19594
|
+
}
|
|
19553
19595
|
}).on("mouseleave", () => {
|
|
19554
19596
|
resetHighlight(g, svg);
|
|
19555
19597
|
});
|
|
@@ -19563,14 +19605,14 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
|
|
|
19563
19605
|
if (rt.isMilestone) {
|
|
19564
19606
|
const mx = xScale(dateToFractionalYear(rt.startDate));
|
|
19565
19607
|
const my = yOffset + BAR_H / 2;
|
|
19566
|
-
g.append("polygon").attr("class", "gantt-milestone").attr("points", diamondPoints(mx, my, MILESTONE_SIZE)).attr("fill", barColor).attr("stroke", barColor).attr("stroke-width", 1.5).attr("data-line-number", String(task.lineNumber)).attr("data-task-name", task.label).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
|
|
19608
|
+
g.append("polygon").attr("class", "gantt-milestone").attr("points", diamondPoints(mx, my, MILESTONE_SIZE)).attr("fill", barColor).attr("stroke", barColor).attr("stroke-width", 1.5).attr("data-line-number", String(task.lineNumber)).attr("data-task-name", task.label).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
|
|
19567
19609
|
if (onClickItem) onClickItem(task.lineNumber);
|
|
19568
19610
|
}).on("mouseenter", () => {
|
|
19569
|
-
|
|
19611
|
+
highlightMilestone(g, svg, task.id);
|
|
19570
19612
|
showGanttDateIndicators(g, xScale, rt.startDate, null, innerHeight, barColor);
|
|
19571
19613
|
g.append("text").attr("class", "gantt-milestone-hover-label").attr("x", mx - MILESTONE_SIZE - 4).attr("y", my).attr("dy", "0.35em").attr("text-anchor", "end").attr("font-size", "10px").attr("fill", barColor).attr("font-weight", "600").text(task.label);
|
|
19572
19614
|
}).on("mouseleave", () => {
|
|
19573
|
-
|
|
19615
|
+
resetHighlight(g, svg);
|
|
19574
19616
|
hideGanttDateIndicators(g);
|
|
19575
19617
|
g.selectAll(".gantt-milestone-hover-label").remove();
|
|
19576
19618
|
});
|
|
@@ -19831,7 +19873,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
|
|
|
19831
19873
|
}
|
|
19832
19874
|
return iconG;
|
|
19833
19875
|
}
|
|
19834
|
-
function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode) {
|
|
19876
|
+
function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
|
|
19835
19877
|
const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
|
|
19836
19878
|
let visibleGroups;
|
|
19837
19879
|
if (activeGroupName) {
|
|
@@ -19841,6 +19883,28 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19841
19883
|
} else {
|
|
19842
19884
|
visibleGroups = tagGroups;
|
|
19843
19885
|
}
|
|
19886
|
+
const usedValues = /* @__PURE__ */ new Map();
|
|
19887
|
+
if (resolvedTasks) {
|
|
19888
|
+
for (const group of visibleGroups) {
|
|
19889
|
+
const key = group.name.toLowerCase();
|
|
19890
|
+
const used = /* @__PURE__ */ new Set();
|
|
19891
|
+
for (const rt of resolvedTasks) {
|
|
19892
|
+
const val = rt.effectiveMetadata[key];
|
|
19893
|
+
if (val) used.add(val.toLowerCase());
|
|
19894
|
+
}
|
|
19895
|
+
usedValues.set(key, used);
|
|
19896
|
+
}
|
|
19897
|
+
}
|
|
19898
|
+
const filteredEntries = /* @__PURE__ */ new Map();
|
|
19899
|
+
for (const group of visibleGroups) {
|
|
19900
|
+
const key = group.name.toLowerCase();
|
|
19901
|
+
const used = usedValues.get(key);
|
|
19902
|
+
if (used && used.size > 0) {
|
|
19903
|
+
filteredEntries.set(key, group.entries.filter((e) => used.has(e.value.toLowerCase())));
|
|
19904
|
+
} else {
|
|
19905
|
+
filteredEntries.set(key, group.entries);
|
|
19906
|
+
}
|
|
19907
|
+
}
|
|
19844
19908
|
const groupWidths = [];
|
|
19845
19909
|
let totalW = 0;
|
|
19846
19910
|
for (const group of visibleGroups) {
|
|
@@ -19851,8 +19915,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19851
19915
|
const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
|
|
19852
19916
|
let groupW = pillW;
|
|
19853
19917
|
if (isActive) {
|
|
19918
|
+
const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
|
|
19854
19919
|
let entriesW = 0;
|
|
19855
|
-
for (const entry of
|
|
19920
|
+
for (const entry of entries) {
|
|
19856
19921
|
entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
|
|
19857
19922
|
}
|
|
19858
19923
|
groupW = LEGEND_CAPSULE_PAD * 2 + pillW + 4 + entriesW;
|
|
@@ -19911,8 +19976,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
|
|
|
19911
19976
|
}
|
|
19912
19977
|
if (isActive) {
|
|
19913
19978
|
const tagKey = group.name.toLowerCase();
|
|
19979
|
+
const entries = filteredEntries.get(tagKey) ?? group.entries;
|
|
19914
19980
|
let ex = pillXOff + pillW + LEGEND_CAPSULE_PAD + 4;
|
|
19915
|
-
for (const entry of
|
|
19981
|
+
for (const entry of entries) {
|
|
19916
19982
|
const entryValue = entry.value.toLowerCase();
|
|
19917
19983
|
const entryG = gEl.append("g").attr("class", "gantt-legend-entry").style("cursor", "pointer");
|
|
19918
19984
|
entryG.append("circle").attr("cx", ex + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
|
|
@@ -19996,13 +20062,26 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
|
|
|
19996
20062
|
const color = marker.color || palette.accent || "#d08770";
|
|
19997
20063
|
const mx = xScale(parseDateToFractionalYear(marker.date));
|
|
19998
20064
|
const markerDate = parseDateStringToDate(marker.date);
|
|
19999
|
-
const
|
|
20000
|
-
|
|
20001
|
-
|
|
20002
|
-
markerG.append("
|
|
20065
|
+
const diamondSize = 5;
|
|
20066
|
+
const labelY = -24;
|
|
20067
|
+
const diamondY = labelY + 14;
|
|
20068
|
+
const markerG = g.append("g").attr("class", "gantt-marker-group").style("cursor", "pointer");
|
|
20069
|
+
markerG.append("rect").attr("x", mx - 40).attr("y", labelY - 12).attr("width", 80).attr("height", innerHeight - labelY + 12).attr("fill", "transparent").attr("pointer-events", "all");
|
|
20070
|
+
markerG.append("text").attr("class", "gantt-marker-label").attr("x", mx).attr("y", labelY).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-weight", "600").attr("fill", color).text(marker.label);
|
|
20071
|
+
markerG.append("path").attr("d", `M${mx},${diamondY - diamondSize} l${diamondSize},${diamondSize} l-${diamondSize},${diamondSize} l-${diamondSize},-${diamondSize} Z`).attr("fill", color).attr("opacity", 0.9);
|
|
20072
|
+
markerG.append("line").attr("class", "gantt-marker").attr("x1", mx).attr("y1", diamondY + diamondSize).attr("x2", mx).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "6 4").attr("opacity", 0.5);
|
|
20073
|
+
const markerLine = markerG.select(".gantt-marker");
|
|
20074
|
+
const markerLabel = markerG.select(".gantt-marker-label");
|
|
20075
|
+
const markerDiamond = markerG.select("path");
|
|
20003
20076
|
markerG.on("mouseenter", () => {
|
|
20077
|
+
markerLine.attr("opacity", 0);
|
|
20078
|
+
markerLabel.attr("opacity", 0);
|
|
20079
|
+
markerDiamond.attr("opacity", 0);
|
|
20004
20080
|
showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color);
|
|
20005
20081
|
}).on("mouseleave", () => {
|
|
20082
|
+
markerLine.attr("opacity", 0.5);
|
|
20083
|
+
markerLabel.attr("opacity", 1);
|
|
20084
|
+
markerDiamond.attr("opacity", 0.9);
|
|
20006
20085
|
hideGanttDateIndicators(g);
|
|
20007
20086
|
});
|
|
20008
20087
|
}
|
|
@@ -20077,6 +20156,7 @@ function highlightGroup(g, svg, groupName) {
|
|
|
20077
20156
|
});
|
|
20078
20157
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20079
20158
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
|
|
20159
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20080
20160
|
}
|
|
20081
20161
|
function highlightLane(g, svg, tagKey, laneName) {
|
|
20082
20162
|
const tagAttr = `data-tag-${tagKey}`;
|
|
@@ -20103,6 +20183,7 @@ function highlightLane(g, svg, tagKey, laneName) {
|
|
|
20103
20183
|
});
|
|
20104
20184
|
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20105
20185
|
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20186
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20106
20187
|
}
|
|
20107
20188
|
function highlightTask(g, svg, taskId) {
|
|
20108
20189
|
g.selectAll(".gantt-task").each(function() {
|
|
@@ -20119,6 +20200,24 @@ function highlightTask(g, svg, taskId) {
|
|
|
20119
20200
|
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20120
20201
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20121
20202
|
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20203
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20204
|
+
}
|
|
20205
|
+
function highlightMilestone(g, svg, taskId) {
|
|
20206
|
+
g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
|
|
20207
|
+
g.selectAll(".gantt-milestone").each(function() {
|
|
20208
|
+
const el = d3Selection10.select(this);
|
|
20209
|
+
el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
|
|
20210
|
+
});
|
|
20211
|
+
svg.selectAll(".gantt-task-label").each(function() {
|
|
20212
|
+
const el = d3Selection10.select(this);
|
|
20213
|
+
el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
|
|
20214
|
+
});
|
|
20215
|
+
g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
|
|
20216
|
+
svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
|
|
20217
|
+
svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
|
|
20218
|
+
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
|
|
20219
|
+
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
|
|
20220
|
+
g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
|
|
20122
20221
|
}
|
|
20123
20222
|
function highlightTaskLabel(svg, lineNumber) {
|
|
20124
20223
|
const ln = String(lineNumber);
|
|
@@ -20138,6 +20237,7 @@ function resetHighlight(g, svg) {
|
|
|
20138
20237
|
svg.selectAll(".gantt-lane-header").attr("opacity", 1);
|
|
20139
20238
|
g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", 1);
|
|
20140
20239
|
g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
|
|
20240
|
+
g.selectAll(".gantt-marker-group").attr("opacity", 1);
|
|
20141
20241
|
}
|
|
20142
20242
|
function buildRowList(resolved, collapsedGroups) {
|
|
20143
20243
|
const rows = [];
|
|
@@ -20216,9 +20316,9 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20216
20316
|
for (const entry of tagGroup.entries) {
|
|
20217
20317
|
const entryKey = entry.value.toLowerCase();
|
|
20218
20318
|
const tasks = buckets.get(entryKey) ?? [];
|
|
20319
|
+
if (tasks.length === 0) continue;
|
|
20219
20320
|
tasks.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
20220
|
-
const
|
|
20221
|
-
const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
|
|
20321
|
+
const aggregateProgress = durationWeightedProgress(tasks);
|
|
20222
20322
|
const laneStartDate = tasks.length > 0 ? new Date(Math.min(...tasks.map((t) => t.startDate.getTime()))) : null;
|
|
20223
20323
|
const laneEndDate = tasks.length > 0 ? new Date(Math.max(...tasks.map((t) => t.endDate.getTime()))) : null;
|
|
20224
20324
|
const isCollapsed = collapsedLanes?.has(entry.value) ?? false;
|
|
@@ -20240,8 +20340,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20240
20340
|
}
|
|
20241
20341
|
if (unbucketed.length > 0) {
|
|
20242
20342
|
unbucketed.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
|
|
20243
|
-
const
|
|
20244
|
-
const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
|
|
20343
|
+
const aggregateProgress = durationWeightedProgress(unbucketed);
|
|
20245
20344
|
const noLaneStartDate = unbucketed.length > 0 ? new Date(Math.min(...unbucketed.map((t) => t.startDate.getTime()))) : null;
|
|
20246
20345
|
const noLaneEndDate = unbucketed.length > 0 ? new Date(Math.max(...unbucketed.map((t) => t.endDate.getTime()))) : null;
|
|
20247
20346
|
const noLaneName = `No ${tagGroup.name}`;
|
|
@@ -20264,6 +20363,20 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
|
|
|
20264
20363
|
}
|
|
20265
20364
|
return rows;
|
|
20266
20365
|
}
|
|
20366
|
+
function durationWeightedProgress(tasks) {
|
|
20367
|
+
let totalDuration = 0;
|
|
20368
|
+
let totalProgress = 0;
|
|
20369
|
+
let hasProgress = false;
|
|
20370
|
+
for (const rt of tasks) {
|
|
20371
|
+
const dur = rt.endDate.getTime() - rt.startDate.getTime();
|
|
20372
|
+
totalDuration += dur;
|
|
20373
|
+
if (rt.task.progress !== null) {
|
|
20374
|
+
totalProgress += rt.task.progress * dur;
|
|
20375
|
+
hasProgress = true;
|
|
20376
|
+
}
|
|
20377
|
+
}
|
|
20378
|
+
return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
|
|
20379
|
+
}
|
|
20267
20380
|
function dateToFractionalYear(d) {
|
|
20268
20381
|
const y = d.getFullYear();
|
|
20269
20382
|
const startOfYear = new Date(y, 0, 1);
|
|
@@ -20290,9 +20403,28 @@ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, col
|
|
|
20290
20403
|
if (endDate && endDate.getTime() !== startDate.getTime()) {
|
|
20291
20404
|
const endPos = xScale(dateToFractionalYear(endDate));
|
|
20292
20405
|
const endLabel = formatGanttDate(endDate);
|
|
20406
|
+
const minLabelGap = 90;
|
|
20407
|
+
const gap = endPos - startPos;
|
|
20408
|
+
let startLabelX = startPos;
|
|
20409
|
+
let endLabelX = endPos;
|
|
20410
|
+
let startAnchor = "middle";
|
|
20411
|
+
let endAnchor = "middle";
|
|
20412
|
+
if (gap < minLabelGap) {
|
|
20413
|
+
const mid = (startPos + endPos) / 2;
|
|
20414
|
+
startLabelX = mid - minLabelGap / 2;
|
|
20415
|
+
endLabelX = mid + minLabelGap / 2;
|
|
20416
|
+
startAnchor = "middle";
|
|
20417
|
+
endAnchor = "middle";
|
|
20418
|
+
}
|
|
20293
20419
|
g.append("line").attr("class", "gantt-hover-date").attr("x1", endPos).attr("y1", -tickLen).attr("x2", endPos).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "4 4").attr("opacity", 0.6);
|
|
20294
|
-
g.
|
|
20295
|
-
|
|
20420
|
+
g.selectAll("text.gantt-hover-date").each(function() {
|
|
20421
|
+
const el = d3Selection10.select(this);
|
|
20422
|
+
if (el.text() === startLabel) {
|
|
20423
|
+
el.attr("x", startLabelX).attr("text-anchor", startAnchor);
|
|
20424
|
+
}
|
|
20425
|
+
});
|
|
20426
|
+
g.append("text").attr("class", "gantt-hover-date").attr("x", endLabelX).attr("y", -tickLen - 4).attr("text-anchor", endAnchor).attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
|
|
20427
|
+
g.append("text").attr("class", "gantt-hover-date").attr("x", endLabelX).attr("y", innerHeight + tickLen + 12).attr("text-anchor", endAnchor).attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
|
|
20296
20428
|
}
|
|
20297
20429
|
}
|
|
20298
20430
|
function hideGanttDateIndicators(g) {
|
|
@@ -22348,7 +22480,7 @@ function parseVisualization(content, palette) {
|
|
|
22348
22480
|
continue;
|
|
22349
22481
|
}
|
|
22350
22482
|
const markerMatch = line10.match(
|
|
22351
|
-
/^marker
|
|
22483
|
+
/^marker:\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/
|
|
22352
22484
|
);
|
|
22353
22485
|
if (markerMatch) {
|
|
22354
22486
|
const colorAnnotation = markerMatch[3]?.trim() || null;
|
|
@@ -22372,7 +22504,7 @@ function parseVisualization(content, palette) {
|
|
|
22372
22504
|
const unit = durationMatch[3];
|
|
22373
22505
|
const endDate = addDurationToDate(startDate, amount, unit);
|
|
22374
22506
|
const segments = durationMatch[5].split("|");
|
|
22375
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22507
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22376
22508
|
result.timelineEvents.push({
|
|
22377
22509
|
date: startDate,
|
|
22378
22510
|
endDate,
|
|
@@ -22389,7 +22521,7 @@ function parseVisualization(content, palette) {
|
|
|
22389
22521
|
);
|
|
22390
22522
|
if (rangeMatch) {
|
|
22391
22523
|
const segments = rangeMatch[4].split("|");
|
|
22392
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22524
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22393
22525
|
result.timelineEvents.push({
|
|
22394
22526
|
date: rangeMatch[1],
|
|
22395
22527
|
endDate: rangeMatch[2],
|
|
@@ -22406,7 +22538,7 @@ function parseVisualization(content, palette) {
|
|
|
22406
22538
|
);
|
|
22407
22539
|
if (pointMatch) {
|
|
22408
22540
|
const segments = pointMatch[2].split("|");
|
|
22409
|
-
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
|
|
22541
|
+
const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
|
|
22410
22542
|
result.timelineEvents.push({
|
|
22411
22543
|
date: pointMatch[1],
|
|
22412
22544
|
endDate: null,
|
|
@@ -25358,9 +25490,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
|
|
|
25358
25490
|
const { renderGantt: renderGantt2 } = await Promise.resolve().then(() => (init_renderer9(), renderer_exports9));
|
|
25359
25491
|
const effectivePalette2 = await resolveExportPalette(theme, palette);
|
|
25360
25492
|
const ganttParsed = parseGantt2(content, effectivePalette2);
|
|
25361
|
-
if (ganttParsed.error) return "";
|
|
25362
25493
|
const resolved = calculateSchedule2(ganttParsed);
|
|
25363
|
-
if (resolved.
|
|
25494
|
+
if (resolved.tasks.length === 0) return "";
|
|
25364
25495
|
const EXPORT_W = 1200;
|
|
25365
25496
|
const EXPORT_H = 800;
|
|
25366
25497
|
const container2 = createExportContainer(EXPORT_W, EXPORT_H);
|