@parallelworks/ui 0.82.1 → 0.83.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Callout.js +30 -28
- package/dist/components/CodeBlock.d.ts +4 -1
- package/dist/components/CodeBlock.js +2 -2
- package/dist/components/Meter.d.ts +6 -2
- package/dist/components/Meter.js +21 -3
- package/dist/components/Pagination.js +8 -3
- package/dist/components/SettingsCard.d.ts +17 -6
- package/dist/components/SettingsCard.js +42 -34
- package/dist/components/Table.d.ts +3 -3
- package/dist/components/Table.js +9 -4
- package/dist/components/ToggleGroup.js +2 -2
- package/dist/components/buttonClasses.js +1 -1
- package/dist/graph/DependencyGraph.d.ts +4 -1
- package/dist/graph/DependencyGraph.js +85 -39
- package/dist/graph/GraphNode.d.ts +3 -0
- package/dist/graph/GraphNode.js +7 -4
- package/dist/graph/TreeView.js +4 -4
- package/dist/graph/connectorGeometry.d.ts +10 -1
- package/dist/graph/connectorGeometry.js +81 -3
- package/dist/graph/viewport.d.ts +25 -1
- package/dist/graph/viewport.js +27 -5
- package/dist/list/ListTable.d.ts +4 -1
- package/dist/list/ListTable.js +4 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
|
@@ -26,7 +26,7 @@ import { WarningMarks, WarningMarksContext, locateWarnings, rowKey } from "./run
|
|
|
26
26
|
import { Joblist } from "./JobSummary.js";
|
|
27
27
|
import { Store, useStore } from "./store.js";
|
|
28
28
|
import { Lineage, LineageContext } from "./lineage.js";
|
|
29
|
-
import { DimRules, JobNode, setDimmed } from "./GraphNode.js";
|
|
29
|
+
import { DimRules, GraphDirectionContext, JobNode, setDimmed } from "./GraphNode.js";
|
|
30
30
|
import { ConnectorLayers } from "./Connectors.js";
|
|
31
31
|
import { routeConnectors, sameBoxes } from "./connectorGeometry.js";
|
|
32
32
|
import { isJobRecord } from "./types.js";
|
|
@@ -35,7 +35,7 @@ import { MatrixGroupNode, MatrixGroupSummaryItem } from "./MatrixGroup.js";
|
|
|
35
35
|
import { StepSummaries } from "./StepSummaries.js";
|
|
36
36
|
import { StepSummary } from "./StepSummary.js";
|
|
37
37
|
import TreeView from "./TreeView.js";
|
|
38
|
-
import { FIT_MAX_SCALE, MIN_SCALE, ZOOM_STEP, enclosing, fitMargin, fitView, intoView, viewShowing, wheelFactor, zoomAround, zoomLimit } from "./viewport.js";
|
|
38
|
+
import { FIT_MAX_SCALE, MIN_SCALE, ZOOM_STEP, enclosing, fitMargin, fitView, intoReach, intoView, reachKept, viewShowing, wheelFactor, zoomAround, zoomLimit } from "./viewport.js";
|
|
39
39
|
import { Fragment, Suspense, createContext, lazy, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
40
40
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
41
41
|
import { flushSync } from "react-dom";
|
|
@@ -294,14 +294,20 @@ function sizingTransitions(el) {
|
|
|
294
294
|
}
|
|
295
295
|
var HOLD_MS = 2e3;
|
|
296
296
|
var skippedBefore = (slots, i) => (slots?.[i] ?? i) - (i === 0 ? 0 : (slots?.[i - 1] ?? i - 1) + 1);
|
|
297
|
-
function measureBoxes(cols, pathPrefix, wrapper) {
|
|
297
|
+
function measureBoxes(cols, pathPrefix, wrapper, down) {
|
|
298
298
|
const boxes = /* @__PURE__ */ new Map();
|
|
299
299
|
for (const col of cols) for (const [head] of col) {
|
|
300
300
|
const el = head === void 0 ? null : document.getElementById(`node_${pathPrefix}${head}`);
|
|
301
|
-
if (head !== void 0 && el)
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
301
|
+
if (head !== void 0 && el) {
|
|
302
|
+
const box = {
|
|
303
|
+
...offsetWithin(el, wrapper),
|
|
304
|
+
width: el.offsetWidth
|
|
305
|
+
};
|
|
306
|
+
boxes.set(head, down ? {
|
|
307
|
+
...box,
|
|
308
|
+
height: el.offsetHeight
|
|
309
|
+
} : box);
|
|
310
|
+
}
|
|
305
311
|
}
|
|
306
312
|
return boxes;
|
|
307
313
|
}
|
|
@@ -313,6 +319,7 @@ var Subgraph = memo(function Subgraph({ jobs, pathPrefix, depth, preview, animT,
|
|
|
313
319
|
const editorApi = useGraphEditor();
|
|
314
320
|
const selectedEdges = useSelectedEdges(editable ? editorApi : null);
|
|
315
321
|
const zBase = depth * SUBGRAPH_Z_STEP;
|
|
322
|
+
const down = useContext(GraphDirectionContext) === "down";
|
|
316
323
|
const placed = useMemo(() => layout ?? (depth > 0 ? positionsOf(jobs) : void 0), [
|
|
317
324
|
layout,
|
|
318
325
|
depth,
|
|
@@ -459,9 +466,10 @@ var Subgraph = memo(function Subgraph({ jobs, pathPrefix, depth, preview, animT,
|
|
|
459
466
|
toggleMatrix
|
|
460
467
|
]);
|
|
461
468
|
const nodes = useMemo(() => /* @__PURE__ */ jsx("div", {
|
|
462
|
-
className: "flex relative",
|
|
469
|
+
className: down ? "flex flex-col items-center relative" : "flex relative",
|
|
463
470
|
children: dependencyCols.map((col, c) => /* @__PURE__ */ jsx("div", {
|
|
464
|
-
|
|
471
|
+
className: down ? "flex items-start justify-center" : void 0,
|
|
472
|
+
style: down ? void 0 : emptyColumnsStyle(skippedBefore(colSlots, c)),
|
|
465
473
|
children: col.map((jobNames, i) => {
|
|
466
474
|
const first = jobNames[0];
|
|
467
475
|
if (first === void 0) return null;
|
|
@@ -504,16 +512,17 @@ var Subgraph = memo(function Subgraph({ jobs, pathPrefix, depth, preview, animT,
|
|
|
504
512
|
pathPrefix,
|
|
505
513
|
toggleMatrix,
|
|
506
514
|
handlers,
|
|
507
|
-
editable
|
|
515
|
+
editable,
|
|
516
|
+
down
|
|
508
517
|
]);
|
|
509
518
|
if (!dependencyCols[0]?.length) return null;
|
|
510
519
|
const wrapper = wrapperRef.current;
|
|
511
|
-
const measured = wrapper ? measureBoxes(dependencyCols, pathPrefix, wrapper) : /* @__PURE__ */ new Map();
|
|
520
|
+
const measured = wrapper ? measureBoxes(dependencyCols, pathPrefix, wrapper, down) : /* @__PURE__ */ new Map();
|
|
512
521
|
const last = routedRef.current;
|
|
513
522
|
const routed = last && last.directDeps === directDeps && sameBoxes(last.boxes, measured) ? last : {
|
|
514
523
|
boxes: measured,
|
|
515
524
|
directDeps,
|
|
516
|
-
edges: routeConnectors(dependencyCols, colSlots.some((column, c) => column !== c), directDeps, measured, 37)
|
|
525
|
+
edges: routeConnectors(dependencyCols, colSlots.some((column, c) => column !== c), directDeps, measured, 37, down ? "down" : "right")
|
|
517
526
|
};
|
|
518
527
|
routedRef.current = routed;
|
|
519
528
|
const { boxes, edges } = routed;
|
|
@@ -704,6 +713,8 @@ function graphBounds(content) {
|
|
|
704
713
|
}
|
|
705
714
|
return bounds;
|
|
706
715
|
}
|
|
716
|
+
var NARROW_PANEL_PX = 640;
|
|
717
|
+
var BLEED_CLASSES = "max-sm:-mx-4 max-sm:w-auto max-sm:rounded-none max-sm:border-x-0";
|
|
707
718
|
var READABLE_SCALE = .5;
|
|
708
719
|
/** Where a job or step a warning took the graph to shows: the panel below its view bar, which sits
|
|
709
720
|
* over the graph. */
|
|
@@ -741,7 +752,7 @@ function flash(row) {
|
|
|
741
752
|
easing: "ease-in"
|
|
742
753
|
});
|
|
743
754
|
}
|
|
744
|
-
function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag", setViewMode, layout, editor, yamlJobs, inputs, workflow, fixedHeight, openStep, onCloseLog, stickySidebar }) {
|
|
755
|
+
function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag", setViewMode, layout, editor, yamlJobs, inputs, workflow, fixedHeight, openStep, onCloseLog, stickySidebar, bleed = false }) {
|
|
745
756
|
const engine = useWorkflowEngine();
|
|
746
757
|
const animT = .125;
|
|
747
758
|
const rootJobs = isJobRecord(run.executedJobs) ? run.executedJobs : {};
|
|
@@ -800,6 +811,7 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
|
|
|
800
811
|
}
|
|
801
812
|
}, [preview]);
|
|
802
813
|
const containerHeight = topOffset !== null ? Math.max(windowSize.h - topOffset - 16, 160) : null;
|
|
814
|
+
const runsDown = !preview && !editor && windowSize.w < NARROW_PANEL_PX;
|
|
803
815
|
const [fitHeight, setFitHeight] = useState(null);
|
|
804
816
|
const fitViewRef = useRef(null);
|
|
805
817
|
useEffect(() => {
|
|
@@ -1012,7 +1024,12 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
|
|
|
1012
1024
|
if (dx !== 0 || dy !== 0) requestAnimationFrame(() => transform.setTransform(x + dx, y + dy, scale, FIT_ANIMATION_MS, "easeOut"));
|
|
1013
1025
|
return;
|
|
1014
1026
|
}
|
|
1015
|
-
const
|
|
1027
|
+
const narrow = !preview && !editor && panel.width < NARROW_PANEL_PX;
|
|
1028
|
+
const view = fitView(bounds, panel, narrow || runsDown ? {
|
|
1029
|
+
minScale: READABLE_SCALE,
|
|
1030
|
+
top: viewBarRef.current?.offsetHeight ?? 0,
|
|
1031
|
+
width: runsDown
|
|
1032
|
+
} : void 0);
|
|
1016
1033
|
if (!view) return;
|
|
1017
1034
|
if (animationMs > 0) {
|
|
1018
1035
|
transform.setTransform(view.x, view.y, view.scale, animationMs, "easeOut");
|
|
@@ -1024,7 +1041,9 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
|
|
|
1024
1041
|
}, [
|
|
1025
1042
|
nodeOf,
|
|
1026
1043
|
editor,
|
|
1027
|
-
runRowOf
|
|
1044
|
+
runRowOf,
|
|
1045
|
+
preview,
|
|
1046
|
+
runsDown
|
|
1028
1047
|
]);
|
|
1029
1048
|
const fitWhenSettled = useCallback(async () => {
|
|
1030
1049
|
const container = containerRef.current;
|
|
@@ -1135,6 +1154,27 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
|
|
|
1135
1154
|
const moved = useCallback(() => {
|
|
1136
1155
|
viewed.current = true;
|
|
1137
1156
|
}, []);
|
|
1157
|
+
const keepInReach = useCallback(() => {
|
|
1158
|
+
const container = containerRef.current;
|
|
1159
|
+
const content = graphContentRef.current;
|
|
1160
|
+
const transform = transformRef.current;
|
|
1161
|
+
const bounds = content ? graphBounds(content) : null;
|
|
1162
|
+
if (!container || !transform || !bounds) return;
|
|
1163
|
+
const { positionX, positionY, scale } = transform.instance.state;
|
|
1164
|
+
const panel = {
|
|
1165
|
+
left: 0,
|
|
1166
|
+
top: 0,
|
|
1167
|
+
width: container.clientWidth,
|
|
1168
|
+
height: container.clientHeight
|
|
1169
|
+
};
|
|
1170
|
+
const { dx, dy } = intoReach({
|
|
1171
|
+
left: positionX + bounds.minX * scale,
|
|
1172
|
+
top: positionY + bounds.minY * scale,
|
|
1173
|
+
width: (bounds.maxX - bounds.minX) * scale,
|
|
1174
|
+
height: (bounds.maxY - bounds.minY) * scale
|
|
1175
|
+
}, panel, reachKept(panel));
|
|
1176
|
+
if (dx !== 0 || dy !== 0) transform.setTransform(positionX + dx, positionY + dy, scale, FIT_ANIMATION_MS, "easeOut");
|
|
1177
|
+
}, []);
|
|
1138
1178
|
const zoomTo = useCallback((view, animationMs) => {
|
|
1139
1179
|
viewed.current = true;
|
|
1140
1180
|
transformRef.current?.setTransform(view.x, view.y, view.scale, animationMs, "easeOut");
|
|
@@ -1484,7 +1524,7 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
|
|
|
1484
1524
|
}),
|
|
1485
1525
|
!preview && /* @__PURE__ */ jsx(AnnotationBanner, { executedJobs: jobs }),
|
|
1486
1526
|
viewMode === "tree" && !preview ? /* @__PURE__ */ jsx("div", {
|
|
1487
|
-
className: "panel w-full border",
|
|
1527
|
+
className: cx("panel w-full border", bleed && BLEED_CLASSES),
|
|
1488
1528
|
children: /* @__PURE__ */ jsx(TreeView, {
|
|
1489
1529
|
jobs,
|
|
1490
1530
|
slug: runSlug,
|
|
@@ -1494,9 +1534,9 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
|
|
|
1494
1534
|
}) : /* @__PURE__ */ jsxs("div", {
|
|
1495
1535
|
ref: setContainerElement,
|
|
1496
1536
|
tabIndex: editor ? -1 : void 0,
|
|
1497
|
-
className: cx("panel w-full overflow-hidden relative", removeBorder ? "border-none" : "border", editor && "outline-none"),
|
|
1537
|
+
className: cx("panel w-full overflow-hidden relative", removeBorder ? "border-none" : "border", bleed && BLEED_CLASSES, editor && "outline-none"),
|
|
1498
1538
|
style: {
|
|
1499
|
-
height: fixedHeight ?? (fitHeight !== null ? `${Math.min(fitHeight, containerHeight ?? windowSize.h * .62)}px` : containerHeight !== null ? `${containerHeight}px` : "62vh"),
|
|
1539
|
+
height: fixedHeight ?? (fitHeight !== null ? `${Math.min(fitHeight, runsDown ? Math.max(containerHeight ?? 0, windowSize.h * .75) : containerHeight ?? windowSize.h * .62)}px` : containerHeight !== null ? `${containerHeight}px` : "62vh"),
|
|
1500
1540
|
minHeight: preview && fitHeight !== null && !fixedHeight ? 0 : 160
|
|
1501
1541
|
},
|
|
1502
1542
|
children: [/* @__PURE__ */ jsx(TransformWrapper, {
|
|
@@ -1511,8 +1551,11 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
|
|
|
1511
1551
|
onInit: () => {
|
|
1512
1552
|
setTimeout(invalidateConnectors, 0);
|
|
1513
1553
|
},
|
|
1554
|
+
velocityAnimation: { sensitivityTouch: 0 },
|
|
1514
1555
|
onPanning: moved,
|
|
1556
|
+
onPanningStop: keepInReach,
|
|
1515
1557
|
onPinch: moved,
|
|
1558
|
+
onPinchStop: keepInReach,
|
|
1516
1559
|
onTransform,
|
|
1517
1560
|
children: () => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
|
|
1518
1561
|
ref: viewBarRef,
|
|
@@ -1542,27 +1585,30 @@ function DependencyGraph({ run, preview = false, removeBorder, viewMode = "dag",
|
|
|
1542
1585
|
ref: graphContentRef,
|
|
1543
1586
|
className: "flex relative mr-[8000px]",
|
|
1544
1587
|
style: { minHeight: 4e3 },
|
|
1545
|
-
children: /* @__PURE__ */ jsx(
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
-
|
|
1588
|
+
children: /* @__PURE__ */ jsx(GraphDirectionContext, {
|
|
1589
|
+
value: runsDown ? "down" : "right",
|
|
1590
|
+
children: /* @__PURE__ */ jsx(Subgraph, {
|
|
1591
|
+
jobs,
|
|
1592
|
+
pathPrefix: pathAppend,
|
|
1593
|
+
depth: 0,
|
|
1594
|
+
preview,
|
|
1595
|
+
animT,
|
|
1596
|
+
expandedSubworkflows,
|
|
1597
|
+
toggleSubworkflow,
|
|
1598
|
+
openInNewGraph,
|
|
1599
|
+
setSublogOpen,
|
|
1600
|
+
invalidateConnectors,
|
|
1601
|
+
requestFit: fitWhenSettled,
|
|
1602
|
+
stepsOpen,
|
|
1603
|
+
toggleSteps,
|
|
1604
|
+
matrixOpen,
|
|
1605
|
+
toggleMatrix,
|
|
1606
|
+
onReady: fitGraph,
|
|
1607
|
+
layout: placed,
|
|
1608
|
+
graph: rootGraph,
|
|
1609
|
+
editable: !!editorApi && currentPath.length === 0
|
|
1610
|
+
}, `sg-${executedJobsPathIdx}-${pathAppend}`)
|
|
1611
|
+
})
|
|
1566
1612
|
})
|
|
1567
1613
|
}) })] })
|
|
1568
1614
|
}), editorOverlays]
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
+
import type { GraphDirection } from './connectorGeometry.js';
|
|
2
3
|
import { type JobHandlers } from './JobSummary.js';
|
|
3
4
|
import type { WorkflowJob } from './types.js';
|
|
4
5
|
export declare const dimClasses: (depth: number) => {
|
|
@@ -10,6 +11,8 @@ export declare const dimClasses: (depth: number) => {
|
|
|
10
11
|
export declare function DimRules({ depth }: {
|
|
11
12
|
depth: number;
|
|
12
13
|
}): import("react").JSX.Element;
|
|
14
|
+
/** Which way the graph around a node runs; a run's graph on a phone runs down. */
|
|
15
|
+
export declare const GraphDirectionContext: import("react").Context<GraphDirection>;
|
|
13
16
|
/** Dims what of a graph level isn't in the hovered job's lineage, while one is hovered. */
|
|
14
17
|
export declare function setDimmed(wrapper: HTMLElement, depth: number, hovering: boolean): void;
|
|
15
18
|
interface NodeShell {
|
package/dist/graph/GraphNode.js
CHANGED
|
@@ -2,7 +2,7 @@ import { BoxGrip } from "./editorRows.js";
|
|
|
2
2
|
import { emptyRowsStyle } from "./gridSpacing.js";
|
|
3
3
|
import { Joblist } from "./JobSummary.js";
|
|
4
4
|
import { useLineage, useLineageStore } from "./lineage.js";
|
|
5
|
-
import { memo } from "react";
|
|
5
|
+
import { createContext, memo, useContext } from "react";
|
|
6
6
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
7
7
|
//#region src/graph/GraphNode.tsx
|
|
8
8
|
var dimClasses = (depth) => ({
|
|
@@ -19,6 +19,8 @@ function DimRules({ depth }) {
|
|
|
19
19
|
children: [`.${level} .${node}{opacity:.5}.${level} .${edge}{opacity:.3}`, "@media (prefers-reduced-motion:reduce){.dag-fade{transition:none!important}}"]
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
|
+
/** Which way the graph around a node runs; a run's graph on a phone runs down. */
|
|
23
|
+
var GraphDirectionContext = createContext("right");
|
|
22
24
|
/** Dims what of a graph level isn't in the hovered job's lineage, while one is hovered. */
|
|
23
25
|
function setDimmed(wrapper, depth, hovering) {
|
|
24
26
|
wrapper.classList.toggle(dimClasses(depth).level, hovering);
|
|
@@ -26,6 +28,7 @@ function setDimmed(wrapper, depth, hovering) {
|
|
|
26
28
|
/** One node of the graph: its jobs' box, raised above connectors while its lineage is shown. */
|
|
27
29
|
function GraphNode({ id, job, rowsAbove, zBase, children }) {
|
|
28
30
|
const lineage = useLineageStore();
|
|
31
|
+
const down = useContext(GraphDirectionContext) === "down";
|
|
29
32
|
const highlight = useLineage((l) => l.node(job), 0);
|
|
30
33
|
const enter = () => lineage?.hover(job);
|
|
31
34
|
const leave = () => lineage?.hover(null);
|
|
@@ -35,10 +38,10 @@ function GraphNode({ id, job, rowsAbove, zBase, children }) {
|
|
|
35
38
|
return /* @__PURE__ */ jsxs("div", {
|
|
36
39
|
id,
|
|
37
40
|
role: "none",
|
|
38
|
-
className: "relative m-24",
|
|
41
|
+
className: down ? "relative mx-8 my-16" : "relative m-24",
|
|
39
42
|
style: {
|
|
40
43
|
zIndex: (highlight > 0 ? 25 : 1) + zBase,
|
|
41
|
-
...emptyRowsStyle(rowsAbove)
|
|
44
|
+
...down ? {} : emptyRowsStyle(rowsAbove)
|
|
42
45
|
},
|
|
43
46
|
onMouseEnter: enter,
|
|
44
47
|
onMouseLeave: leave,
|
|
@@ -70,4 +73,4 @@ var JobNode = memo(function JobNode({ jobNames, jobs, handlers, preview, editabl
|
|
|
70
73
|
});
|
|
71
74
|
});
|
|
72
75
|
//#endregion
|
|
73
|
-
export { DimRules, GraphNode, JobNode, dimClasses, setDimmed };
|
|
76
|
+
export { DimRules, GraphDirectionContext, GraphNode, JobNode, dimClasses, setDimmed };
|
package/dist/graph/TreeView.js
CHANGED
|
@@ -77,20 +77,20 @@ function JobRow({ jobName, job, isExpanded, onToggle, prefix, state }) {
|
|
|
77
77
|
className: "flex min-w-0 items-center gap-1.5",
|
|
78
78
|
children: [/* @__PURE__ */ jsx("span", {
|
|
79
79
|
className: "font-mono text-sm text-[var(--theme-app)] truncate",
|
|
80
|
+
title: jobLabel(jobName, job),
|
|
80
81
|
children: jobLabel(jobName, job)
|
|
81
82
|
}), /* @__PURE__ */ jsx(WarningMark, { warnings: marks.under(rowKey(prefix, jobName)) })]
|
|
82
83
|
}), dependencies.length > 0 && /* @__PURE__ */ jsxs("div", {
|
|
83
|
-
className: "flex items-center gap-1.5",
|
|
84
|
+
className: "flex flex-wrap items-center gap-x-1.5 gap-y-1",
|
|
84
85
|
children: [/* @__PURE__ */ jsx("span", {
|
|
85
|
-
className: "text-
|
|
86
|
+
className: "whitespace-nowrap text-xs text-[var(--theme-muted-text-color)]",
|
|
86
87
|
children: t.dependsOn
|
|
87
88
|
}), dependencies.map((dep) => /* @__PURE__ */ jsx("span", {
|
|
88
|
-
className: "text-
|
|
89
|
+
className: "whitespace-nowrap text-xs px-1.5 py-0.5 rounded bg-[var(--theme-muted-panel-bg)] text-[var(--theme-muted-text-color)]",
|
|
89
90
|
children: jobLabel(dep)
|
|
90
91
|
}, dep))]
|
|
91
92
|
})]
|
|
92
93
|
}),
|
|
93
|
-
/* @__PURE__ */ jsx("div", { className: "flex-1" }),
|
|
94
94
|
/* @__PURE__ */ jsx(Elapsed, {
|
|
95
95
|
start: job.startedAt,
|
|
96
96
|
end: job.completedAt,
|
|
@@ -18,11 +18,18 @@ export declare function arcRadius(minRise: number, colGap: number): number;
|
|
|
18
18
|
* a connector spanning several columns runs level until it nears its target.
|
|
19
19
|
*/
|
|
20
20
|
export declare function connectorPath(start: Point, end: Point, r: number, colGap: number): ConnectorPath;
|
|
21
|
+
/** Which way a graph runs: ranks as columns, left to right, or as rows, top to bottom. */
|
|
22
|
+
export type GraphDirection = 'right' | 'down';
|
|
23
|
+
/** A connector from a node's bottom to a later row's node's top: `connectorPath` turned a quarter,
|
|
24
|
+
* with the bends in the gap above `end`'s row. */
|
|
25
|
+
export declare function connectorPathDown(start: Point, end: Point, r: number, rowGap: number): ConnectorPath;
|
|
21
26
|
/** A node's place in its graph level, as laid out. */
|
|
22
27
|
export interface Box {
|
|
23
28
|
x: number;
|
|
24
29
|
y: number;
|
|
25
30
|
width: number;
|
|
31
|
+
/** Where a graph runs down, its height, which places the connectors leaving its bottom. */
|
|
32
|
+
height?: number | undefined;
|
|
26
33
|
}
|
|
27
34
|
/** Whether two layouts put every node in the same place. */
|
|
28
35
|
export declare function sameBoxes(a: Map<string, Box>, b: Map<string, Box>): boolean;
|
|
@@ -41,4 +48,6 @@ export interface DrawnEdge {
|
|
|
41
48
|
*/
|
|
42
49
|
export declare function routeConnectors(cols: string[][][],
|
|
43
50
|
/** Whether columns are left empty between those drawn, so gaps between them differ. */
|
|
44
|
-
spaced: boolean, deps: Record<string, string[]>, boxes: Map<string, Box>, connectorY: number
|
|
51
|
+
spaced: boolean, deps: Record<string, string[]>, boxes: Map<string, Box>, connectorY: number,
|
|
52
|
+
/** `down` joins a node's bottom to the top of one in a later row, at their middles. */
|
|
53
|
+
direction?: GraphDirection): DrawnEdge[];
|
|
@@ -44,12 +44,44 @@ function connectorPath(start, end, r, colGap) {
|
|
|
44
44
|
].join(" ")
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
+
/** A connector from a node's bottom to a later row's node's top: `connectorPath` turned a quarter,
|
|
48
|
+
* with the bends in the gap above `end`'s row. */
|
|
49
|
+
function connectorPathDown(start, end, r, rowGap) {
|
|
50
|
+
const across = connectorPath({
|
|
51
|
+
x: start.y,
|
|
52
|
+
y: start.x
|
|
53
|
+
}, {
|
|
54
|
+
x: end.y,
|
|
55
|
+
y: end.x
|
|
56
|
+
}, r, rowGap);
|
|
57
|
+
return {
|
|
58
|
+
d: transposed(across.d),
|
|
59
|
+
reversed: transposed(across.reversed)
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
function transposed(d) {
|
|
63
|
+
const parts = d.split(" ");
|
|
64
|
+
const out = [];
|
|
65
|
+
for (let i = 0; i < parts.length;) {
|
|
66
|
+
const op = parts[i++] ?? "";
|
|
67
|
+
if (op === "A") {
|
|
68
|
+
const [rx, ry, rot, large, sweep, x, y] = parts.slice(i, i + 7);
|
|
69
|
+
out.push("A", ry ?? "", rx ?? "", rot ?? "", large ?? "", sweep === "1" ? "0" : "1", y ?? "", x ?? "");
|
|
70
|
+
i += 7;
|
|
71
|
+
} else {
|
|
72
|
+
const [x, y] = parts.slice(i, i + 2);
|
|
73
|
+
out.push(op, y ?? "", x ?? "");
|
|
74
|
+
i += 2;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return out.join(" ");
|
|
78
|
+
}
|
|
47
79
|
/** Whether two layouts put every node in the same place. */
|
|
48
80
|
function sameBoxes(a, b) {
|
|
49
81
|
if (a.size !== b.size) return false;
|
|
50
82
|
for (const [job, box] of a) {
|
|
51
83
|
const other = b.get(job);
|
|
52
|
-
if (!other || other.x !== box.x || other.y !== box.y || other.width !== box.width) return false;
|
|
84
|
+
if (!other || other.x !== box.x || other.y !== box.y || other.width !== box.width || other.height !== box.height) return false;
|
|
53
85
|
}
|
|
54
86
|
return true;
|
|
55
87
|
}
|
|
@@ -58,7 +90,8 @@ function sameBoxes(a, b) {
|
|
|
58
90
|
* jobs share their needs, so its first job's needs are the node's; a need naming a node not laid out
|
|
59
91
|
* draws nothing. `connectorY` is how far below a node's top its connectors meet it.
|
|
60
92
|
*/
|
|
61
|
-
function routeConnectors(cols, spaced, deps, boxes, connectorY) {
|
|
93
|
+
function routeConnectors(cols, spaced, deps, boxes, connectorY, direction = "right") {
|
|
94
|
+
if (direction === "down") return routeDown(cols, deps, boxes);
|
|
62
95
|
const heads = (c) => cols[c]?.[0]?.[0];
|
|
63
96
|
const gapAfter = (c) => {
|
|
64
97
|
const left = boxes.get(heads(c) ?? "");
|
|
@@ -99,5 +132,50 @@ function routeConnectors(cols, spaced, deps, boxes, connectorY) {
|
|
|
99
132
|
path: connectorPath(edge.start, edge.end, r, colGap)
|
|
100
133
|
}));
|
|
101
134
|
}
|
|
135
|
+
function routeDown(rows, deps, boxes) {
|
|
136
|
+
const top = (row) => Math.min(...row.map(([head]) => boxes.get(head ?? "")?.y ?? Number.POSITIVE_INFINITY));
|
|
137
|
+
const bottom = (row) => Math.max(...row.map(([head]) => {
|
|
138
|
+
const box = boxes.get(head ?? "");
|
|
139
|
+
return box ? box.y + (box.height ?? 0) : Number.NEGATIVE_INFINITY;
|
|
140
|
+
}));
|
|
141
|
+
let rowGap = Number.POSITIVE_INFINITY;
|
|
142
|
+
for (let r = 0; r + 1 < rows.length; r++) {
|
|
143
|
+
const gap = top(rows[r + 1] ?? []) - bottom(rows[r] ?? []);
|
|
144
|
+
if (Number.isFinite(gap)) rowGap = Math.min(rowGap, gap);
|
|
145
|
+
}
|
|
146
|
+
if (!Number.isFinite(rowGap)) rowGap = 32;
|
|
147
|
+
const ends = [];
|
|
148
|
+
let minShift = Number.POSITIVE_INFINITY;
|
|
149
|
+
for (const row of rows) for (const [to] of row) {
|
|
150
|
+
const target = to === void 0 ? void 0 : boxes.get(to);
|
|
151
|
+
if (to === void 0 || !target) continue;
|
|
152
|
+
for (const from of deps[to] ?? []) {
|
|
153
|
+
const source = boxes.get(from);
|
|
154
|
+
if (!source) continue;
|
|
155
|
+
const start = {
|
|
156
|
+
x: Math.round(source.x + source.width / 2),
|
|
157
|
+
y: source.y + (source.height ?? 0) - 2
|
|
158
|
+
};
|
|
159
|
+
const middle = Math.round(target.x + target.width / 2);
|
|
160
|
+
const end = {
|
|
161
|
+
x: Math.abs(middle - start.x) <= 8 ? start.x : middle,
|
|
162
|
+
y: target.y + 2
|
|
163
|
+
};
|
|
164
|
+
const shift = Math.abs(end.x - start.x);
|
|
165
|
+
if (shift > 0) minShift = Math.min(minShift, shift);
|
|
166
|
+
ends.push({
|
|
167
|
+
from,
|
|
168
|
+
to,
|
|
169
|
+
start,
|
|
170
|
+
end
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
const r = arcRadius(minShift, rowGap);
|
|
175
|
+
return ends.map((edge) => ({
|
|
176
|
+
...edge,
|
|
177
|
+
path: connectorPathDown(edge.start, edge.end, r, rowGap)
|
|
178
|
+
}));
|
|
179
|
+
}
|
|
102
180
|
//#endregion
|
|
103
|
-
export { arcRadius, connectorPath, routeConnectors, sameBoxes };
|
|
181
|
+
export { arcRadius, connectorPath, connectorPathDown, routeConnectors, sameBoxes };
|
package/dist/graph/viewport.d.ts
CHANGED
|
@@ -31,13 +31,37 @@ export declare function fitMargin(panel: {
|
|
|
31
31
|
width: number;
|
|
32
32
|
height: number;
|
|
33
33
|
}): number;
|
|
34
|
+
/** How a fit may differ from showing everything. */
|
|
35
|
+
export interface FitOptions {
|
|
36
|
+
/** The farthest out it goes: a graph too big to read at this scale shows from its start,
|
|
37
|
+
* its top left, for the rest to be panned to. */
|
|
38
|
+
minScale?: number | undefined;
|
|
39
|
+
/** Room kept clear at the panel's top, such as under a bar over it, when the fit stops at
|
|
40
|
+
* `minScale`. */
|
|
41
|
+
top?: number | undefined;
|
|
42
|
+
/** Fits the graph's width alone, for one that runs down the panel to be panned along: centered
|
|
43
|
+
* across the panel, from its top below `top`. */
|
|
44
|
+
width?: boolean | undefined;
|
|
45
|
+
}
|
|
34
46
|
/** The view that shows all of `bounds` in a panel from its top left, no closer than a fit comes. */
|
|
35
47
|
export declare function fitView(bounds: Bounds, panel: {
|
|
36
48
|
width: number;
|
|
37
49
|
height: number;
|
|
38
|
-
}): View | null;
|
|
50
|
+
}, { minScale, top, width: byWidth }?: FitOptions): View | null;
|
|
39
51
|
/** The least shift that brings [start, end] inside [low, high]; none when it can't all fit, so the view stays. */
|
|
40
52
|
export declare function intoView(start: number, end: number, low: number, high: number): number;
|
|
53
|
+
/** How much of a graph a pan or pinch leaves in its panel, at the least: half the panel's shorter
|
|
54
|
+
* side, or all of a graph smaller than that. */
|
|
55
|
+
export declare function reachKept(panel: {
|
|
56
|
+
width: number;
|
|
57
|
+
height: number;
|
|
58
|
+
}): number;
|
|
59
|
+
/** The least shift that brings at least `keep` of `graph` back inside `panel` on each axis, so a
|
|
60
|
+
* pan or a fling never leaves the graph out of reach; none while that much already shows. */
|
|
61
|
+
export declare function intoReach(graph: Rect, panel: Rect, keep: number): {
|
|
62
|
+
dx: number;
|
|
63
|
+
dy: number;
|
|
64
|
+
};
|
|
41
65
|
/** A box on screen, as `getBoundingClientRect` gives it. */
|
|
42
66
|
export interface Rect {
|
|
43
67
|
left: number;
|
package/dist/graph/viewport.js
CHANGED
|
@@ -34,15 +34,18 @@ function fitMargin(panel) {
|
|
|
34
34
|
return Math.min(panel.width, panel.height) * .05;
|
|
35
35
|
}
|
|
36
36
|
/** The view that shows all of `bounds` in a panel from its top left, no closer than a fit comes. */
|
|
37
|
-
function fitView(bounds, panel) {
|
|
37
|
+
function fitView(bounds, panel, { minScale = MIN_SCALE, top = 0, width: byWidth = false } = {}) {
|
|
38
38
|
const width = bounds.maxX - bounds.minX;
|
|
39
39
|
const height = bounds.maxY - bounds.minY;
|
|
40
40
|
if (width <= 0 || height <= 0) return null;
|
|
41
41
|
const margin = fitMargin(panel);
|
|
42
|
-
const
|
|
42
|
+
const fit = Math.min((panel.width - margin * 2) / width, byWidth ? FIT_MAX_SCALE : (panel.height - margin * 2) / height, FIT_MAX_SCALE);
|
|
43
|
+
const floor = Math.min(Math.max(MIN_SCALE, minScale), FIT_MAX_SCALE);
|
|
44
|
+
const scale = Math.max(floor, fit);
|
|
45
|
+
const y = fit < floor || byWidth ? Math.max(margin, top + margin) : margin;
|
|
43
46
|
return {
|
|
44
|
-
x: margin - bounds.minX * scale,
|
|
45
|
-
y:
|
|
47
|
+
x: (byWidth ? (panel.width - width * scale) / 2 : margin) - bounds.minX * scale,
|
|
48
|
+
y: y - bounds.minY * scale,
|
|
46
49
|
scale
|
|
47
50
|
};
|
|
48
51
|
}
|
|
@@ -52,6 +55,25 @@ function intoView(start, end, low, high) {
|
|
|
52
55
|
if (start < low) return low - start;
|
|
53
56
|
return end > high ? high - end : 0;
|
|
54
57
|
}
|
|
58
|
+
/** How much of a graph a pan or pinch leaves in its panel, at the least: half the panel's shorter
|
|
59
|
+
* side, or all of a graph smaller than that. */
|
|
60
|
+
function reachKept(panel) {
|
|
61
|
+
return Math.min(panel.width, panel.height) / 2;
|
|
62
|
+
}
|
|
63
|
+
/** The least shift that brings at least `keep` of `graph` back inside `panel` on each axis, so a
|
|
64
|
+
* pan or a fling never leaves the graph out of reach; none while that much already shows. */
|
|
65
|
+
function intoReach(graph, panel, keep) {
|
|
66
|
+
const axis = (start, size, low, span) => {
|
|
67
|
+
if (size <= 0) return 0;
|
|
68
|
+
const least = Math.min(keep, size, span);
|
|
69
|
+
if (start + size < low + least) return low + least - (start + size);
|
|
70
|
+
return start > low + span - least ? low + span - least - start : 0;
|
|
71
|
+
};
|
|
72
|
+
return {
|
|
73
|
+
dx: axis(graph.left, graph.width, panel.left, panel.width),
|
|
74
|
+
dy: axis(graph.top, graph.height, panel.top, panel.height)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
55
77
|
/** The smallest box around all of `rects`, which must not be empty. */
|
|
56
78
|
function enclosing(rects) {
|
|
57
79
|
const left = Math.min(...rects.map((r) => r.left));
|
|
@@ -114,4 +136,4 @@ function viewShowing(region, target, visible, origin, view, readable) {
|
|
|
114
136
|
};
|
|
115
137
|
}
|
|
116
138
|
//#endregion
|
|
117
|
-
export { FIT_MAX_SCALE, MIN_SCALE, ZOOM_STEP, enclosing, fitMargin, fitView, intoView, viewShowing, wheelFactor, zoomAround, zoomLimit };
|
|
139
|
+
export { FIT_MAX_SCALE, MIN_SCALE, ZOOM_STEP, enclosing, fitMargin, fitView, intoReach, intoView, reachKept, viewShowing, wheelFactor, zoomAround, zoomLimit };
|
package/dist/list/ListTable.d.ts
CHANGED
|
@@ -36,7 +36,10 @@ export declare const STRETCHED_LINK_ROW = "relative [&_:is(a,button,input,label,
|
|
|
36
36
|
export declare function NameCell({ icon, iconTile, name, subtitle, href, badge, title, children, }: {
|
|
37
37
|
icon: ReactNode;
|
|
38
38
|
/** Sets the icon in a small bordered tile, so rows whose icons differ in
|
|
39
|
-
* weight, such as kinds of token or runner, still line up.
|
|
39
|
+
* weight, such as kinds of token or runner, still line up. The 28px tile
|
|
40
|
+
* sits beside a name and its subtitle; a row with no subtitle keeps its
|
|
41
|
+
* icon bare, centered where the tile would be, so a one-line row stays
|
|
42
|
+
* as short as its text and its name still lines up with tiled rows. */
|
|
40
43
|
iconTile?: boolean | undefined;
|
|
41
44
|
name: string;
|
|
42
45
|
subtitle?: string | undefined;
|
package/dist/list/ListTable.js
CHANGED
|
@@ -45,9 +45,12 @@ function NameCell({ icon, iconTile = false, name, subtitle, href, badge, title,
|
|
|
45
45
|
const stretch = useContext(StretchRowLinkContext);
|
|
46
46
|
const hasIcon = icon !== null && icon !== void 0 && icon !== false;
|
|
47
47
|
const columns = hasIcon ? "col-span-2 grid grid-cols-subgrid items-center" : "flex items-center";
|
|
48
|
-
const inner = /* @__PURE__ */ jsxs(Fragment$1, { children: [hasIcon && (iconTile ? /* @__PURE__ */ jsx("span", {
|
|
48
|
+
const inner = /* @__PURE__ */ jsxs(Fragment$1, { children: [hasIcon && (iconTile && subtitle ? /* @__PURE__ */ jsx("span", {
|
|
49
49
|
className: "flex size-7 shrink-0 items-center justify-center rounded-md border border-(--theme-border) text-(--theme-muted-text-color) [&>svg]:size-3.5",
|
|
50
50
|
children: icon
|
|
51
|
+
}) : iconTile ? /* @__PURE__ */ jsx("span", {
|
|
52
|
+
className: "flex w-7 shrink-0 items-center justify-center text-(--theme-muted-text-color) [&>svg]:size-3.5",
|
|
53
|
+
children: icon
|
|
51
54
|
}) : icon), /* @__PURE__ */ jsxs("div", {
|
|
52
55
|
className: "min-w-0",
|
|
53
56
|
children: [/* @__PURE__ */ jsxs("div", {
|