@anlyx/ui 0.1.0 → 0.1.2
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/AnlyxAppShell.d.ts +1 -1
- package/dist/components/AnlyxAppShell.js +150 -15
- package/dist/components/ApiCallList.d.ts +0 -1
- package/dist/components/ApiCallList.js +0 -1
- package/dist/components/CanvasPlaceholder.d.ts +0 -1
- package/dist/components/CanvasPlaceholder.js +0 -1
- package/dist/components/CaptureStatusEmptyState.d.ts +0 -1
- package/dist/components/CaptureStatusEmptyState.js +5 -4
- package/dist/components/EndpointList.d.ts +0 -1
- package/dist/components/EndpointList.js +1 -2
- package/dist/components/EndpointMapCanvas.d.ts +5 -2
- package/dist/components/EndpointMapCanvas.js +93 -13
- package/dist/components/FlowLegend.d.ts +4 -2
- package/dist/components/FlowLegend.js +4 -2
- package/dist/components/FlowNodeCard.d.ts +1 -2
- package/dist/components/FlowNodeCard.js +25 -3
- package/dist/components/InspectorPanel.d.ts +8 -3
- package/dist/components/InspectorPanel.js +17 -3
- package/dist/components/PageList.d.ts +0 -1
- package/dist/components/PageList.js +1 -2
- package/dist/components/PageStoryboardCard.d.ts +0 -1
- package/dist/components/PageStoryboardCard.js +4 -2
- package/dist/components/PageStoryboardView.d.ts +4 -3
- package/dist/components/PageStoryboardView.js +5 -3
- package/dist/components/ProcessFlowView.d.ts +21 -0
- package/dist/components/ProcessFlowView.js +9 -0
- package/dist/components/ProcessTimeline.d.ts +9 -0
- package/dist/components/ProcessTimeline.js +46 -0
- package/dist/components/ReplayControls.d.ts +5 -2
- package/dist/components/ReplayControls.js +4 -3
- package/dist/components/ScreenshotSegmentCard.d.ts +0 -1
- package/dist/components/ScreenshotSegmentCard.js +0 -1
- package/dist/components/Sidebar.d.ts +5 -4
- package/dist/components/Sidebar.js +6 -3
- package/dist/components/StatusBadge.d.ts +0 -1
- package/dist/components/StatusBadge.js +0 -1
- package/dist/flow/build-react-flow-model.d.ts +6 -2
- package/dist/flow/build-react-flow-model.js +15 -18
- package/dist/flow/layout/elk-layout.d.ts +7 -0
- package/dist/flow/layout/elk-layout.js +74 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/mock-data.d.ts +0 -1
- package/dist/mock-data.js +0 -1
- package/dist/replay/build-replay-steps.d.ts +0 -1
- package/dist/replay/build-replay-steps.js +0 -1
- package/dist/replay/use-replay-lite.d.ts +2 -2
- package/dist/replay/use-replay-lite.js +1 -1
- package/dist/styles.css +826 -100
- package/dist/viewer/ViewerApp.d.ts +0 -1
- package/dist/viewer/ViewerApp.js +0 -1
- package/dist/viewer/viewer-entry.d.ts +1 -1
- package/dist/viewer/viewer-entry.js +1 -1
- package/package.json +11 -7
- package/dist/components/AnlyxAppShell.d.ts.map +0 -1
- package/dist/components/AnlyxAppShell.js.map +0 -1
- package/dist/components/ApiCallList.d.ts.map +0 -1
- package/dist/components/ApiCallList.js.map +0 -1
- package/dist/components/CanvasPlaceholder.d.ts.map +0 -1
- package/dist/components/CanvasPlaceholder.js.map +0 -1
- package/dist/components/CaptureStatusEmptyState.d.ts.map +0 -1
- package/dist/components/CaptureStatusEmptyState.js.map +0 -1
- package/dist/components/EndpointList.d.ts.map +0 -1
- package/dist/components/EndpointList.js.map +0 -1
- package/dist/components/EndpointMapCanvas.d.ts.map +0 -1
- package/dist/components/EndpointMapCanvas.js.map +0 -1
- package/dist/components/FlowLegend.d.ts.map +0 -1
- package/dist/components/FlowLegend.js.map +0 -1
- package/dist/components/FlowNodeCard.d.ts.map +0 -1
- package/dist/components/FlowNodeCard.js.map +0 -1
- package/dist/components/InspectorPanel.d.ts.map +0 -1
- package/dist/components/InspectorPanel.js.map +0 -1
- package/dist/components/PageList.d.ts.map +0 -1
- package/dist/components/PageList.js.map +0 -1
- package/dist/components/PageStoryboardCard.d.ts.map +0 -1
- package/dist/components/PageStoryboardCard.js.map +0 -1
- package/dist/components/PageStoryboardView.d.ts.map +0 -1
- package/dist/components/PageStoryboardView.js.map +0 -1
- package/dist/components/ReplayControls.d.ts.map +0 -1
- package/dist/components/ReplayControls.js.map +0 -1
- package/dist/components/ScreenshotSegmentCard.d.ts.map +0 -1
- package/dist/components/ScreenshotSegmentCard.js.map +0 -1
- package/dist/components/Sidebar.d.ts.map +0 -1
- package/dist/components/Sidebar.js.map +0 -1
- package/dist/components/StatusBadge.d.ts.map +0 -1
- package/dist/components/StatusBadge.js.map +0 -1
- package/dist/flow/build-react-flow-model.d.ts.map +0 -1
- package/dist/flow/build-react-flow-model.js.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/mock-data.d.ts.map +0 -1
- package/dist/mock-data.js.map +0 -1
- package/dist/replay/build-replay-steps.d.ts.map +0 -1
- package/dist/replay/build-replay-steps.js.map +0 -1
- package/dist/replay/use-replay-lite.d.ts.map +0 -1
- package/dist/replay/use-replay-lite.js.map +0 -1
- package/dist/viewer/ViewerApp.d.ts.map +0 -1
- package/dist/viewer/ViewerApp.js.map +0 -1
- package/dist/viewer/viewer-entry.d.ts.map +0 -1
- package/dist/viewer/viewer-entry.js.map +0 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { ScreenshotSegmentCard } from "./ScreenshotSegmentCard.js";
|
|
3
3
|
export function PageStoryboardCard({ page }) {
|
|
4
|
-
return (_jsxs("section", { className: "anlyx-storyboard-panel", "aria-label": "Screenshot segments", children: [_jsxs("div", { className: "anlyx-storyboard-section-heading", children: [_jsx("h2", { children: "Segments" }), _jsx("span", { children: page.screenshots.length })] }), page.screenshots.length > 0 ? (_jsx("div", { className: "anlyx-segment-grid", children: page.screenshots.map((segment) => (_jsx(ScreenshotSegmentCard, { segment: segment }, `${page.id}:segment:${segment.segmentIndex}`))) })) : (
|
|
4
|
+
return (_jsxs("section", { className: "anlyx-storyboard-panel", "aria-label": "Screenshot segments", children: [_jsxs("div", { className: "anlyx-storyboard-section-heading", children: [_jsx("h2", { children: "Segments" }), _jsx("span", { children: page.screenshots.length })] }), page.screenshots.length > 0 ? (_jsx("div", { className: "anlyx-segment-grid", children: page.screenshots.map((segment) => (_jsx(ScreenshotSegmentCard, { segment: segment }, `${page.id}:segment:${segment.segmentIndex}`))) })) : (_jsxs("div", { className: "anlyx-segment-grid anlyx-segment-grid--empty", children: [_jsx(PlaceholderSegment, { index: 1, title: "Overview" }), _jsx(PlaceholderSegment, { index: 2, title: "Primary content" }), _jsx(PlaceholderSegment, { index: 3, title: "Related sections" })] }))] }));
|
|
5
|
+
}
|
|
6
|
+
function PlaceholderSegment({ index, title }) {
|
|
7
|
+
return (_jsxs("article", { className: "anlyx-segment-card anlyx-segment-card--placeholder", children: [_jsx("div", { className: "anlyx-segment-card__image", "aria-hidden": "true", children: _jsx("span", { children: index }) }), _jsxs("div", { className: "anlyx-segment-card__body", children: [_jsx("h3", { children: title }), _jsx("p", { children: "Waiting for capture data" }), _jsxs("div", { className: "anlyx-segment-card__meta", children: [_jsx("span", { children: "Viewport pending" }), _jsx("span", { children: "Capture pending" })] })] })] }));
|
|
5
8
|
}
|
|
6
|
-
//# sourceMappingURL=PageStoryboardCard.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
-
import type { PageStoryboard } from "@anlyx/core";
|
|
1
|
+
import type { PageStoryboard, ScanResult } from "@anlyx/core";
|
|
2
2
|
export type PageStoryboardViewProps = {
|
|
3
|
+
data: ScanResult;
|
|
3
4
|
page: PageStoryboard | undefined;
|
|
5
|
+
onViewProcessFlow: (view: "process") => void;
|
|
4
6
|
};
|
|
5
|
-
export declare function PageStoryboardView({ page }: PageStoryboardViewProps): JSX.Element;
|
|
6
|
-
//# sourceMappingURL=PageStoryboardView.d.ts.map
|
|
7
|
+
export declare function PageStoryboardView({ data, page, onViewProcessFlow }: PageStoryboardViewProps): JSX.Element;
|
|
@@ -3,7 +3,9 @@ import { ApiCallList } from "./ApiCallList.js";
|
|
|
3
3
|
import { CaptureStatusEmptyState } from "./CaptureStatusEmptyState.js";
|
|
4
4
|
import { PageStoryboardCard } from "./PageStoryboardCard.js";
|
|
5
5
|
import { StatusBadge } from "./StatusBadge.js";
|
|
6
|
-
export function PageStoryboardView({ page }) {
|
|
7
|
-
|
|
6
|
+
export function PageStoryboardView({ data, page, onViewProcessFlow }) {
|
|
7
|
+
const linkedEndpoints = page
|
|
8
|
+
? data.endpoints.filter((endpoint) => page.apiCalls.some((apiCall) => apiCall.endpointId === endpoint.id))
|
|
9
|
+
: [];
|
|
10
|
+
return (_jsxs("main", { className: "anlyx-workspace", children: [_jsxs("header", { className: "anlyx-workspace-header", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Connected Frontend" }), _jsx("h1", { children: page ? page.route : "Page Storyboard" })] }), _jsxs("div", { className: "anlyx-workspace-actions", children: [_jsx("button", { className: "anlyx-toolbar-button", type: "button", disabled: linkedEndpoints.length === 0, onClick: () => onViewProcessFlow("process"), children: "View Process Flow" }), page ? _jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus }) : null] })] }), _jsx("section", { className: "anlyx-page-storyboard", role: "region", "aria-label": "Page Storyboard", children: page ? (_jsxs(_Fragment, { children: [_jsxs("section", { className: "anlyx-page-summary", "aria-label": "Selected page summary", children: [_jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "Route" }), _jsx("h2", { children: page.route })] }), _jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "File" }), _jsx("p", { children: page.filePath ?? "Unknown" })] }), _jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "Status" }), _jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus })] }), _jsxs("div", { children: [_jsx("span", { className: "anlyx-field__label", children: "Linked endpoints" }), _jsx("p", { children: linkedEndpoints.length })] })] }), _jsx(CaptureStatusEmptyState, { status: page.captureStatus, ...(page.errorMessage ? { reason: page.errorMessage } : {}) }), _jsxs("div", { className: "anlyx-storyboard-grid", children: [_jsx(PageStoryboardCard, { page: page }), _jsxs("div", { className: "anlyx-storyboard-side", children: [_jsx(ApiCallList, { apiCalls: page.apiCalls }), _jsxs("section", { className: "anlyx-storyboard-panel", "aria-label": "Page to endpoint relationship", children: [_jsxs("div", { className: "anlyx-storyboard-section-heading", children: [_jsx("h2", { children: "Page to Endpoint" }), _jsx("span", { children: linkedEndpoints.length })] }), linkedEndpoints.length > 0 ? (_jsxs("div", { className: "anlyx-relationship-diagram", children: [_jsxs("div", { className: "anlyx-relationship-source", children: [_jsx("strong", { children: page.route }), _jsx("span", { children: "Frontend Page" })] }), _jsx("ul", { className: "anlyx-relationship-list", children: linkedEndpoints.map((endpoint) => (_jsxs("li", { children: [_jsx("span", { className: "anlyx-relationship-line", "aria-hidden": "true" }), _jsx(StatusBadge, { tone: endpoint.method, children: endpoint.method }), _jsx("span", { children: endpoint.path })] }, endpoint.id))) })] })) : (_jsx("p", { className: "anlyx-empty-inline", children: "No backend endpoint was linked during capture." }))] })] })] })] })) : (_jsx("div", { className: "anlyx-storyboard-empty", children: _jsx("p", { children: "No pages available yet." }) })) })] }));
|
|
8
11
|
}
|
|
9
|
-
//# sourceMappingURL=PageStoryboardView.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Endpoint, EndpointFlow, FlowNode } from "@anlyx/core";
|
|
2
|
+
import type { ReplayLiteState } from "../replay/use-replay-lite.js";
|
|
3
|
+
import type { ReplayStep } from "../replay/build-replay-steps.js";
|
|
4
|
+
export type ProcessFlowViewProps = {
|
|
5
|
+
endpoint: Endpoint | undefined;
|
|
6
|
+
flow: EndpointFlow | undefined;
|
|
7
|
+
replayState: ReplayLiteState;
|
|
8
|
+
replaySteps: ReplayStep[];
|
|
9
|
+
replayLoop: boolean;
|
|
10
|
+
replayDisabled: boolean;
|
|
11
|
+
replaySpeed: number;
|
|
12
|
+
selectedNodeId: string | undefined;
|
|
13
|
+
onSelectNode: (node: FlowNode) => void;
|
|
14
|
+
onPause: () => void;
|
|
15
|
+
onPlay: () => void;
|
|
16
|
+
onRestart: () => void;
|
|
17
|
+
onToggleLoop: () => void;
|
|
18
|
+
onSpeedChange: (speed: number) => void;
|
|
19
|
+
onViewStructure: () => void;
|
|
20
|
+
};
|
|
21
|
+
export declare function ProcessFlowView({ endpoint, flow, replayState, replaySteps, replayLoop, replayDisabled, replaySpeed, selectedNodeId, onSelectNode, onPause, onPlay, onRestart, onToggleLoop, onSpeedChange, onViewStructure }: ProcessFlowViewProps): JSX.Element;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { EndpointMapCanvas } from "./EndpointMapCanvas.js";
|
|
3
|
+
import { ProcessTimeline } from "./ProcessTimeline.js";
|
|
4
|
+
import { ReplayControls } from "./ReplayControls.js";
|
|
5
|
+
export function ProcessFlowView({ endpoint, flow, replayState, replaySteps, replayLoop, replayDisabled, replaySpeed, selectedNodeId, onSelectNode, onPause, onPlay, onRestart, onToggleLoop, onSpeedChange, onViewStructure }) {
|
|
6
|
+
return (_jsxs("div", { className: "anlyx-process-view", children: [_jsx(ReplayControls, { disabled: replayDisabled, loop: replayLoop, speed: replaySpeed, state: replayState, steps: replaySteps, unavailableReason: "Process Flow is unavailable because this endpoint has no scanned main path.", onPause: onPause, onPlay: onPlay, onRestart: onRestart, onSpeedChange: onSpeedChange, onToggleLoop: onToggleLoop }), _jsx(EndpointMapCanvas, { eyebrow: "Request Process Flow", endpoint: endpoint, flow: flow, replayState: replayState, selectedNodeId: selectedNodeId, title: endpoint
|
|
7
|
+
? `${endpoint.method} ${endpoint.path}`
|
|
8
|
+
: "Request process flow from scanned graph", toolbar: _jsx("button", { className: "anlyx-toolbar-button", type: "button", onClick: onViewStructure, children: "View on Structure" }), variant: "process", onSelectNode: onSelectNode }), _jsx(ProcessTimeline, { flow: flow, state: replayState, steps: replaySteps })] }));
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { EndpointFlow } from "@anlyx/core";
|
|
2
|
+
import type { ReplayStep } from "../replay/build-replay-steps.js";
|
|
3
|
+
import type { ReplayLiteState } from "../replay/use-replay-lite.js";
|
|
4
|
+
export type ProcessTimelineProps = {
|
|
5
|
+
flow: EndpointFlow | undefined;
|
|
6
|
+
steps: ReplayStep[];
|
|
7
|
+
state: ReplayLiteState;
|
|
8
|
+
};
|
|
9
|
+
export declare function ProcessTimeline({ flow, steps, state }: ProcessTimelineProps): JSX.Element;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Check } from "lucide-react";
|
|
3
|
+
import { motion } from "motion/react";
|
|
4
|
+
export function ProcessTimeline({ flow, steps, state }) {
|
|
5
|
+
const nodeById = new Map(flow?.nodes.map((node) => [node.id, node]) ?? []);
|
|
6
|
+
const isComplete = state.phase === "complete";
|
|
7
|
+
return (_jsxs("section", { className: "anlyx-process-timeline", "aria-label": "Process Flow timeline", children: [_jsxs("div", { className: "anlyx-process-timeline__header", children: [_jsx("span", { children: "Inferred request path" }), _jsxs("span", { children: [steps.length, " replay steps"] })] }), _jsxs("ol", { className: "anlyx-process-timeline__rail", children: [steps.map((step, index) => {
|
|
8
|
+
const node = nodeById.get(step.nodeId);
|
|
9
|
+
const isActive = state.phase !== "idle" &&
|
|
10
|
+
state.phase !== "complete" &&
|
|
11
|
+
state.currentStepIndex === index;
|
|
12
|
+
const isCompleted = state.phase === "complete" ||
|
|
13
|
+
(state.phase !== "idle" && state.currentStepIndex > index);
|
|
14
|
+
return (_jsxs("li", { className: [
|
|
15
|
+
"anlyx-process-step",
|
|
16
|
+
`anlyx-process-step--${step.phase}`,
|
|
17
|
+
isActive ? "anlyx-process-step--active" : "",
|
|
18
|
+
isCompleted ? "anlyx-process-step--complete" : ""
|
|
19
|
+
]
|
|
20
|
+
.filter(Boolean)
|
|
21
|
+
.join(" "), children: [_jsx(motion.span, { animate: isActive ? { scale: [1, 1.18, 1] } : { scale: 1 }, className: "anlyx-process-step__dot", "aria-hidden": "true", transition: { duration: 1.1, repeat: isActive ? Infinity : 0 }, children: isCompleted ? _jsx(Check, { size: 11, strokeWidth: 3 }) : null }), _jsx("span", { className: "anlyx-process-step__phase", children: step.phase === "request" ? "Request" : "Response" }), _jsx("span", { className: "anlyx-process-step__label", children: formatStepLabel(node?.type, step.phase) }), _jsx("span", { className: "anlyx-process-step__node", children: node?.label ?? step.nodeId })] }, `${step.phase}:${step.nodeId}:${index}`));
|
|
22
|
+
}), _jsxs("li", { className: [
|
|
23
|
+
"anlyx-process-step",
|
|
24
|
+
"anlyx-process-step--complete-stop",
|
|
25
|
+
isComplete ? "anlyx-process-step--active" : ""
|
|
26
|
+
]
|
|
27
|
+
.filter(Boolean)
|
|
28
|
+
.join(" "), children: [_jsx(motion.span, { animate: isComplete ? { scale: [1, 1.18, 1] } : { scale: 1 }, className: "anlyx-process-step__dot", "aria-hidden": "true", transition: { duration: 1.1, repeat: isComplete ? Infinity : 0 }, children: isComplete ? _jsx(Check, { size: 11, strokeWidth: 3 }) : null }), _jsx("span", { className: "anlyx-process-step__phase", children: "Complete" }), _jsx("span", { className: "anlyx-process-step__label", children: "Client" }), _jsx("span", { className: "anlyx-process-step__node", children: "Response delivered" })] })] })] }));
|
|
29
|
+
}
|
|
30
|
+
function formatStepLabel(type, phase) {
|
|
31
|
+
const suffix = phase === "response" ? " Return" : "";
|
|
32
|
+
switch (type) {
|
|
33
|
+
case "endpoint":
|
|
34
|
+
return phase === "response" ? "Endpoint Return" : "Endpoint";
|
|
35
|
+
case "controller":
|
|
36
|
+
return `Controller${suffix}`;
|
|
37
|
+
case "service":
|
|
38
|
+
return `Service${suffix}`;
|
|
39
|
+
case "repository":
|
|
40
|
+
return `Repository${suffix}`;
|
|
41
|
+
case "database":
|
|
42
|
+
return phase === "response" ? "Database Result" : "Database";
|
|
43
|
+
default:
|
|
44
|
+
return "Flow Node";
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -1,13 +1,16 @@
|
|
|
1
1
|
import type { ReplayLiteState } from "../replay/use-replay-lite.js";
|
|
2
|
+
import type { ReplayStep } from "../replay/build-replay-steps.js";
|
|
2
3
|
export type ReplayControlsProps = {
|
|
3
4
|
state: ReplayLiteState;
|
|
5
|
+
steps: ReplayStep[];
|
|
4
6
|
loop: boolean;
|
|
5
7
|
disabled?: boolean;
|
|
8
|
+
speed: number;
|
|
6
9
|
unavailableReason?: string;
|
|
7
10
|
onPlay: () => void;
|
|
8
11
|
onPause: () => void;
|
|
9
12
|
onRestart: () => void;
|
|
10
13
|
onToggleLoop: () => void;
|
|
14
|
+
onSpeedChange: (speed: number) => void;
|
|
11
15
|
};
|
|
12
|
-
export declare function ReplayControls({ state, loop, disabled, unavailableReason, onPlay, onPause, onRestart, onToggleLoop }: ReplayControlsProps): JSX.Element;
|
|
13
|
-
//# sourceMappingURL=ReplayControls.d.ts.map
|
|
16
|
+
export declare function ReplayControls({ state, steps, loop, speed, disabled, unavailableReason, onPlay, onPause, onRestart, onToggleLoop, onSpeedChange }: ReplayControlsProps): JSX.Element;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
import { Pause, Play, RefreshCw, Repeat2 } from "lucide-react";
|
|
3
|
+
export function ReplayControls({ state, steps, loop, speed, disabled = false, unavailableReason, onPlay, onPause, onRestart, onToggleLoop, onSpeedChange }) {
|
|
4
|
+
const currentStepNumber = state.phase === "idle" ? 0 : Math.min(state.currentStepIndex + 1, steps.length);
|
|
5
|
+
return (_jsxs("section", { className: "anlyx-replay", "aria-label": "Process Flow controls", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Replay from scanned flow graph" }), _jsxs("div", { className: "anlyx-replay__buttons", "aria-label": "Process Flow actions", children: [_jsxs("button", { className: "anlyx-replay__button--primary", type: "button", disabled: disabled || state.isPlaying, onClick: onPlay, children: [_jsx(Play, { size: 14, strokeWidth: 2.5 }), _jsx("span", { children: "Play" })] }), _jsxs("button", { type: "button", disabled: disabled || !state.isPlaying, onClick: onPause, children: [_jsx(Pause, { size: 14, strokeWidth: 2.5 }), _jsx("span", { children: "Pause" })] }), _jsxs("button", { type: "button", disabled: disabled, onClick: onRestart, children: [_jsx(RefreshCw, { size: 14, strokeWidth: 2.5 }), _jsx("span", { children: "Restart" })] }), _jsxs("button", { type: "button", "aria-pressed": loop, disabled: disabled, onClick: onToggleLoop, children: [_jsx(Repeat2, { size: 14, strokeWidth: 2.5 }), _jsxs("span", { children: ["Loop ", loop ? "on" : "off"] })] })] })] }), _jsxs("div", { className: "anlyx-replay__state", children: [_jsxs("span", { children: ["Step ", currentStepNumber, "/", steps.length] }), _jsxs("span", { children: ["Phase: ", state.phase] }), _jsxs("label", { children: ["Speed", _jsxs("select", { "aria-label": "Replay speed", disabled: disabled, value: speed, onChange: (event) => onSpeedChange(Number(event.currentTarget.value)), children: [_jsx("option", { value: 1100, children: "0.75x" }), _jsx("option", { value: 800, children: "1x" }), _jsx("option", { value: 520, children: "1.5x" })] })] }), _jsx("span", { children: "Main Flow only" })] }), disabled && unavailableReason ? (_jsx("p", { className: "anlyx-replay__empty", children: unavailableReason })) : null] }));
|
|
4
6
|
}
|
|
5
|
-
//# sourceMappingURL=ReplayControls.js.map
|
|
@@ -3,4 +3,3 @@ export function ScreenshotSegmentCard({ segment }) {
|
|
|
3
3
|
const title = segment.title ?? `Segment ${segment.segmentIndex + 1}`;
|
|
4
4
|
return (_jsxs("article", { className: "anlyx-segment-card", children: [_jsx("div", { className: "anlyx-segment-card__image", children: segment.path ? _jsx("img", { alt: title, src: segment.path }) : _jsx("span", { children: "No image path" }) }), _jsxs("div", { className: "anlyx-segment-card__body", children: [_jsxs("h3", { children: ["Segment ", segment.segmentIndex + 1] }), segment.title ? _jsx("p", { children: segment.title }) : null, segment.path ? _jsx("code", { children: segment.path }) : _jsx("code", { children: "No path" }), _jsxs("div", { className: "anlyx-segment-card__meta", children: [_jsxs("span", { children: ["scrollY ", segment.scrollY] }), _jsxs("span", { children: [segment.viewport.width, " x ", segment.viewport.height] })] })] })] }));
|
|
5
5
|
}
|
|
6
|
-
//# sourceMappingURL=ScreenshotSegmentCard.js.map
|
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
import type { Endpoint, PageStoryboard, ScanResult } from "@anlyx/core";
|
|
2
2
|
type SidebarProps = {
|
|
3
3
|
data: ScanResult;
|
|
4
|
-
activeView: "
|
|
4
|
+
activeView: "structure" | "frontend" | "process";
|
|
5
|
+
collapsed: boolean;
|
|
5
6
|
selectedEndpointId: string | undefined;
|
|
6
7
|
selectedPageId: string | undefined;
|
|
7
|
-
onSelectView: (view: "
|
|
8
|
+
onSelectView: (view: "structure" | "frontend" | "process") => void;
|
|
9
|
+
onToggleCollapsed: () => void;
|
|
8
10
|
onSelectEndpoint: (endpoint: Endpoint) => void;
|
|
9
11
|
onSelectPage: (page: PageStoryboard) => void;
|
|
10
12
|
};
|
|
11
|
-
export declare function Sidebar({ data, activeView, selectedEndpointId, selectedPageId, onSelectView, onSelectEndpoint, onSelectPage }: SidebarProps): JSX.Element;
|
|
13
|
+
export declare function Sidebar({ data, activeView, collapsed, selectedEndpointId, selectedPageId, onSelectView, onToggleCollapsed, onSelectEndpoint, onSelectPage }: SidebarProps): JSX.Element;
|
|
12
14
|
export {};
|
|
13
|
-
//# sourceMappingURL=Sidebar.d.ts.map
|
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { PanelLeftClose, PanelLeftOpen, Search } from "lucide-react";
|
|
2
3
|
import { EndpointList } from "./EndpointList.js";
|
|
3
4
|
import { PageList } from "./PageList.js";
|
|
4
|
-
export function Sidebar({ data, activeView, selectedEndpointId, selectedPageId, onSelectView, onSelectEndpoint, onSelectPage }) {
|
|
5
|
-
|
|
5
|
+
export function Sidebar({ data, activeView, collapsed, selectedEndpointId, selectedPageId, onSelectView, onToggleCollapsed, onSelectEndpoint, onSelectPage }) {
|
|
6
|
+
if (collapsed) {
|
|
7
|
+
return (_jsxs("aside", { className: "anlyx-sidebar anlyx-sidebar--collapsed", "aria-label": "Primary navigation", children: [_jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Expand navigation panel", onClick: onToggleCollapsed, children: _jsx(PanelLeftOpen, { size: 15, strokeWidth: 2.4 }) }), _jsx("span", { className: "anlyx-collapsed-label", children: "Nav" })] }));
|
|
8
|
+
}
|
|
9
|
+
return (_jsxs("aside", { className: "anlyx-sidebar", "aria-label": "Primary navigation", children: [_jsxs("div", { className: "anlyx-brand", children: [_jsx("div", { className: "anlyx-brand__mark", "aria-hidden": "true", children: "A" }), _jsxs("div", { children: [_jsx("div", { className: "anlyx-brand__name", children: "Anlyx" }), _jsx("div", { className: "anlyx-brand__project", children: data.projectName })] }), _jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Collapse navigation panel", onClick: onToggleCollapsed, children: _jsx(PanelLeftClose, { size: 15, strokeWidth: 2.4 }) })] }), _jsxs("nav", { className: "anlyx-tabs", "aria-label": "Views", children: [_jsx("button", { className: activeView === "structure" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("structure"), children: "Structure" }), _jsx("button", { className: activeView === "frontend" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("frontend"), children: "Connected Frontend" }), _jsx("button", { className: activeView === "process" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("process"), children: "Process Flow" })] }), _jsxs("label", { className: "anlyx-search", children: [_jsx("span", { className: "anlyx-search__label", children: "Search" }), _jsxs("span", { className: "anlyx-search__control", children: [_jsx(Search, { size: 14, strokeWidth: 2.4 }), _jsx("input", { placeholder: "Search endpoints or pages", type: "search" })] })] }), _jsx("div", { className: "anlyx-sidebar__list-region", children: activeView === "frontend" ? (_jsx(PageList, { pages: data.pages, selectedPageId: selectedPageId, onSelectPage: onSelectPage })) : (_jsx(EndpointList, { endpoints: data.endpoints, selectedEndpointId: selectedEndpointId, onSelectEndpoint: onSelectEndpoint })) })] }));
|
|
6
10
|
}
|
|
7
|
-
//# sourceMappingURL=Sidebar.js.map
|
|
@@ -2,4 +2,3 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
2
2
|
export function StatusBadge({ children, tone = "neutral", label }) {
|
|
3
3
|
return (_jsxs("span", { className: `anlyx-status-badge anlyx-status-badge--${tone.toLowerCase()}`, children: [label ? _jsx("span", { className: "anlyx-status-badge__label", children: label }) : null, children] }));
|
|
4
4
|
}
|
|
5
|
-
//# sourceMappingURL=StatusBadge.js.map
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { ConfidenceLevel, EndpointFlow, FlowEdge, FlowNode } from "@anlyx/core";
|
|
2
2
|
import type { Edge, Node } from "@xyflow/react";
|
|
3
|
+
import { type FlowLayoutVariant } from "./layout/elk-layout.js";
|
|
3
4
|
export type AnlyxFlowRole = "main" | "sub" | "secondary";
|
|
4
5
|
export type AnlyxFlowNodeData = {
|
|
5
6
|
node: FlowNode;
|
|
@@ -16,6 +17,7 @@ export type AnlyxFlowEdgeData = {
|
|
|
16
17
|
flowRole: AnlyxFlowRole;
|
|
17
18
|
confidence?: ConfidenceLevel;
|
|
18
19
|
isReplayActive?: boolean;
|
|
20
|
+
replayPhase?: "request" | "response" | "idle" | "complete";
|
|
19
21
|
};
|
|
20
22
|
export type AnlyxReactFlowNode = Node<AnlyxFlowNodeData, "anlyxNode">;
|
|
21
23
|
export type AnlyxReactFlowEdge = Edge<AnlyxFlowEdgeData>;
|
|
@@ -23,5 +25,7 @@ export type ReactFlowModel = {
|
|
|
23
25
|
nodes: AnlyxReactFlowNode[];
|
|
24
26
|
edges: AnlyxReactFlowEdge[];
|
|
25
27
|
};
|
|
26
|
-
export
|
|
27
|
-
|
|
28
|
+
export type BuildReactFlowModelOptions = {
|
|
29
|
+
variant?: FlowLayoutVariant;
|
|
30
|
+
};
|
|
31
|
+
export declare function buildReactFlowModel(flow: EndpointFlow, options?: BuildReactFlowModelOptions): ReactFlowModel;
|
|
@@ -1,41 +1,39 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
const
|
|
4
|
-
const SUB_Y_OFFSET = 180;
|
|
5
|
-
export function buildReactFlowModel(flow) {
|
|
1
|
+
import { layoutWithFallback } from "./layout/elk-layout.js";
|
|
2
|
+
export function buildReactFlowModel(flow, options = {}) {
|
|
3
|
+
const variant = options.variant ?? "structure";
|
|
6
4
|
const mainPathSet = new Set(flow.mainPath);
|
|
7
5
|
const nodesById = new Map(flow.nodes.map((node) => [node.id, node]));
|
|
8
6
|
const positionedNodes = new Map();
|
|
9
7
|
flow.mainPath.forEach((nodeId, index) => {
|
|
10
8
|
const node = nodesById.get(nodeId);
|
|
11
9
|
if (node) {
|
|
12
|
-
positionedNodes.set(node.id, createNode(node, "main", { x: index *
|
|
10
|
+
positionedNodes.set(node.id, createNode(node, "main", { x: index * 220, y: 0 }));
|
|
13
11
|
}
|
|
14
12
|
});
|
|
15
13
|
flow.nodes
|
|
16
14
|
.filter((node) => !mainPathSet.has(node.id))
|
|
17
15
|
.forEach((node, index) => {
|
|
18
|
-
positionedNodes.set(node.id, createNode(node, "secondary", { x: index *
|
|
16
|
+
positionedNodes.set(node.id, createNode(node, "secondary", { x: index * 220, y: 120 }));
|
|
19
17
|
});
|
|
20
18
|
flow.subFlows.forEach((subFlow, subFlowIndex) => {
|
|
21
19
|
const parentPosition = positionedNodes.get(subFlow.parentNodeId)?.position ?? {
|
|
22
|
-
x: subFlowIndex *
|
|
20
|
+
x: subFlowIndex * 220,
|
|
23
21
|
y: 0
|
|
24
22
|
};
|
|
25
23
|
subFlow.nodes.forEach((node, index) => {
|
|
26
24
|
positionedNodes.set(node.id, createNode(node, "sub", {
|
|
27
|
-
x: parentPosition.x + index *
|
|
28
|
-
y: parentPosition.y +
|
|
25
|
+
x: parentPosition.x + index * 180,
|
|
26
|
+
y: parentPosition.y + 180 + subFlowIndex * 36
|
|
29
27
|
}, subFlow.id));
|
|
30
28
|
});
|
|
31
29
|
});
|
|
32
|
-
return {
|
|
30
|
+
return layoutWithFallback({
|
|
33
31
|
nodes: [...positionedNodes.values()],
|
|
34
32
|
edges: [
|
|
35
|
-
...flow.edges.map((edge) => createEdge(edge, getEdgeRole(edge, mainPathSet))),
|
|
36
|
-
...flow.subFlows.flatMap((subFlow) => subFlow.edges.map((edge) => createEdge(edge, "sub")))
|
|
33
|
+
...flow.edges.map((edge, index) => createEdge(edge, getEdgeRole(edge, mainPathSet), index)),
|
|
34
|
+
...flow.subFlows.flatMap((subFlow, subFlowIndex) => subFlow.edges.map((edge, edgeIndex) => createEdge(edge, "sub", `${subFlowIndex}:${edgeIndex}`)))
|
|
37
35
|
]
|
|
38
|
-
};
|
|
36
|
+
}, flow.mainPath, { variant });
|
|
39
37
|
}
|
|
40
38
|
function createNode(node, flowRole, position, subFlowId) {
|
|
41
39
|
return {
|
|
@@ -52,12 +50,12 @@ function createNode(node, flowRole, position, subFlowId) {
|
|
|
52
50
|
}
|
|
53
51
|
};
|
|
54
52
|
}
|
|
55
|
-
function createEdge(edge, flowRole) {
|
|
53
|
+
function createEdge(edge, flowRole, stableSuffix) {
|
|
56
54
|
return {
|
|
57
|
-
id: edge.id
|
|
55
|
+
id: `${edge.id}:${stableSuffix}`,
|
|
58
56
|
source: edge.from,
|
|
59
57
|
target: edge.to,
|
|
60
|
-
type: "
|
|
58
|
+
type: "anlyxEdge",
|
|
61
59
|
animated: false,
|
|
62
60
|
className: `anlyx-flow-edge anlyx-flow-edge--${flowRole}`,
|
|
63
61
|
data: {
|
|
@@ -76,4 +74,3 @@ function getEdgeRole(edge, mainPathSet) {
|
|
|
76
74
|
}
|
|
77
75
|
return "secondary";
|
|
78
76
|
}
|
|
79
|
-
//# sourceMappingURL=build-react-flow-model.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { ReactFlowModel } from "../build-react-flow-model.js";
|
|
2
|
+
export type FlowLayoutVariant = "structure" | "process";
|
|
3
|
+
export type LayoutOptions = {
|
|
4
|
+
variant: FlowLayoutVariant;
|
|
5
|
+
};
|
|
6
|
+
export declare function layoutWithElk(model: ReactFlowModel, options: LayoutOptions): Promise<ReactFlowModel>;
|
|
7
|
+
export declare function layoutWithFallback(model: ReactFlowModel, mainPath: string[], options: LayoutOptions): ReactFlowModel;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import ELKConstructor from "elkjs/lib/elk.bundled.js";
|
|
2
|
+
const Elk = ELKConstructor;
|
|
3
|
+
const elk = new Elk();
|
|
4
|
+
const NODE_WIDTH = 196;
|
|
5
|
+
const NODE_HEIGHT = 92;
|
|
6
|
+
const STRUCTURE_MAIN_Y = 0;
|
|
7
|
+
const STRUCTURE_SECONDARY_Y = 145;
|
|
8
|
+
const PROCESS_REQUEST_Y = 0;
|
|
9
|
+
const PROCESS_BRANCH_Y = 145;
|
|
10
|
+
const MAIN_X_SPACING = 232;
|
|
11
|
+
const BRANCH_X_SPACING = 190;
|
|
12
|
+
export async function layoutWithElk(model, options) {
|
|
13
|
+
if (model.nodes.length === 0) {
|
|
14
|
+
return model;
|
|
15
|
+
}
|
|
16
|
+
const graph = {
|
|
17
|
+
id: "anlyx-flow",
|
|
18
|
+
layoutOptions: {
|
|
19
|
+
"elk.algorithm": "layered",
|
|
20
|
+
"elk.direction": "RIGHT",
|
|
21
|
+
"elk.layered.spacing.nodeNodeBetweenLayers": options.variant === "process" ? "72" : "84",
|
|
22
|
+
"elk.spacing.nodeNode": options.variant === "process" ? "44" : "52",
|
|
23
|
+
"elk.layered.nodePlacement.strategy": "NETWORK_SIMPLEX",
|
|
24
|
+
"elk.layered.crossingMinimization.strategy": "LAYER_SWEEP"
|
|
25
|
+
},
|
|
26
|
+
children: model.nodes.map((node) => ({
|
|
27
|
+
id: node.id,
|
|
28
|
+
width: NODE_WIDTH,
|
|
29
|
+
height: NODE_HEIGHT
|
|
30
|
+
})),
|
|
31
|
+
edges: model.edges.map((edge) => ({
|
|
32
|
+
id: edge.id,
|
|
33
|
+
sources: [edge.source],
|
|
34
|
+
targets: [edge.target]
|
|
35
|
+
}))
|
|
36
|
+
};
|
|
37
|
+
const layoutedGraph = await elk.layout(graph);
|
|
38
|
+
const positions = new Map(layoutedGraph.children?.map((node) => [node.id, { x: node.x ?? 0, y: node.y ?? 0 }]) ?? []);
|
|
39
|
+
return {
|
|
40
|
+
nodes: model.nodes.map((node) => ({
|
|
41
|
+
...node,
|
|
42
|
+
position: positions.get(node.id) ?? node.position
|
|
43
|
+
})),
|
|
44
|
+
edges: model.edges
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
export function layoutWithFallback(model, mainPath, options) {
|
|
48
|
+
const mainPathIndex = new Map(mainPath.map((nodeId, index) => [nodeId, index]));
|
|
49
|
+
let secondaryIndex = 0;
|
|
50
|
+
return {
|
|
51
|
+
nodes: model.nodes.map((node) => {
|
|
52
|
+
const pathIndex = mainPathIndex.get(node.id);
|
|
53
|
+
if (pathIndex !== undefined) {
|
|
54
|
+
return {
|
|
55
|
+
...node,
|
|
56
|
+
position: {
|
|
57
|
+
x: pathIndex * MAIN_X_SPACING,
|
|
58
|
+
y: options.variant === "process" ? PROCESS_REQUEST_Y : STRUCTURE_MAIN_Y
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const nextSecondaryIndex = secondaryIndex++;
|
|
63
|
+
return {
|
|
64
|
+
...node,
|
|
65
|
+
position: {
|
|
66
|
+
x: Math.max(1, Math.floor(nextSecondaryIndex / 2) + 1) * BRANCH_X_SPACING,
|
|
67
|
+
y: (options.variant === "process" ? PROCESS_BRANCH_Y : STRUCTURE_SECONDARY_Y) +
|
|
68
|
+
(nextSecondaryIndex % 2) * 116
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}),
|
|
72
|
+
edges: model.edges
|
|
73
|
+
};
|
|
74
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -26,4 +26,3 @@ export { buildReactFlowModel } from "./flow/build-react-flow-model.js";
|
|
|
26
26
|
export { mockScanResult } from "./mock-data.js";
|
|
27
27
|
export { buildReplaySteps } from "./replay/build-replay-steps.js";
|
|
28
28
|
export { useReplayLite } from "./replay/use-replay-lite.js";
|
|
29
|
-
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
CHANGED
|
@@ -19,4 +19,3 @@ export { buildReactFlowModel } from "./flow/build-react-flow-model.js";
|
|
|
19
19
|
export { mockScanResult } from "./mock-data.js";
|
|
20
20
|
export { buildReplaySteps } from "./replay/build-replay-steps.js";
|
|
21
21
|
export { useReplayLite } from "./replay/use-replay-lite.js";
|
|
22
|
-
//# sourceMappingURL=index.js.map
|
package/dist/mock-data.d.ts
CHANGED
package/dist/mock-data.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ReplayPhase } from "./build-replay-steps.js";
|
|
1
|
+
import { type ReplayPhase, type ReplayStep } from "./build-replay-steps.js";
|
|
2
2
|
export type ReplayLiteState = {
|
|
3
3
|
phase: ReplayPhase;
|
|
4
4
|
isPlaying: boolean;
|
|
@@ -16,6 +16,7 @@ export type UseReplayLiteOptions = {
|
|
|
16
16
|
};
|
|
17
17
|
export type UseReplayLiteResult = {
|
|
18
18
|
state: ReplayLiteState;
|
|
19
|
+
steps: ReplayStep[];
|
|
19
20
|
loop: boolean;
|
|
20
21
|
play: () => void;
|
|
21
22
|
pause: () => void;
|
|
@@ -23,4 +24,3 @@ export type UseReplayLiteResult = {
|
|
|
23
24
|
toggleLoop: () => void;
|
|
24
25
|
};
|
|
25
26
|
export declare function useReplayLite({ mainPath, loop: initialLoop, intervalMs }: UseReplayLiteOptions): UseReplayLiteResult;
|
|
26
|
-
//# sourceMappingURL=use-replay-lite.d.ts.map
|
|
@@ -67,6 +67,7 @@ export function useReplayLite({ mainPath, loop: initialLoop = false, intervalMs
|
|
|
67
67
|
isPlaying,
|
|
68
68
|
phase
|
|
69
69
|
}),
|
|
70
|
+
steps,
|
|
70
71
|
loop,
|
|
71
72
|
play,
|
|
72
73
|
pause,
|
|
@@ -94,4 +95,3 @@ function toReplayState(options) {
|
|
|
94
95
|
: {})
|
|
95
96
|
};
|
|
96
97
|
}
|
|
97
|
-
//# sourceMappingURL=use-replay-lite.js.map
|