Phase 6D with the SVG rendering
This commit is contained in:
parent
ba5960f907
commit
5831172b1d
@ -819,7 +819,8 @@ else
|
||||
{
|
||||
if (Model.PlotLanes.Any() && Model.OrderedScenes.Any())
|
||||
{
|
||||
<div class="plot-lane-board" data-timeline-section="plot-lines" style="--scene-count:@timelineColumns.Count">
|
||||
<div class="plot-lane-board" data-timeline-section="plot-lines" data-plot-line-svg-board style="--scene-count:@timelineColumns.Count">
|
||||
<svg class="plot-lane-svg-overlay" data-plot-line-svg aria-hidden="true"></svg>
|
||||
<div class="lane-board-heading plot-lane-board-heading">
|
||||
<button type="button" data-collapse-toggle="plot-lines">Plot line lanes</button>
|
||||
<small>Thread event markers by scene</small>
|
||||
@ -834,7 +835,7 @@ else
|
||||
{
|
||||
if (lane.PlotLine.PlotLineTypeName != group.Key) { continue; }
|
||||
var laneFocusClass = Model.Filter.FocusType == "plotline" && Model.Filter.FocusID.HasValue && Model.Filter.FocusID != lane.PlotLine.PlotLineID ? "lane-dim" : "";
|
||||
<div class="plot-lane-row @laneFocusClass" style="--plot-colour:@lane.PlotLine.Colour">
|
||||
<div class="plot-lane-row @laneFocusClass" data-plot-line-row data-plot-line-id="@lane.PlotLine.PlotLineID" data-plot-colour="@lane.PlotLine.Colour" style="--plot-colour:@lane.PlotLine.Colour">
|
||||
<a class="plot-lane-label" asp-controller="PlotLines" asp-action="Edit" asp-route-id="@lane.PlotLine.PlotLineID">
|
||||
<span class="colour-swatch" style="background:@lane.PlotLine.Colour"></span>
|
||||
<strong>@lane.PlotLine.PlotLineName</strong>
|
||||
@ -849,7 +850,7 @@ else
|
||||
}
|
||||
|
||||
var slot = lane.SceneSlots.FirstOrDefault(x => x.Scene.SceneID == realPlotScene.SceneID);
|
||||
<div class="plot-lane-slot" title="Scene @realPlotScene.SceneNumber: @realPlotScene.SceneTitle">
|
||||
<div class="plot-lane-slot" data-plot-line-slot data-scene-id="@realPlotScene.SceneID" title="Scene @realPlotScene.SceneNumber: @realPlotScene.SceneTitle">
|
||||
@foreach (var threadEvent in slot?.Events ?? Enumerable.Empty<ThreadEvent>())
|
||||
{
|
||||
var markerClass = threadEvent.EventTypeName is "Resolved" or "Payoff"
|
||||
@ -858,6 +859,8 @@ else
|
||||
? "contradicted"
|
||||
: "";
|
||||
<a class="thread-marker @markerClass"
|
||||
data-plot-event-node
|
||||
data-plot-event-type="@threadEvent.PlotEventTypeName"
|
||||
asp-controller="Timeline"
|
||||
asp-action="Index"
|
||||
asp-route-ProjectID="@Model.Project.ProjectID"
|
||||
|
||||
@ -827,6 +827,7 @@ body.timeline-inspector-resizing {
|
||||
}
|
||||
|
||||
.plot-lane-board {
|
||||
position: relative;
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
width: max-content;
|
||||
@ -836,6 +837,42 @@ body.timeline-inspector-resizing {
|
||||
padding-top: 14px;
|
||||
}
|
||||
|
||||
.plot-lane-svg-overlay {
|
||||
position: absolute;
|
||||
inset: 0 auto auto 0;
|
||||
z-index: 4;
|
||||
pointer-events: none;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.plot-line-svg-path {
|
||||
fill: none;
|
||||
stroke-width: 4;
|
||||
stroke-linecap: round;
|
||||
opacity: 0.72;
|
||||
}
|
||||
|
||||
.plot-event-svg-node circle,
|
||||
.plot-event-svg-node path {
|
||||
stroke: #ffffff;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.plot-event-svg-node line {
|
||||
stroke: #8b1e2d;
|
||||
stroke-width: 4;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
|
||||
.plot-event-svg-node-reveal circle {
|
||||
stroke-width: 3;
|
||||
}
|
||||
|
||||
.plot-event-svg-node-resolve circle:last-child {
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.lane-board-heading,
|
||||
.metric-lane-board-heading,
|
||||
.plot-lane-board-heading {
|
||||
@ -927,6 +964,10 @@ body.timeline-inspector-resizing {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.plot-lane-board [data-plot-event-node] {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.thread-marker:hover {
|
||||
color: var(--plotline-ink);
|
||||
background: color-mix(in srgb, var(--plot-colour), #ffffff 58%);
|
||||
|
||||
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
@ -1307,6 +1307,7 @@
|
||||
densitySelect.value = nextValue;
|
||||
}
|
||||
localStorage.setItem(densityKey, nextValue);
|
||||
window.requestAnimationFrame(renderPlotLineSvg);
|
||||
};
|
||||
|
||||
const applyZoom = (value) => {
|
||||
@ -1316,6 +1317,7 @@
|
||||
zoomSelect.value = nextValue;
|
||||
}
|
||||
localStorage.setItem(zoomKey, nextValue);
|
||||
window.requestAnimationFrame(renderPlotLineSvg);
|
||||
};
|
||||
|
||||
const getScene = (sceneId) => root.querySelector(`[data-scene-id='${CSS.escape(String(sceneId))}']`);
|
||||
@ -1351,6 +1353,83 @@
|
||||
|
||||
const saveCollapsed = () => localStorage.setItem(collapsedKey, JSON.stringify([...collapsed]));
|
||||
|
||||
const renderPlotLineSvg = () => {
|
||||
root.querySelectorAll("[data-plot-line-svg-board]").forEach((board) => {
|
||||
const svg = board.querySelector("[data-plot-line-svg]");
|
||||
if (!svg) {
|
||||
return;
|
||||
}
|
||||
|
||||
svg.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));
|
||||
|
||||
const makeSvg = (name, attributes = {}) => {
|
||||
const element = document.createElementNS("http://www.w3.org/2000/svg", name);
|
||||
Object.entries(attributes).forEach(([key, value]) => element.setAttribute(key, String(value)));
|
||||
return element;
|
||||
};
|
||||
|
||||
const pointFor = (row, marker) => {
|
||||
const slot = marker.closest("[data-plot-line-slot]");
|
||||
if (!slot) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const slotRect = slot.getBoundingClientRect();
|
||||
const rowRect = row.getBoundingClientRect();
|
||||
return {
|
||||
x: slotRect.left - boardRect.left + slotRect.width / 2,
|
||||
y: rowRect.top - boardRect.top + rowRect.height / 2,
|
||||
type: (marker.dataset.plotEventType || "Progress").toLowerCase()
|
||||
};
|
||||
};
|
||||
|
||||
const drawNode = (point, colour) => {
|
||||
const group = makeSvg("g", { class: `plot-event-svg-node plot-event-svg-node-${point.type}` });
|
||||
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`,
|
||||
fill: colour
|
||||
}));
|
||||
} else if (point.type === "abandon") {
|
||||
group.append(makeSvg("line", { x1: point.x - 6, y1: point.y - 6, x2: point.x + 6, y2: point.y + 6 }));
|
||||
group.append(makeSvg("line", { x1: point.x + 6, y1: point.y - 6, x2: point.x - 6, y2: point.y + 6 }));
|
||||
} else {
|
||||
group.append(makeSvg("circle", { cx: point.x, cy: point.y, r: 6, fill: colour }));
|
||||
if (point.type === "resolve") {
|
||||
group.append(makeSvg("circle", { cx: point.x, cy: point.y, r: 10, fill: "none", stroke: colour }));
|
||||
}
|
||||
}
|
||||
svg.append(group);
|
||||
};
|
||||
|
||||
board.querySelectorAll("[data-plot-line-row]").forEach((row) => {
|
||||
const colour = row.dataset.plotColour || "#2f6f63";
|
||||
const points = [...row.querySelectorAll("[data-plot-event-node]")]
|
||||
.map((marker) => pointFor(row, marker))
|
||||
.filter(Boolean)
|
||||
.sort((a, b) => a.x - b.x);
|
||||
|
||||
if (points.length > 1) {
|
||||
const first = points[0];
|
||||
const last = points[points.length - 1];
|
||||
svg.append(makeSvg("path", {
|
||||
class: "plot-line-svg-path",
|
||||
d: `M ${first.x} ${first.y} L ${last.x} ${last.y}`,
|
||||
stroke: colour
|
||||
}));
|
||||
}
|
||||
|
||||
points.forEach((point) => drawNode(point, colour));
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const setCollapsed = (id, shouldCollapse) => {
|
||||
document.querySelectorAll(`[data-collapse-body='${CSS.escape(id)}']`).forEach((body) => {
|
||||
body.classList.toggle("timeline-section-collapsed", shouldCollapse);
|
||||
@ -1378,6 +1457,7 @@
|
||||
}
|
||||
setCollapsed(id, nextCollapsed);
|
||||
saveCollapsed();
|
||||
renderPlotLineSvg();
|
||||
window.dispatchEvent(new Event("plotline:timeline-layout-changed"));
|
||||
});
|
||||
});
|
||||
@ -1394,12 +1474,16 @@
|
||||
}
|
||||
})();
|
||||
window.requestAnimationFrame(() => {
|
||||
renderPlotLineSvg();
|
||||
scrollActiveChapterIntoView("auto");
|
||||
if (hadPendingScrollRestore) {
|
||||
restoreScrollPosition();
|
||||
}
|
||||
});
|
||||
|
||||
window.addEventListener("resize", renderPlotLineSvg);
|
||||
window.addEventListener("plotline:timeline-layout-changed", renderPlotLineSvg);
|
||||
|
||||
const saveScrollForSceneSelection = (event) => {
|
||||
if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) {
|
||||
return;
|
||||
|
||||
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