Implemented the Phase C1C polish pass.

This commit is contained in:
Nick Beckley 2026-06-19 21:43:34 +01:00
parent 377d0540ca
commit ca63a994dc
4 changed files with 90 additions and 13 deletions

View File

@ -1067,7 +1067,8 @@ body.timeline-inspector-resizing {
padding-top: 14px;
}
.plot-lane-svg-overlay {
.plot-lane-svg-overlay,
.plot-lane-warning-overlay {
position: absolute;
inset: 0 auto auto 0;
z-index: 4;
@ -1075,6 +1076,10 @@ body.timeline-inspector-resizing {
overflow: visible;
}
.plot-lane-warning-overlay {
z-index: 8;
}
.plot-line-svg-path {
fill: none;
stroke-width: 3.5;
@ -1286,6 +1291,7 @@ body.timeline-inspector-resizing {
.thread-marker {
position: relative;
z-index: 5;
left: var(--thread-marker-offset-x, 0);
display: inline-flex;
align-items: center;
justify-content: center;

File diff suppressed because one or more lines are too long

View File

@ -1589,14 +1589,26 @@
if (!svg) {
return;
}
let warningSvg = board.querySelector("[data-plot-line-warning-svg]");
if (!warningSvg) {
warningSvg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
warningSvg.setAttribute("class", "plot-lane-warning-overlay");
warningSvg.setAttribute("data-plot-line-warning-svg", "true");
warningSvg.setAttribute("aria-hidden", "true");
board.append(warningSvg);
}
svg.replaceChildren();
warningSvg.replaceChildren();
const boardRect = board.getBoundingClientRect();
const width = Math.max(board.scrollWidth, boardRect.width);
const height = Math.max(board.scrollHeight, boardRect.height);
svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
svg.setAttribute("width", String(width));
svg.setAttribute("height", String(height));
warningSvg.setAttribute("viewBox", `0 0 ${width} ${height}`);
warningSvg.setAttribute("width", String(width));
warningSvg.setAttribute("height", String(height));
const makeSvg = (name, attributes = {}) => {
const element = document.createElementNS("http://www.w3.org/2000/svg", name);
@ -1609,6 +1621,60 @@
return rowRect.top - boardRect.top + rowRect.height / 2;
};
const resetMarkerOffsets = () => {
board.querySelectorAll("[data-plot-event-node]").forEach((marker) => {
marker.style.removeProperty("--thread-marker-offset-x");
});
};
const markerSceneId = (marker) => marker.closest("[data-plot-line-slot]")?.dataset.sceneId || "";
const safeMarkerOffset = (marker) => {
const slot = marker.closest("[data-plot-line-slot]");
if (!slot) {
return 0;
}
const slotRect = slot.getBoundingClientRect();
const markerRect = marker.getBoundingClientRect();
return Math.max(0, Math.min(22, (slotRect.width - markerRect.width) / 2 - 6));
};
const applySameSceneSplitOffsets = () => {
resetMarkerOffsets();
const rowsById = new Map([...board.querySelectorAll("[data-plot-line-row]")]
.filter((row) => row.dataset.plotLineId)
.map((row) => [row.dataset.plotLineId, row]));
const offsets = new Map();
const addOffset = (marker, direction) => {
const offset = safeMarkerOffset(marker);
if (offset > 0) {
offsets.set(marker, direction * offset);
}
};
board.querySelectorAll("[data-plot-event-node]").forEach((marker) => {
const type = (marker.dataset.plotEventType || "").toLowerCase();
if (type !== "branch" && type !== "split") {
return;
}
const targetIds = type === "branch"
? [marker.dataset.targetPlotLineId || ""].filter(Boolean)
: (marker.dataset.targetPlotLineIds || "").split(",").map((value) => value.trim()).filter(Boolean);
targetIds.forEach((targetId) => {
const firstTargetMarker = rowsById.get(targetId)?.querySelector("[data-plot-event-node]");
if (firstTargetMarker && markerSceneId(marker) === markerSceneId(firstTargetMarker)) {
addOffset(marker, -1);
addOffset(firstTargetMarker, 1);
}
});
});
offsets.forEach((offset, marker) => marker.style.setProperty("--thread-marker-offset-x", `${offset}px`));
};
applySameSceneSplitOffsets();
const pointFor = (row, marker) => {
const slot = marker.closest("[data-plot-line-slot]");
if (!slot) {
@ -1734,10 +1800,10 @@
}
}
svg.append(group);
warningSvg.append(group);
};
const drawConnector = (source, targetRow, colour, type) => {
const drawConnector = (source, targetRow, colour, type, targetPlotLineId = "") => {
if (!targetRow) {
if (window.console?.warn) {
window.console.warn(`Plot line ${type} connector skipped because target plot line data is missing.`);
@ -1745,15 +1811,20 @@
return;
}
const target = { x: source.x, y: rowCenterY(targetRow) };
const flowX = Math.max(28, Math.min(46, Math.abs(target.y - source.y) * 0.28));
const targetPoint = rowDataById.get(targetPlotLineId)?.points[0];
const target = targetPoint || { x: source.x + 42, y: rowCenterY(targetRow), radius: 10 };
const sourceRadius = Math.max(8, source.radius || 12);
const startX = source.x + sourceRadius;
const targetX = startX + flowX;
const midX = startX + flowX / 2;
const targetRadius = Math.max(8, target.radius || 12);
const direction = target.x >= source.x ? 1 : -1;
const startX = source.x + direction * sourceRadius;
const endX = target.x - direction * targetRadius;
const flowX = Math.max(24, Math.min(52, Math.abs(target.y - source.y) * 0.28 + Math.abs(endX - startX) * 0.2));
const controlOffset = direction * flowX;
const firstControlX = startX + controlOffset;
const secondControlX = endX - controlOffset;
svg.append(makeSvg("path", {
class: `plot-line-svg-connector plot-line-svg-connector-${type}`,
d: `M ${startX} ${source.y} C ${midX} ${source.y}, ${midX} ${target.y}, ${targetX} ${target.y}`,
d: `M ${startX} ${source.y} C ${firstControlX} ${source.y}, ${secondControlX} ${target.y}, ${endX} ${target.y}`,
stroke: colour
}));
};
@ -1818,12 +1889,12 @@
rowData.forEach(({ points, colour }) => {
points.forEach((point) => {
if (point.type === "branch" || point.type === "merge") {
drawConnector(point, rowLookup.get(point.targetPlotLineId), colour, point.type);
drawConnector(point, rowLookup.get(point.targetPlotLineId), colour, point.type, point.targetPlotLineId);
}
if (point.type === "split" && point.targetPlotLineIds.length >= 2) {
point.targetPlotLineIds.forEach((targetPlotLineId) => {
drawConnector(point, rowLookup.get(targetPlotLineId), colour, point.type);
drawConnector(point, rowLookup.get(targetPlotLineId), colour, point.type, targetPlotLineId);
});
} else if (point.type === "split" && point.targetPlotLineIds.length > 0 && window.console?.warn) {
window.console.warn("Plot line split connector skipped because fewer than two targets are available.");

File diff suppressed because one or more lines are too long