Looking even better

This commit is contained in:
Colin Dawson 2026-02-26 00:21:55 +00:00
parent b1f502f8d7
commit e867e530b4

View File

@ -85,8 +85,25 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
const predecessors = task.config.predecessors as string[] | undefined;
if (!predecessors || predecessors.length === 0) {
columnByGuid.set(guid, 0);
return 0;
// Spread root tasks across columns when there are multiple starts
const rootTasks = tasks.filter((t) => {
const preds = t.config.predecessors as string[] | undefined;
return !preds || preds.length === 0;
});
rootTasks.sort((a, b) => {
const nameA = (a.config.name as string) || a.type || "";
const nameB = (b.config.name as string) || b.type || "";
return nameA.localeCompare(nameB);
});
const rootIndex = rootTasks.findIndex(
(t) => (t.config.guid as string) === guid,
);
const column = Math.max(0, rootIndex);
columnByGuid.set(guid, column);
return column;
}
// If multiple predecessors, position task at the average of their columns
@ -241,6 +258,13 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
return found?.positions ?? [];
};
const getColumnPercent = (column: number): number => {
const numColumns = columnRange.max - columnRange.min + 1;
if (numColumns <= 1) return 50;
const gridCol = column - columnRange.min;
return ((gridCol + 0.5) / numColumns) * 100;
};
// Calculate connector positions based on grid columns
const getConnectorPositionsByColumn = (
levelTasks: CreateWorkflowTemplateVersion["tasks"][],
@ -275,6 +299,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
const getPredecessorPositionsByColumn = (
levelTasks: CreateWorkflowTemplateVersion["tasks"][],
excludeTasks?: Set<string>,
): number[] => {
const numColumns = columnRange.max - columnRange.min + 1;
if (numColumns <= 1) return [50];
@ -284,6 +309,9 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
levelTasks.forEach((task) => {
const preds = (task.config.predecessors as string[]) ?? [];
preds.forEach((predGuid) => {
// Skip excluded tasks (e.g., those with override)
if (excludeTasks && excludeTasks.has(predGuid)) return;
const predColumn = taskColumnMap.current.get(predGuid);
if (predColumn !== undefined) {
predColumns.add(predColumn);
@ -291,10 +319,9 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
});
});
return Array.from(predColumns).map((predColumn) => {
const gridCol = predColumn - columnRange.min;
return ((gridCol + 0.5) / numColumns) * 100;
});
return Array.from(predColumns).map((predColumn) =>
getColumnPercent(predColumn),
);
};
const renderConnector = (
@ -402,7 +429,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
);
}
// Otherwise render SVG with line from start to end (always, even if vertical)
// Render orthogonal connector (vertical-horizontal-vertical)
return (
<svg
aria-hidden="true"
@ -410,12 +437,31 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
viewBox="0 0 100 40"
preserveAspectRatio="none"
>
{/* Vertical line from predecessor */}
<line
className="visualiser-connector-branch-line"
x1={startX}
y1="0"
x2={startX}
y2="14"
/>
{/* Horizontal line connecting to child column */}
{startX !== endX && (
<line
className="visualiser-connector-branch-line"
x1={startX}
y1="14"
x2={endX}
y2="14"
/>
)}
{/* Vertical line down to child */}
<line
className="visualiser-connector-branch-line"
x1={endX}
y1="14"
x2={endX}
y2="40"
y2="34"
/>
<polygon
className="visualiser-connector-branch-arrow"
@ -457,6 +503,11 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
);
}
const topX =
predecessorPositions && predecessorPositions.length > 0
? predecessorPositions[0]
: 50;
return (
<svg
aria-hidden="true"
@ -466,9 +517,9 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
>
<line
className="visualiser-connector-branch-line"
x1="50"
x1={topX}
y1="0"
x2="50"
x2={topX}
y2="14"
/>
<line
@ -547,6 +598,11 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
return connections;
};
const rootTaskCount = tasks.filter((task) => {
const preds = task.config.predecessors as string[] | undefined;
return !preds || preds.length === 0;
}).length;
return (
<div className="visualiser-root">
<div className="visualiser-container" ref={visualiserContainerRef}>
@ -585,7 +641,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
let styleObj: React.CSSProperties = {};
if (isRootTask && numColumns > 1) {
if (isRootTask && numColumns > 1 && rootTaskCount === 1) {
// Root tasks span all columns and center their content
styleObj = {
gridColumn: `1 / -1`,
@ -644,12 +700,89 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
</div>
</div>
{index < levels.length - 1
? renderConnector(
levels[index + 1].length,
getConnectorPositionsByColumn(levels[index + 1]),
getPredecessorPositionsByColumn(levels[index + 1]),
levels[index + 1],
)
? (() => {
const currentLevel = levels[index];
const nextLevel = levels[index + 1];
// Tasks with override should not have normal progression connectors
const overrideTasks = new Set(
currentLevel
.filter(
(task) =>
task.config.overrideDefaultTaskProgression === true,
)
.map((task) => task.config.guid as string),
);
const hasMergeTask = nextLevel.some((task) => {
const preds =
(task.config.predecessors as string[]) ?? [];
// Only consider predecessors without override
const validPreds = preds.filter(
(predGuid) => !overrideTasks.has(predGuid),
);
if (validPreds.length <= 1) return false;
const predColumns = validPreds
.map((predGuid) => taskColumnMap.current.get(predGuid))
.filter((col) => col !== undefined) as number[];
return new Set(predColumns).size > 1;
});
if (hasMergeTask) {
// Filter to tasks with at least one valid predecessor
const tasksWithValidPreds = nextLevel.filter((task) => {
const preds =
(task.config.predecessors as string[]) ?? [];
return preds.some(
(predGuid) => !overrideTasks.has(predGuid),
);
});
return renderConnector(
tasksWithValidPreds.length,
getConnectorPositionsByColumn(tasksWithValidPreds),
getPredecessorPositionsByColumn(
tasksWithValidPreds,
overrideTasks,
),
tasksWithValidPreds,
);
}
const groups = new Map<
string,
CreateWorkflowTemplateVersion["tasks"][]
>();
nextLevel.forEach((task) => {
const preds =
(task.config.predecessors as string[]) ?? [];
preds.forEach((predGuid) => {
// Skip predecessors with override
if (overrideTasks.has(predGuid)) return;
const list = groups.get(predGuid) ?? [];
list.push(task);
groups.set(predGuid, list);
});
});
return Array.from(groups.entries()).map(
([predGuid, children]) => {
const predColumn = taskColumnMap.current.get(predGuid);
if (predColumn === undefined) return null;
return renderConnector(
children.length,
getConnectorPositionsByColumn(children),
[getColumnPercent(predColumn)],
children,
);
},
);
})()
: null}
</React.Fragment>
))}