The visualiser now has on hover highlighting
This commit is contained in:
parent
f506f047b8
commit
1a3aaac5b3
@ -46,6 +46,23 @@
|
||||
text-align: center;
|
||||
letter-spacing: 0.2px;
|
||||
box-shadow: 0 6px 14px rgba($blue, 0.2);
|
||||
transition:
|
||||
opacity 0.16s ease,
|
||||
transform 0.16s ease,
|
||||
box-shadow 0.16s ease;
|
||||
}
|
||||
|
||||
.visualiser-node.is-hovered {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 10px 22px rgba($blue, 0.33);
|
||||
}
|
||||
|
||||
.visualiser-node.is-related {
|
||||
box-shadow: 0 8px 18px rgba($blue, 0.27);
|
||||
}
|
||||
|
||||
.visualiser-node.is-dim {
|
||||
opacity: 0.45;
|
||||
}
|
||||
|
||||
.visualiser-node-content {
|
||||
@ -160,6 +177,10 @@
|
||||
stroke-width: 2px;
|
||||
stroke-dasharray: 4, 4;
|
||||
vector-effect: non-scaling-stroke;
|
||||
transition:
|
||||
opacity 0.16s ease,
|
||||
stroke-width 0.16s ease,
|
||||
stroke 0.16s ease;
|
||||
}
|
||||
|
||||
.visualiser-outcome-connector-arrow {
|
||||
@ -176,6 +197,27 @@
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
user-select: none;
|
||||
transition:
|
||||
opacity 0.16s ease,
|
||||
fill 0.16s ease;
|
||||
}
|
||||
|
||||
.visualiser-outcome-connector-line.is-active {
|
||||
stroke-width: 2.6px;
|
||||
stroke: rgba($blue, 0.75);
|
||||
stroke-dasharray: 0;
|
||||
}
|
||||
|
||||
.visualiser-outcome-connector-line.is-dim {
|
||||
opacity: 0.2;
|
||||
}
|
||||
|
||||
.visualiser-outcome-connector-label.is-active {
|
||||
fill: darken($blue, 8%);
|
||||
}
|
||||
|
||||
.visualiser-outcome-connector-label.is-dim {
|
||||
opacity: 0.25;
|
||||
}
|
||||
|
||||
@include color-mode(dark) {
|
||||
|
||||
@ -295,6 +295,7 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
}, []);
|
||||
const levels = React.useMemo(() => buildLevels(tasks), [tasks]);
|
||||
const edges = React.useMemo(() => buildEdges(tasks), [tasks]);
|
||||
const [hoveredGuid, setHoveredGuid] = React.useState<string | null>(null);
|
||||
const levelByGuid = React.useMemo(() => {
|
||||
const levelMap = new Map<string, number>();
|
||||
levels.forEach((level, levelIndex) => {
|
||||
@ -408,6 +409,31 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
|
||||
return sharedLaneByTarget;
|
||||
}, [edges, levelByGuid, edgeLaneOffsetByKey, nodePositions]);
|
||||
const connectedByGuid = React.useMemo(() => {
|
||||
const map = new Map<string, Set<string>>();
|
||||
|
||||
tasks.forEach((task) => {
|
||||
map.set(task.config.guid as string, new Set<string>());
|
||||
});
|
||||
|
||||
edges.forEach((edge) => {
|
||||
const source = map.get(edge.sourceGuid) ?? new Set<string>();
|
||||
source.add(edge.targetGuid);
|
||||
map.set(edge.sourceGuid, source);
|
||||
|
||||
const target = map.get(edge.targetGuid) ?? new Set<string>();
|
||||
target.add(edge.sourceGuid);
|
||||
map.set(edge.targetGuid, target);
|
||||
});
|
||||
|
||||
return map;
|
||||
}, [tasks, edges]);
|
||||
const relatedGuids = React.useMemo(() => {
|
||||
if (!hoveredGuid) {
|
||||
return new Set<string>();
|
||||
}
|
||||
return connectedByGuid.get(hoveredGuid) ?? new Set<string>();
|
||||
}, [hoveredGuid, connectedByGuid]);
|
||||
|
||||
React.useEffect(() => {
|
||||
const measurePositions = () => {
|
||||
@ -470,6 +496,22 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
};
|
||||
}, [levels]);
|
||||
|
||||
const labelCollisionState = new Map<string, number[]>();
|
||||
const getAdjustedLabelY = (x: number, preferredY: number): number => {
|
||||
const bucket = Math.round(x / 8).toString();
|
||||
const used = labelCollisionState.get(bucket) ?? [];
|
||||
|
||||
let y = preferredY;
|
||||
const minGap = 2.1;
|
||||
while (used.some((existingY) => Math.abs(existingY - y) < minGap)) {
|
||||
y += minGap;
|
||||
}
|
||||
|
||||
used.push(y);
|
||||
labelCollisionState.set(bucket, used);
|
||||
return y;
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="visualiser-root">
|
||||
<div className="visualiser-container" ref={visualiserContainerRef}>
|
||||
@ -483,22 +525,31 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
gap: "40px",
|
||||
}}
|
||||
>
|
||||
{level.map((task) => (
|
||||
<div
|
||||
key={task.config.guid as string}
|
||||
className="visualiser-node"
|
||||
data-guid={task.config.guid as string}
|
||||
>
|
||||
<div className="visualiser-node-content">
|
||||
<ValidationErrorIcon
|
||||
visible={
|
||||
taskValidation[task.config.guid as string] === false
|
||||
}
|
||||
/>
|
||||
<span>{(task.config.name as string) || task.type}</span>
|
||||
{level.map((task) => {
|
||||
const guid = task.config.guid as string;
|
||||
const isHovered = hoveredGuid === guid;
|
||||
const isRelated =
|
||||
hoveredGuid !== null && relatedGuids.has(guid);
|
||||
const isDim =
|
||||
hoveredGuid !== null && !isHovered && !isRelated;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={guid}
|
||||
className={`visualiser-node${isHovered ? " is-hovered" : ""}${isRelated ? " is-related" : ""}${isDim ? " is-dim" : ""}`}
|
||||
data-guid={guid}
|
||||
onMouseEnter={() => setHoveredGuid(guid)}
|
||||
onMouseLeave={() => setHoveredGuid(null)}
|
||||
>
|
||||
<div className="visualiser-node-content">
|
||||
<ValidationErrorIcon
|
||||
visible={taskValidation[guid] === false}
|
||||
/>
|
||||
<span>{(task.config.name as string) || task.type}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
@ -643,11 +694,17 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
return tEnum(matchedOutcome ?? outcome);
|
||||
})
|
||||
.join(", ");
|
||||
const edgeIsActive =
|
||||
hoveredGuid !== null &&
|
||||
(edge.sourceGuid === hoveredGuid ||
|
||||
edge.targetGuid === hoveredGuid);
|
||||
const edgeIsDim = hoveredGuid !== null && !edgeIsActive;
|
||||
const adjustedLabelY = getAdjustedLabelY(labelX, labelY - 1.25);
|
||||
|
||||
return (
|
||||
<g key={`${edge.sourceGuid}-${edge.targetGuid}`}>
|
||||
<path
|
||||
className="visualiser-outcome-connector-line"
|
||||
className={`visualiser-outcome-connector-line${edgeIsActive ? " is-active" : ""}${edgeIsDim ? " is-dim" : ""}`}
|
||||
d={path}
|
||||
markerEnd={
|
||||
isMergedLeft ? undefined : "url(#visualiser-arrow)"
|
||||
@ -656,8 +713,8 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
{label && (
|
||||
<text
|
||||
x={labelX}
|
||||
y={labelY - 1.25}
|
||||
className="visualiser-outcome-connector-label"
|
||||
y={adjustedLabelY}
|
||||
className={`visualiser-outcome-connector-label${edgeIsActive ? " is-active" : ""}${edgeIsDim ? " is-dim" : ""}`}
|
||||
textAnchor={labelAnchor}
|
||||
dominantBaseline="middle"
|
||||
>
|
||||
@ -676,11 +733,20 @@ const VisualiserTab: React.FC<VisualiserTabProps> = ({
|
||||
|
||||
const endX = target.x - target.width / 2;
|
||||
const endY = target.y;
|
||||
const mergedIsActive =
|
||||
hoveredGuid !== null &&
|
||||
(hoveredGuid === targetGuid ||
|
||||
edges.some(
|
||||
(edge) =>
|
||||
edge.targetGuid === targetGuid &&
|
||||
edge.sourceGuid === hoveredGuid,
|
||||
));
|
||||
const mergedIsDim = hoveredGuid !== null && !mergedIsActive;
|
||||
|
||||
return (
|
||||
<path
|
||||
key={`merged-left-${targetGuid}`}
|
||||
className="visualiser-outcome-connector-line"
|
||||
className={`visualiser-outcome-connector-line${mergedIsActive ? " is-active" : ""}${mergedIsDim ? " is-dim" : ""}`}
|
||||
d={`M ${laneX} ${endY} L ${endX} ${endY}`}
|
||||
markerEnd="url(#visualiser-arrow)"
|
||||
/>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user