Phase 6F as timeline SVG visual-state rendering
This commit is contained in:
parent
f7cf477f70
commit
b346379de2
@ -859,6 +859,49 @@ body.timeline-inspector-resizing {
|
||||
opacity: 0.58;
|
||||
}
|
||||
|
||||
.plot-line-svg-path.plotline-state-neglected {
|
||||
stroke-dasharray: 9 8;
|
||||
opacity: 0.44;
|
||||
}
|
||||
|
||||
.plot-line-svg-path.plotline-state-resolved,
|
||||
.plot-line-svg-path.plotline-state-questionable {
|
||||
stroke: #8c9491;
|
||||
opacity: 0.42;
|
||||
}
|
||||
|
||||
.plot-line-svg-path.plotline-state-abandoned {
|
||||
stroke: #9b2f36;
|
||||
stroke-dasharray: 5 7;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.plot-event-svg-node.plotline-state-resolved circle,
|
||||
.plot-event-svg-node.plotline-state-questionable circle {
|
||||
fill: #8c9491;
|
||||
}
|
||||
|
||||
.plot-event-svg-node.plotline-state-abandoned circle,
|
||||
.plot-event-svg-node.plotline-state-abandoned path,
|
||||
.plot-event-svg-node.plotline-state-abandoned line {
|
||||
fill: #9b2f36;
|
||||
stroke: #9b2f36;
|
||||
}
|
||||
|
||||
.plotline-state-questionable-marker path {
|
||||
fill: #f4c430;
|
||||
stroke: #8a6712;
|
||||
stroke-width: 1.5;
|
||||
}
|
||||
|
||||
.plotline-state-questionable-marker line,
|
||||
.plotline-state-questionable-marker circle {
|
||||
stroke: #5e4710;
|
||||
fill: #5e4710;
|
||||
stroke-width: 1.6;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.plot-event-svg-node circle,
|
||||
.plot-event-svg-node path {
|
||||
stroke: #ffffff;
|
||||
|
||||
2
PlotLine/wwwroot/css/site.min.css
vendored
2
PlotLine/wwwroot/css/site.min.css
vendored
File diff suppressed because one or more lines are too long
@ -1389,6 +1389,7 @@
|
||||
return {
|
||||
x: slotRect.left - boardRect.left + slotRect.width / 2,
|
||||
y: rowCenterY(row),
|
||||
sceneIndex: 0,
|
||||
type: (marker.dataset.plotEventType || "Progress").toLowerCase(),
|
||||
targetPlotLineId: marker.dataset.targetPlotLineId || "",
|
||||
targetPlotLineIds: (marker.dataset.targetPlotLineIds || "")
|
||||
@ -1398,6 +1399,14 @@
|
||||
};
|
||||
};
|
||||
|
||||
const sceneXs = [...new Set([...board.querySelectorAll("[data-plot-line-slot]")]
|
||||
.map((slot) => {
|
||||
const slotRect = slot.getBoundingClientRect();
|
||||
return Math.round(slotRect.left - boardRect.left + slotRect.width / 2);
|
||||
}))]
|
||||
.sort((a, b) => a - b);
|
||||
const latestSceneIndex = Math.max(sceneXs.length - 1, 0);
|
||||
|
||||
const rowLookup = new Map();
|
||||
board.querySelectorAll("[data-plot-line-row]").forEach((row) => {
|
||||
if (row.dataset.plotLineId) {
|
||||
@ -1412,13 +1421,38 @@
|
||||
points: [...row.querySelectorAll("[data-plot-event-node]")]
|
||||
.map((marker) => pointFor(row, marker))
|
||||
.filter(Boolean)
|
||||
.map((point) => ({
|
||||
...point,
|
||||
sceneIndex: Math.max(0, sceneXs.findIndex((x) => x === Math.round(point.x)))
|
||||
}))
|
||||
.sort((a, b) => a.x - b.x),
|
||||
incomingStartXs: []
|
||||
}));
|
||||
const rowDataById = new Map(rowData.map((item) => [item.plotLineId, item]));
|
||||
|
||||
const drawNode = (point, colour) => {
|
||||
const group = makeSvg("g", { class: `plot-event-svg-node plot-event-svg-node-${point.type}` });
|
||||
const stateFor = (points) => {
|
||||
const hasResolve = points.some((point) => point.type === "resolve");
|
||||
const hasAbandon = points.some((point) => point.type === "abandon");
|
||||
const hasStructuralTarget = points.some((point) =>
|
||||
(point.type === "branch" && point.targetPlotLineId) ||
|
||||
(point.type === "merge" && point.targetPlotLineId) ||
|
||||
(point.type === "split" && point.targetPlotLineIds.length >= 2));
|
||||
const lastSceneIndex = points.reduce((max, point) => Math.max(max, point.sceneIndex), 0);
|
||||
const isNeglected = !hasResolve && !hasAbandon && latestSceneIndex - lastSceneIndex >= 10;
|
||||
|
||||
if (hasAbandon) {
|
||||
return "abandoned";
|
||||
}
|
||||
|
||||
if (hasResolve) {
|
||||
return hasStructuralTarget ? "resolved" : "questionable";
|
||||
}
|
||||
|
||||
return isNeglected ? "neglected" : "active";
|
||||
};
|
||||
|
||||
const drawNode = (point, colour, state) => {
|
||||
const group = makeSvg("g", { class: `plot-event-svg-node plot-event-svg-node-${point.type} plotline-state-${state}` });
|
||||
if (point.type === "twist") {
|
||||
group.append(makeSvg("path", {
|
||||
d: `M ${point.x} ${point.y - 7} L ${point.x + 7} ${point.y} L ${point.x} ${point.y + 7} L ${point.x - 7} ${point.y} Z`,
|
||||
@ -1436,6 +1470,19 @@
|
||||
svg.append(group);
|
||||
};
|
||||
|
||||
const drawQuestionableMarker = (point) => {
|
||||
const group = makeSvg("g", { class: "plotline-state-questionable-marker" });
|
||||
const title = makeSvg("title");
|
||||
title.textContent = "Resolved without merge, split, or branch. Verify this thread has sufficient payoff.";
|
||||
group.append(title);
|
||||
group.append(makeSvg("path", {
|
||||
d: `M ${point.x + 12} ${point.y - 13} L ${point.x + 22} ${point.y + 5} L ${point.x + 2} ${point.y + 5} Z`
|
||||
}));
|
||||
group.append(makeSvg("line", { x1: point.x + 12, y1: point.y - 7, x2: point.x + 12, y2: point.y - 1 }));
|
||||
group.append(makeSvg("circle", { cx: point.x + 12, cy: point.y + 2, r: 1.4 }));
|
||||
svg.append(group);
|
||||
};
|
||||
|
||||
const drawConnector = (source, targetRow, colour, type) => {
|
||||
if (!targetRow) {
|
||||
if (window.console?.warn) {
|
||||
@ -1468,7 +1515,11 @@
|
||||
});
|
||||
});
|
||||
|
||||
rowData.forEach(({ colour, points, incomingStartXs }) => {
|
||||
rowData.forEach((item) => {
|
||||
item.state = stateFor(item.points);
|
||||
});
|
||||
|
||||
rowData.forEach(({ colour, points, incomingStartXs, state }) => {
|
||||
if (points.length > 0) {
|
||||
const explicitStart = points.find((point) => point.type === "start");
|
||||
const incomingStartX = incomingStartXs.length ? Math.min(...incomingStartXs) : null;
|
||||
@ -1482,7 +1533,7 @@
|
||||
|
||||
if (endX > startX) {
|
||||
svg.append(makeSvg("path", {
|
||||
class: "plot-line-svg-path",
|
||||
class: `plot-line-svg-path plotline-state-${state}`,
|
||||
d: `M ${startX} ${first.y} L ${endX} ${first.y}`,
|
||||
stroke: colour
|
||||
}));
|
||||
@ -1506,8 +1557,13 @@
|
||||
});
|
||||
});
|
||||
|
||||
rowData.forEach(({ points, colour }) => {
|
||||
points.forEach((point) => drawNode(point, colour));
|
||||
rowData.forEach(({ points, colour, state }) => {
|
||||
points.forEach((point) => {
|
||||
drawNode(point, colour, state);
|
||||
if (state === "questionable" && point.type === "resolve") {
|
||||
drawQuestionableMarker(point);
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
2
PlotLine/wwwroot/js/site.min.js
vendored
2
PlotLine/wwwroot/js/site.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
x
Reference in New Issue
Block a user