@anlyx/ui 0.1.1 → 0.1.3
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/AnalysisEvidenceList.d.ts +5 -0
- package/dist/components/AnalysisEvidenceList.js +61 -0
- package/dist/components/AnlyxAppShell.d.ts +1 -1
- package/dist/components/AnlyxAppShell.js +159 -15
- package/dist/components/ApiCallList.d.ts +3 -3
- package/dist/components/ApiCallList.js +12 -3
- 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/FlowStoryView.d.ts +22 -0
- package/dist/components/FlowStoryView.js +117 -0
- package/dist/components/InspectorPanel.d.ts +8 -3
- package/dist/components/InspectorPanel.js +36 -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 +13 -3
- package/dist/components/ProcessFlowView.d.ts +21 -0
- package/dist/components/ProcessFlowView.js +16 -0
- package/dist/components/ProcessTimeline.d.ts +9 -0
- package/dist/components/ProcessTimeline.js +46 -0
- package/dist/components/ReplayControls.d.ts +6 -2
- package/dist/components/ReplayControls.js +31 -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 +19 -4
- 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 +50 -5
- package/dist/overlay/AnlyxFlowEdge.d.ts +2 -0
- package/dist/overlay/AnlyxFlowEdge.js +15 -0
- package/dist/overlay/AnlyxFlowNode.d.ts +13 -0
- package/dist/overlay/AnlyxFlowNode.js +28 -0
- package/dist/overlay/FlowDrawer.d.ts +2 -0
- package/dist/overlay/FlowDrawer.js +59 -0
- package/dist/overlay/MainFlowCanvas.d.ts +20 -0
- package/dist/overlay/MainFlowCanvas.js +285 -0
- package/dist/overlay/RecentApiEventsTable.d.ts +5 -0
- package/dist/overlay/RecentApiEventsTable.js +19 -0
- package/dist/overlay/overlay-entry.d.ts +8 -0
- package/dist/overlay/overlay-entry.js +14 -0
- package/dist/overlay/overlay-ui.css +2 -0
- package/dist/overlay/overlay-ui.js +14 -0
- package/dist/overlay/types.d.ts +38 -0
- package/dist/overlay/types.js +1 -0
- package/dist/overlay/ui.d.ts +18 -0
- package/dist/overlay/ui.js +13 -0
- package/dist/readme-demo/ReadmeDemoApp.d.ts +4 -0
- package/dist/readme-demo/ReadmeDemoApp.js +126 -0
- package/dist/readme-demo/readme-demo-entry.d.ts +1 -0
- package/dist/readme-demo/readme-demo-entry.js +8 -0
- 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 +2018 -178
- package/dist/viewer/ViewerApp.d.ts +0 -1
- package/dist/viewer/ViewerApp.js +13 -7
- package/dist/viewer/viewer-entry.d.ts +1 -1
- package/dist/viewer/viewer-entry.js +1 -1
- package/package.json +7 -3
- 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
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Endpoint, EndpointFlow, FlowNode, PageStoryboard, ScanResult } 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 FlowStoryViewProps = {
|
|
5
|
+
data: ScanResult;
|
|
6
|
+
endpoint: Endpoint | undefined;
|
|
7
|
+
flow: EndpointFlow | undefined;
|
|
8
|
+
page: PageStoryboard | undefined;
|
|
9
|
+
replayDisabled: boolean;
|
|
10
|
+
replayLoop: boolean;
|
|
11
|
+
replaySpeed: number;
|
|
12
|
+
replayState: ReplayLiteState;
|
|
13
|
+
replaySteps: ReplayStep[];
|
|
14
|
+
selectedNodeId: string | undefined;
|
|
15
|
+
onPause: () => void;
|
|
16
|
+
onPlay: () => void;
|
|
17
|
+
onRestart: () => void;
|
|
18
|
+
onSelectNode: (node: FlowNode) => void;
|
|
19
|
+
onSpeedChange: (speed: number) => void;
|
|
20
|
+
onToggleLoop: () => void;
|
|
21
|
+
};
|
|
22
|
+
export declare function FlowStoryView({ data, endpoint, flow, page, replayDisabled, replayLoop, replaySpeed, replayState, replaySteps, selectedNodeId, onPause, onPlay, onRestart, onSelectNode, onSpeedChange, onToggleLoop }: FlowStoryViewProps): JSX.Element;
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Braces, Code2, Database, FileText, GitBranch, Globe2, Layers3, Maximize2, MonitorSmartphone, MousePointerClick, Route, ShieldCheck, Wifi } from "lucide-react";
|
|
3
|
+
import { ReplayControls } from "./ReplayControls.js";
|
|
4
|
+
import { StatusBadge } from "./StatusBadge.js";
|
|
5
|
+
export function FlowStoryView({ data, endpoint, flow, page, replayDisabled, replayLoop, replaySpeed, replayState, replaySteps, selectedNodeId, onPause, onPlay, onRestart, onSelectNode, onSpeedChange, onToggleLoop }) {
|
|
6
|
+
const storyPage = page ?? findPageForEndpoint(data, endpoint);
|
|
7
|
+
const screenshot = storyPage?.screenshots[0];
|
|
8
|
+
const stats = getFlowStoryStats(flow);
|
|
9
|
+
const replayStepLabels = buildReplayStepLabels(flow);
|
|
10
|
+
const primaryApiCall = storyPage?.apiCalls.find((apiCall) => apiCall.endpointId === endpoint?.id);
|
|
11
|
+
return (_jsxs("main", { className: "anlyx-flow-story", children: [_jsxs("header", { className: "anlyx-flow-story__header", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Flow Story" }), _jsx("h1", { children: endpoint ? titleForEndpoint(endpoint) : "Application flow story" }), _jsxs("div", { className: "anlyx-flow-story__summary", "aria-label": "Flow Story summary", children: [_jsxs("span", { children: [stats.mainSteps, " main steps"] }), _jsxs("span", { children: [stats.supportCalls, " support calls"] }), _jsxs("span", { children: [stats.evidenceCount, " evidence items"] })] })] }), _jsxs("div", { className: "anlyx-flow-story__actions", children: [_jsx(StatusBadge, { tone: "neutral", children: "Replay Lite" }), _jsxs("button", { className: "anlyx-toolbar-button", type: "button", children: [_jsx(Maximize2, { size: 14, strokeWidth: 2.4 }), "Fit view"] })] })] }), _jsx(InteractionEvidenceChain, { apiCallLabel: formatObservedApiCall(primaryApiCall, endpoint), backendStepCount: stats.mainSteps, pageRoute: storyPage?.route }), _jsxs("section", { className: "anlyx-flow-story__stage", "aria-label": "Flow Story canvas", children: [_jsx(PagePreviewCard, { page: storyPage }), _jsxs("div", { className: "anlyx-flow-story__request", "aria-hidden": "true", children: [_jsx("span", { children: "Request" }), _jsx("i", {})] }), _jsx(FlowStoryDiagram, { flow: flow, replayState: replayState, selectedNodeId: selectedNodeId, onSelectNode: onSelectNode }), _jsx("div", { className: "anlyx-flow-story__response", "aria-hidden": "true", children: _jsx("span", { children: "Response" }) })] }), _jsx(ReplayControls, { disabled: replayDisabled, loop: replayLoop, speed: replaySpeed, state: replayState, stepLabels: replayStepLabels, steps: replaySteps, unavailableReason: "Replay is unavailable because this flow has no scanned main path.", onPause: onPause, onPlay: onPlay, onRestart: onRestart, onSpeedChange: onSpeedChange, onToggleLoop: onToggleLoop }), screenshot?.path ? _jsx("span", { className: "anlyx-sr-only", children: screenshot.path }) : null] }));
|
|
12
|
+
}
|
|
13
|
+
function InteractionEvidenceChain({ apiCallLabel, backendStepCount, pageRoute }) {
|
|
14
|
+
return (_jsxs("section", { className: "anlyx-interaction-chain", "aria-label": "Interaction evidence chain", children: [_jsx(EvidenceChainItem, { detail: pageRoute ?? "No page linked", icon: MousePointerClick, label: "Triggered page", tone: "page" }), _jsx(EvidenceChainItem, { detail: apiCallLabel, icon: Wifi, label: "Browser API event", tone: "api" }), _jsx(EvidenceChainItem, { detail: `${backendStepCount} steps`, icon: Route, label: "Scanned backend path", tone: "backend" }), _jsx(EvidenceChainItem, { detail: "not runtime tracing", icon: ShieldCheck, label: "Static evidence", tone: "guard" })] }));
|
|
15
|
+
}
|
|
16
|
+
function EvidenceChainItem({ detail, icon: Icon, label, tone }) {
|
|
17
|
+
return (_jsxs("article", { className: `anlyx-interaction-chain__item anlyx-interaction-chain__item--${tone}`, children: [_jsx("span", { className: "anlyx-interaction-chain__icon", "aria-hidden": "true", children: _jsx(Icon, { size: 16, strokeWidth: 2.5 }) }), _jsxs("div", { children: [_jsx("span", { children: label }), _jsx("strong", { children: detail })] })] }));
|
|
18
|
+
}
|
|
19
|
+
function PagePreviewCard({ page }) {
|
|
20
|
+
const screenshot = page?.screenshots[0];
|
|
21
|
+
const primaryApiCall = page?.apiCalls[0];
|
|
22
|
+
return (_jsxs("article", { className: "anlyx-page-preview", "aria-label": "Frontend page preview", children: [_jsxs("div", { className: "anlyx-page-preview__chrome", children: [_jsx(MonitorSmartphone, { size: 16, strokeWidth: 2.4 }), _jsx("span", { children: page?.route ?? "No page linked" })] }), _jsxs("div", { className: "anlyx-page-preview__screen", children: [_jsxs("div", { className: "anlyx-page-preview__topbar", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {}), _jsx("strong", { children: "STARBUCKS" })] }), _jsxs("div", { className: "anlyx-page-preview__hero", children: [_jsxs("div", { children: [_jsx("span", { children: screenshot?.title ?? "Captured page" }), _jsx("strong", { children: "Birthday Reward" }), _jsx("p", { children: "Enjoy a free drink or food item on your birthday." })] }), _jsxs("div", { className: "anlyx-page-preview__reward", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("strong", { children: "1" })] })] }), _jsxs("div", { className: "anlyx-page-preview__facts", children: [_jsx("span", { children: "Valid Jan 1 - Dec 31" }), _jsx("span", { children: "Once per year" })] }), _jsxs("div", { className: "anlyx-page-preview__steps", children: [_jsx("span", { children: "Show this reward in store" }), _jsx("span", { children: "Choose drink or food" }), _jsx("span", { children: "Enjoy birthday treat" })] }), _jsxs("div", { className: "anlyx-page-preview__api", children: [_jsx("strong", { children: primaryApiCall?.method ?? "GET" }), _jsx("span", { children: primaryApiCall?.path ?? "/api/..." })] })] }), _jsxs("div", { className: "anlyx-page-preview__meta", children: [_jsx(StatusBadge, { tone: page?.captureStatus ?? "pending", children: page?.captureStatus ?? "pending" }), _jsxs("span", { children: [page?.apiCalls.length ?? 0, " API calls"] }), _jsxs("span", { children: [page?.screenshots.length ?? 0, " screenshots"] })] })] }));
|
|
23
|
+
}
|
|
24
|
+
function FlowStoryDiagram({ flow, replayState, selectedNodeId, onSelectNode }) {
|
|
25
|
+
if (!flow) {
|
|
26
|
+
return (_jsx("div", { className: "anlyx-flow-story__diagram anlyx-flow-story__diagram--empty", children: _jsx("p", { children: "No scanned backend flow available for this endpoint." }) }));
|
|
27
|
+
}
|
|
28
|
+
const nodesById = new Map(flow.nodes.map((node) => [node.id, node]));
|
|
29
|
+
const mainNodes = flow.mainPath
|
|
30
|
+
.map((nodeId) => nodesById.get(nodeId))
|
|
31
|
+
.filter((node) => Boolean(node))
|
|
32
|
+
.filter((node) => node.type !== "page");
|
|
33
|
+
const supportingNodes = flow.subFlows.flatMap((subFlow) => subFlow.nodes);
|
|
34
|
+
return (_jsxs("div", { className: "anlyx-flow-story__diagram", role: "region", "aria-label": "Flow Story path", children: [_jsxs("div", { className: "anlyx-flow-story__diagram-head", children: [_jsxs("div", { children: [_jsx("span", { children: "Main path" }), _jsxs("strong", { children: [mainNodes.length, " steps"] })] }), _jsxs("div", { children: [_jsx("span", { children: "Support" }), _jsxs("strong", { children: [supportingNodes.length, " calls"] })] })] }), _jsx("div", { className: "anlyx-flow-story__lane", "aria-label": "Main request path", children: mainNodes.map((node, index) => (_jsx(FlowStoryStep, { isActive: replayState.activeNodeId === node.id, isSelected: selectedNodeId === node.id, node: node, stepNumber: index + 1, showArrow: index < mainNodes.length - 1, onSelectNode: onSelectNode }, node.id))) }), supportingNodes.length > 0 ? (_jsxs("div", { className: "anlyx-flow-story__support", children: [_jsxs("div", { className: "anlyx-flow-story__support-heading", children: [_jsx(GitBranch, { size: 15, strokeWidth: 2.5 }), _jsx("span", { children: "Support calls from service" })] }), _jsx("div", { className: "anlyx-flow-story__support-grid", children: supportingNodes.map((node) => (_jsx(FlowStorySupportNode, { isActive: replayState.activeNodeId === node.id, isSelected: selectedNodeId === node.id, node: node, onSelectNode: onSelectNode }, node.id))) })] })) : null] }));
|
|
35
|
+
}
|
|
36
|
+
function FlowStoryStep({ node, isActive, isSelected, stepNumber, showArrow, onSelectNode }) {
|
|
37
|
+
const Icon = getFlowStoryIcon(node.type);
|
|
38
|
+
return (_jsxs("div", { className: "anlyx-flow-story__step-wrap", children: [_jsxs("button", { className: [
|
|
39
|
+
"anlyx-flow-story__step",
|
|
40
|
+
`anlyx-flow-story__step--${node.type}`,
|
|
41
|
+
isActive ? "anlyx-flow-story__step--active" : "",
|
|
42
|
+
isSelected ? "anlyx-flow-story__step--selected" : ""
|
|
43
|
+
]
|
|
44
|
+
.filter(Boolean)
|
|
45
|
+
.join(" "), type: "button", onClick: () => onSelectNode(node), "aria-label": `Select ${node.type} step ${stepNumber}: ${node.label}`, children: [_jsx("span", { className: "anlyx-flow-story__step-number", children: String(stepNumber).padStart(2, "0") }), _jsx("span", { className: "anlyx-flow-story__step-icon", "aria-hidden": "true", children: _jsx(Icon, { size: 16, strokeWidth: 2.5 }) }), _jsx("span", { className: "anlyx-flow-story__step-type", children: node.type }), _jsx("strong", { children: node.label }), _jsx(StatusBadge, { tone: node.confidence ?? "unknown", label: "confidence", children: node.confidence ?? "unknown" })] }), showArrow ? _jsx("span", { className: "anlyx-flow-story__arrow", "aria-hidden": "true" }) : null] }));
|
|
46
|
+
}
|
|
47
|
+
function FlowStorySupportNode({ node, isActive, isSelected, onSelectNode }) {
|
|
48
|
+
const Icon = getFlowStoryIcon(node.type);
|
|
49
|
+
return (_jsxs("button", { className: [
|
|
50
|
+
"anlyx-flow-story__support-node",
|
|
51
|
+
isActive ? "anlyx-flow-story__support-node--active" : "",
|
|
52
|
+
isSelected ? "anlyx-flow-story__support-node--selected" : ""
|
|
53
|
+
]
|
|
54
|
+
.filter(Boolean)
|
|
55
|
+
.join(" "), type: "button", onClick: () => onSelectNode(node), "aria-label": `Select support call ${node.label}`, children: [_jsx(Icon, { size: 15, strokeWidth: 2.5 }), _jsx("span", { children: node.type }), _jsx("strong", { children: node.label }), _jsx(StatusBadge, { tone: node.confidence ?? "unknown", label: "confidence", children: node.confidence ?? "unknown" })] }));
|
|
56
|
+
}
|
|
57
|
+
function findPageForEndpoint(data, endpoint) {
|
|
58
|
+
if (!endpoint) {
|
|
59
|
+
return data.pages[0];
|
|
60
|
+
}
|
|
61
|
+
return (data.pages.find((page) => page.apiCalls.some((apiCall) => apiCall.endpointId === endpoint.id)) ?? data.pages[0]);
|
|
62
|
+
}
|
|
63
|
+
function titleForEndpoint(endpoint) {
|
|
64
|
+
const handler = endpoint.controller && endpoint.handler
|
|
65
|
+
? `${endpoint.controller}#${endpoint.handler}`
|
|
66
|
+
: undefined;
|
|
67
|
+
return handler ?? `${endpoint.method} ${endpoint.path}`;
|
|
68
|
+
}
|
|
69
|
+
function formatObservedApiCall(apiCall, endpoint) {
|
|
70
|
+
if (apiCall) {
|
|
71
|
+
return `${apiCall.method} ${apiCall.path}`;
|
|
72
|
+
}
|
|
73
|
+
if (endpoint) {
|
|
74
|
+
return `${endpoint.method} ${endpoint.path}`;
|
|
75
|
+
}
|
|
76
|
+
return "No API event matched";
|
|
77
|
+
}
|
|
78
|
+
function getFlowStoryStats(flow) {
|
|
79
|
+
if (!flow) {
|
|
80
|
+
return { evidenceCount: 0, mainSteps: 0, supportCalls: 0 };
|
|
81
|
+
}
|
|
82
|
+
return {
|
|
83
|
+
mainSteps: flow.mainPath.filter((nodeId) => !nodeId.startsWith("page:")).length,
|
|
84
|
+
supportCalls: flow.subFlows.reduce((count, subFlow) => count + subFlow.nodes.length, 0),
|
|
85
|
+
evidenceCount: flow.nodes.reduce((count, node) => count + (node.evidence?.length ?? 0), 0) +
|
|
86
|
+
flow.edges.reduce((count, edge) => count + (edge.evidence?.length ?? 0), 0) +
|
|
87
|
+
flow.subFlows.reduce((count, subFlow) => count +
|
|
88
|
+
subFlow.nodes.reduce((nodeCount, node) => nodeCount + (node.evidence?.length ?? 0), 0) +
|
|
89
|
+
subFlow.edges.reduce((edgeCount, edge) => edgeCount + (edge.evidence?.length ?? 0), 0), 0)
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
function buildReplayStepLabels(flow) {
|
|
93
|
+
if (!flow) {
|
|
94
|
+
return {};
|
|
95
|
+
}
|
|
96
|
+
return Object.fromEntries(flow.nodes.map((node) => [node.id, node.label]));
|
|
97
|
+
}
|
|
98
|
+
function getFlowStoryIcon(type) {
|
|
99
|
+
switch (type) {
|
|
100
|
+
case "endpoint":
|
|
101
|
+
return Globe2;
|
|
102
|
+
case "controller":
|
|
103
|
+
return Code2;
|
|
104
|
+
case "service":
|
|
105
|
+
return Layers3;
|
|
106
|
+
case "repository":
|
|
107
|
+
return Braces;
|
|
108
|
+
case "database":
|
|
109
|
+
return Database;
|
|
110
|
+
case "mapper":
|
|
111
|
+
case "validator":
|
|
112
|
+
case "utility":
|
|
113
|
+
return GitBranch;
|
|
114
|
+
default:
|
|
115
|
+
return FileText;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -1,9 +1,14 @@
|
|
|
1
|
-
import type { EndpointFlow, FlowNode, ScanResult } from "@anlyx/core";
|
|
1
|
+
import type { EndpointFlow, FlowNode, PageStoryboard, ScanResult } from "@anlyx/core";
|
|
2
|
+
import type { ReplayLiteState } from "../replay/use-replay-lite.js";
|
|
2
3
|
type InspectorPanelProps = {
|
|
3
4
|
data: ScanResult;
|
|
5
|
+
activeView: "flowStory" | "structure" | "frontend" | "process";
|
|
6
|
+
collapsed: boolean;
|
|
4
7
|
selectedFlow: EndpointFlow | undefined;
|
|
5
8
|
selectedNode: FlowNode | undefined;
|
|
9
|
+
selectedPage: PageStoryboard | undefined;
|
|
10
|
+
replayState: ReplayLiteState;
|
|
11
|
+
onToggleCollapsed: () => void;
|
|
6
12
|
};
|
|
7
|
-
export declare function InspectorPanel({ data, selectedFlow, selectedNode }: InspectorPanelProps): JSX.Element;
|
|
13
|
+
export declare function InspectorPanel({ data, activeView, collapsed, selectedFlow, selectedNode, selectedPage, replayState, onToggleCollapsed }: InspectorPanelProps): JSX.Element;
|
|
8
14
|
export {};
|
|
9
|
-
//# sourceMappingURL=InspectorPanel.d.ts.map
|
|
@@ -1,8 +1,16 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { AnalysisEvidenceList } from "./AnalysisEvidenceList.js";
|
|
2
3
|
import { StatusBadge } from "./StatusBadge.js";
|
|
3
|
-
export function InspectorPanel({ data, selectedFlow, selectedNode }) {
|
|
4
|
+
export function InspectorPanel({ data, activeView, collapsed, selectedFlow, selectedNode, selectedPage, replayState, onToggleCollapsed }) {
|
|
5
|
+
if (collapsed) {
|
|
6
|
+
return (_jsxs("aside", { className: "anlyx-inspector anlyx-inspector--collapsed", role: "complementary", "aria-label": "Inspector", children: [_jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Expand inspector panel", onClick: onToggleCollapsed, children: "Open" }), _jsx("span", { className: "anlyx-collapsed-label", children: "Inspector" })] }));
|
|
7
|
+
}
|
|
8
|
+
if (activeView === "frontend") {
|
|
9
|
+
return (_jsxs("aside", { className: "anlyx-inspector", role: "complementary", "aria-label": "Inspector", children: [_jsxs("div", { className: "anlyx-panel-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Inspector" }), _jsx("h2", { children: "Frontend Page" })] }), _jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Collapse inspector panel", onClick: onToggleCollapsed, children: "Collapse" })] }), selectedPage ? (_jsxs("div", { className: "anlyx-inspector-stack", children: [_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Details", children: [_jsx("h3", { children: "Details" }), _jsx(Field, { label: "Route", value: selectedPage.route }), _jsx(Field, { label: "File path", value: selectedPage.filePath ?? "Manual or unknown" }), _jsxs("div", { className: "anlyx-field", children: [_jsx("span", { className: "anlyx-field__label", children: "Capture status" }), _jsx(StatusBadge, { tone: selectedPage.captureStatus, children: selectedPage.captureStatus })] }), _jsx(Field, { label: "Screenshots", value: String(selectedPage.screenshots.length) }), _jsx(Field, { label: "API calls", value: String(selectedPage.apiCalls.length) })] }), selectedPage.errorMessage ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Capture error", children: [_jsx("h3", { children: "Capture error" }), _jsx("p", { children: selectedPage.errorMessage })] })) : null] })) : (_jsx("p", { className: "anlyx-empty", children: "No page selected" }))] }));
|
|
10
|
+
}
|
|
4
11
|
const linkedPages = selectedNode ? findLinkedPages(data, selectedNode.id) : [];
|
|
5
|
-
|
|
12
|
+
const calls = selectedNode ? findCalls(selectedFlow, selectedNode.id) : [];
|
|
13
|
+
return (_jsxs("aside", { className: "anlyx-inspector", role: "complementary", "aria-label": "Inspector", children: [_jsxs("div", { className: "anlyx-panel-heading", children: [_jsxs("div", { children: [_jsx("p", { className: "anlyx-eyebrow", children: "Inspector" }), _jsx("h2", { children: activeView === "process" ? "Process Step" : "Flow Evidence" })] }), _jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Collapse inspector panel", onClick: onToggleCollapsed, children: "Collapse" })] }), selectedNode ? (_jsxs("div", { className: "anlyx-inspector-stack", children: [activeView === "process" ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Replay state", children: [_jsx("h3", { children: "Process replay" }), _jsx(Field, { label: "Active Step", value: replayState.phase }), _jsx(Field, { label: "Step", value: String(replayState.currentStepIndex + 1) }), _jsx(Field, { label: "Active Node", value: replayState.activeNodeId ?? "none" }), _jsx(Field, { label: "Active Edge", value: formatActiveEdge(replayState) }), _jsx("p", { className: "anlyx-inspector-note", children: "Source: scanned static flow graph" })] })) : null, _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Details", children: [_jsx("h3", { children: "Details" }), _jsx(Field, { label: "Type", value: selectedNode.type }), _jsx(Field, { label: "Label", value: selectedNode.label }), _jsx(Field, { label: "File path", value: selectedNode.filePath ?? "Unknown" }), _jsx(Field, { label: "Line number", value: formatLineNumber(selectedNode.lineNumber) })] }), _jsx(AnalysisEvidenceList, { node: selectedNode }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Calls", children: [_jsx("h3", { children: "Calls" }), calls.length > 0 ? (_jsx("ul", { className: "anlyx-call-list", children: calls.map((call) => (_jsxs("li", { children: [_jsx("span", { children: call.label }), _jsx(StatusBadge, { tone: call.confidence, children: call.confidence })] }, call.id))) })) : (_jsx("p", { children: "No outgoing calls detected for this node." }))] }), selectedNode.metadata ? (_jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Metadata", children: [_jsxs("div", { className: "anlyx-inspector-group__heading", children: [_jsx("h3", { children: "Metadata" }), _jsx("button", { className: "anlyx-copy-button", type: "button", onClick: () => copyToClipboard(JSON.stringify(selectedNode.metadata, null, 2)), children: "Copy" })] }), _jsx("pre", { className: "anlyx-metadata", children: JSON.stringify(selectedNode.metadata, null, 2) })] })) : null, _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Confidence", children: [_jsx("h3", { children: "Confidence" }), _jsx(StatusBadge, { tone: selectedNode.confidence ?? "unknown", label: "confidence", children: selectedNode.confidence ?? "unknown" })] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Linked pages", children: [_jsx("h3", { children: "Linked pages" }), linkedPages.length > 0 ? (_jsx("ul", { children: linkedPages.map((page) => (_jsx("li", { children: page.route }, page.id))) })) : (_jsx("p", { children: "None" }))] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "Sub flows", children: [_jsx("h3", { children: "Sub flows" }), _jsxs("p", { children: [selectedFlow?.subFlows.length ?? 0, " collapsed"] })] }), _jsxs("section", { className: "anlyx-inspector-group", "aria-label": "DB tables", children: [_jsx("h3", { children: "DB tables" }), _jsx("p", { children: findDatabaseLabel(selectedFlow) ?? "None" })] })] })) : (_jsx("p", { className: "anlyx-empty", children: "No node selected" }))] }));
|
|
6
14
|
}
|
|
7
15
|
function Field({ label, value }) {
|
|
8
16
|
return (_jsxs("div", { className: "anlyx-field", children: [_jsx("span", { className: "anlyx-field__label", children: label }), _jsx("span", { className: "anlyx-field__value", children: value })] }));
|
|
@@ -10,10 +18,35 @@ function Field({ label, value }) {
|
|
|
10
18
|
function formatLineNumber(lineNumber) {
|
|
11
19
|
return lineNumber === undefined ? "Unknown" : String(lineNumber);
|
|
12
20
|
}
|
|
21
|
+
function formatActiveEdge(replayState) {
|
|
22
|
+
if (!replayState.activeEdge) {
|
|
23
|
+
return "none";
|
|
24
|
+
}
|
|
25
|
+
return `${replayState.activeEdge.from} -> ${replayState.activeEdge.to}`;
|
|
26
|
+
}
|
|
27
|
+
function copyToClipboard(value) {
|
|
28
|
+
void navigator.clipboard?.writeText(value);
|
|
29
|
+
}
|
|
13
30
|
function findLinkedPages(data, nodeId) {
|
|
14
31
|
return data.pages.filter((page) => page.apiCalls.some((apiCall) => apiCall.endpointId === nodeId));
|
|
15
32
|
}
|
|
16
33
|
function findDatabaseLabel(flow) {
|
|
17
34
|
return flow?.nodes.find((node) => node.type === "database")?.label;
|
|
18
35
|
}
|
|
19
|
-
|
|
36
|
+
function findCalls(flow, nodeId) {
|
|
37
|
+
if (!flow) {
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
const allNodes = [...flow.nodes, ...flow.subFlows.flatMap((subFlow) => subFlow.nodes)];
|
|
41
|
+
const allEdges = [...flow.edges, ...flow.subFlows.flatMap((subFlow) => subFlow.edges)];
|
|
42
|
+
return allEdges
|
|
43
|
+
.filter((edge) => edge.from === nodeId)
|
|
44
|
+
.map((edge) => {
|
|
45
|
+
const target = allNodes.find((node) => node.id === edge.to);
|
|
46
|
+
return {
|
|
47
|
+
id: edge.id,
|
|
48
|
+
label: target?.label ?? edge.to,
|
|
49
|
+
confidence: edge.confidence ?? "unknown"
|
|
50
|
+
};
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
2
|
import { StatusBadge } from "./StatusBadge.js";
|
|
3
3
|
export function PageList({ pages, selectedPageId, onSelectPage }) {
|
|
4
|
-
return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-pages-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-pages-heading", children: "Pages" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Page list", children: pages.map((page) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-page-button", type: "button", "aria-current": page.id === selectedPageId ? "true" : undefined, "aria-label": `${page.route} ${page.captureStatus} ${page.apiCalls.length} API calls ${page.screenshots.length} screenshots`, onClick: () => onSelectPage(page), children: [_jsx("span", { className: "anlyx-list-item__line", children: _jsx("span", { className: "anlyx-list-item__primary", children: page.route }) }), _jsxs("span", { className: "anlyx-list-item__meta", children: [_jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus }), _jsxs("span", { children: [page.apiCalls.length, " API calls"] }), _jsxs("span", { children: [page.screenshots.length, " screenshots"] })] }), page.errorMessage ? (_jsx("span", { className: "anlyx-list-item__meta", children: page.errorMessage })) : null] }) }, page.id))) })] }));
|
|
4
|
+
return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-pages-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-pages-heading", children: "Frontend Pages" }), _jsx("ul", { className: "anlyx-list", "aria-label": "Page list", children: pages.map((page) => (_jsx("li", { className: "anlyx-list-item", children: _jsxs("button", { className: "anlyx-page-button", type: "button", "aria-current": page.id === selectedPageId ? "true" : undefined, "aria-label": `${page.route} ${page.captureStatus} ${page.apiCalls.length} API calls ${page.screenshots.length} screenshots`, onClick: () => onSelectPage(page), children: [_jsx("span", { className: "anlyx-list-item__line", children: _jsx("span", { className: "anlyx-list-item__primary", children: page.route }) }), _jsxs("span", { className: "anlyx-list-item__meta", children: [_jsx(StatusBadge, { tone: page.captureStatus, children: page.captureStatus }), _jsxs("span", { children: [page.apiCalls.length, " API calls"] }), _jsxs("span", { children: [page.screenshots.length, " screenshots"] })] }), page.errorMessage ? (_jsx("span", { className: "anlyx-list-item__meta", children: page.errorMessage })) : null] }) }, page.id))) })] }));
|
|
5
5
|
}
|
|
6
|
-
//# sourceMappingURL=PageList.js.map
|
|
@@ -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,17 @@ 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
|
+
const linkedApiCallCount = page?.apiCalls.filter((apiCall) => apiCall.endpointId).length ?? 0;
|
|
11
|
+
const unmatchedApiCallCount = page ? page.apiCalls.length - linkedApiCallCount : 0;
|
|
12
|
+
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 } : {}) }), _jsx(PageEvidenceBoard, { linkedApiCallCount: linkedApiCallCount, linkedEndpointCount: linkedEndpoints.length, page: page, unmatchedApiCallCount: unmatchedApiCallCount }), _jsxs("div", { className: "anlyx-storyboard-grid", children: [_jsx(PageStoryboardCard, { page: page }), _jsxs("div", { className: "anlyx-storyboard-side", children: [_jsx(ApiCallList, { apiCalls: page.apiCalls, endpoints: data.endpoints }), _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." }) })) })] }));
|
|
13
|
+
}
|
|
14
|
+
function PageEvidenceBoard({ linkedApiCallCount, linkedEndpointCount, page, unmatchedApiCallCount }) {
|
|
15
|
+
return (_jsxs("section", { className: "anlyx-page-evidence-board", role: "region", "aria-label": "Page execution evidence", children: [_jsxs("div", { className: "anlyx-page-evidence-board__intro", children: [_jsx("span", { children: "Capture proof" }), _jsx("strong", { children: page.captureStatus === "success" ? "Observed page run" : "Incomplete page run" })] }), _jsx(EvidenceMetric, { label: "Screenshots", value: String(page.screenshots.length), detail: page.screenshots.length > 0 ? "visual segments captured" : "waiting for capture" }), _jsx(EvidenceMetric, { label: "API evidence", value: String(page.apiCalls.length), detail: `${linkedApiCallCount} linked` }), _jsx(EvidenceMetric, { label: "Backend linkage", value: String(linkedEndpointCount), detail: `${unmatchedApiCallCount} unmatched` })] }));
|
|
16
|
+
}
|
|
17
|
+
function EvidenceMetric({ detail, label, value }) {
|
|
18
|
+
return (_jsxs("div", { className: "anlyx-page-evidence-metric", children: [_jsx("span", { children: label }), _jsx("strong", { children: value }), _jsx("em", { children: detail })] }));
|
|
8
19
|
}
|
|
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,16 @@
|
|
|
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
|
+
const replayStepLabels = buildReplayStepLabels(flow);
|
|
7
|
+
return (_jsxs("div", { className: "anlyx-process-view", children: [_jsx(ReplayControls, { disabled: replayDisabled, loop: replayLoop, speed: replaySpeed, state: replayState, stepLabels: replayStepLabels, 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
|
|
8
|
+
? `${endpoint.method} ${endpoint.path}`
|
|
9
|
+
: "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 })] }));
|
|
10
|
+
}
|
|
11
|
+
function buildReplayStepLabels(flow) {
|
|
12
|
+
if (!flow) {
|
|
13
|
+
return {};
|
|
14
|
+
}
|
|
15
|
+
return Object.fromEntries(flow.nodes.map((node) => [node.id, node.label]));
|
|
16
|
+
}
|
|
@@ -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,17 @@
|
|
|
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[];
|
|
6
|
+
stepLabels?: Record<string, string>;
|
|
4
7
|
loop: boolean;
|
|
5
8
|
disabled?: boolean;
|
|
9
|
+
speed: number;
|
|
6
10
|
unavailableReason?: string;
|
|
7
11
|
onPlay: () => void;
|
|
8
12
|
onPause: () => void;
|
|
9
13
|
onRestart: () => void;
|
|
10
14
|
onToggleLoop: () => void;
|
|
15
|
+
onSpeedChange: (speed: number) => void;
|
|
11
16
|
};
|
|
12
|
-
export declare function ReplayControls({ state, loop, disabled, unavailableReason, onPlay, onPause, onRestart, onToggleLoop }: ReplayControlsProps): JSX.Element;
|
|
13
|
-
//# sourceMappingURL=ReplayControls.d.ts.map
|
|
17
|
+
export declare function ReplayControls({ state, steps, stepLabels, loop, speed, disabled, unavailableReason, onPlay, onPause, onRestart, onToggleLoop, onSpeedChange }: ReplayControlsProps): JSX.Element;
|
|
@@ -1,5 +1,33 @@
|
|
|
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, stepLabels = {}, 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
|
+
const currentStep = state.phase === "idle" || state.phase === "complete"
|
|
6
|
+
? undefined
|
|
7
|
+
: steps[state.currentStepIndex];
|
|
8
|
+
const focusLabel = currentStep ? formatStepNode(currentStep.nodeId, stepLabels) : "Ready";
|
|
9
|
+
const edgeLabel = formatStepEdge(currentStep, stepLabels);
|
|
10
|
+
const phaseLabel = formatReplayPhase(state.phase);
|
|
11
|
+
return (_jsxs("section", { className: "anlyx-replay", "aria-label": "Process Flow controls", children: [_jsxs("div", { className: "anlyx-replay__top", 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" })] })] }), _jsxs("div", { className: "anlyx-replay__focus", role: "group", "aria-label": "Replay focus", children: [_jsxs("div", { children: [_jsx("span", { className: "anlyx-replay__phase", children: phaseLabel }), _jsx("strong", { children: focusLabel }), _jsx("p", { children: edgeLabel })] }), _jsx("ol", { className: "anlyx-replay__rail", "aria-label": "Replay step rail", children: steps.map((step, index) => (_jsx("li", { className: index === state.currentStepIndex ? "anlyx-replay__rail-step--active" : "", "aria-current": index === state.currentStepIndex ? "step" : undefined, title: `${formatReplayPhase(step.phase)}: ${formatStepNode(step.nodeId, stepLabels)}`, children: _jsx("span", { children: String(index + 1).padStart(2, "0") }) }, `${step.phase}:${step.nodeId}:${step.index}`))) })] }), disabled && unavailableReason ? (_jsx("p", { className: "anlyx-replay__empty", children: unavailableReason })) : null] }));
|
|
12
|
+
}
|
|
13
|
+
function formatReplayPhase(phase) {
|
|
14
|
+
switch (phase) {
|
|
15
|
+
case "request":
|
|
16
|
+
return "Request travel";
|
|
17
|
+
case "response":
|
|
18
|
+
return "Response return";
|
|
19
|
+
case "complete":
|
|
20
|
+
return "Response delivered";
|
|
21
|
+
case "idle":
|
|
22
|
+
return "Ready to replay";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function formatStepNode(nodeId, labels) {
|
|
26
|
+
return labels[nodeId] ?? nodeId;
|
|
27
|
+
}
|
|
28
|
+
function formatStepEdge(step, labels) {
|
|
29
|
+
if (!step?.fromNodeId || !step.toNodeId) {
|
|
30
|
+
return "Waiting at the first visible node in the scanned main path.";
|
|
31
|
+
}
|
|
32
|
+
return `${formatStepNode(step.fromNodeId, labels)} -> ${formatStepNode(step.toNodeId, labels)}`;
|
|
4
33
|
}
|
|
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: "flowStory" | "structure" | "frontend" | "process";
|
|
5
|
+
collapsed: boolean;
|
|
5
6
|
selectedEndpointId: string | undefined;
|
|
6
7
|
selectedPageId: string | undefined;
|
|
7
|
-
onSelectView: (view: "
|
|
8
|
+
onSelectView: (view: "flowStory" | "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,22 @@
|
|
|
1
|
-
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { Box, 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: "Interaction flow map" })] }), _jsx("button", { className: "anlyx-panel-toggle", type: "button", "aria-label": "Collapse navigation panel", onClick: onToggleCollapsed, children: _jsx(PanelLeftClose, { size: 15, strokeWidth: 2.4 }) })] }), _jsxs("button", { className: "anlyx-project-select", type: "button", "aria-label": `Project ${data.projectName}`, children: [_jsx(Box, { size: 15, strokeWidth: 2.4 }), _jsx("span", { children: data.projectName })] }), _jsxs("nav", { className: "anlyx-tabs", "aria-label": "Views", children: [_jsx("button", { className: activeView === "flowStory" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("flowStory"), children: "Flow Story" }), _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: "Captures" }), _jsx("button", { className: activeView === "process" ? "anlyx-tab anlyx-tab--active" : "anlyx-tab", type: "button", onClick: () => onSelectView("process"), children: "Process" })] }), _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 pages, endpoints, or services", type: "search" })] })] }), _jsxs("div", { className: "anlyx-sidebar__list-region", children: [_jsx(PageList, { pages: data.pages, selectedPageId: selectedPageId, onSelectPage: onSelectPage }), _jsx(EndpointList, { endpoints: data.endpoints, selectedEndpointId: selectedEndpointId, onSelectEndpoint: onSelectEndpoint }), _jsx(BackendServiceList, { data: data })] })] }));
|
|
10
|
+
}
|
|
11
|
+
function BackendServiceList({ data }) {
|
|
12
|
+
const services = [
|
|
13
|
+
...new Map(data.flows
|
|
14
|
+
.flatMap((flow) => [...flow.nodes, ...flow.subFlows.flatMap((subFlow) => subFlow.nodes)])
|
|
15
|
+
.filter((node) => ["service", "repository", "mapper", "validator", "utility"].includes(node.type))
|
|
16
|
+
.map((node) => [node.id, node])).values()
|
|
17
|
+
];
|
|
18
|
+
if (services.length === 0) {
|
|
19
|
+
return _jsx(_Fragment, {});
|
|
20
|
+
}
|
|
21
|
+
return (_jsxs("section", { className: "anlyx-sidebar-section", "aria-labelledby": "anlyx-services-heading", children: [_jsx("div", { className: "anlyx-section-heading", id: "anlyx-services-heading", children: "Backend Services" }), _jsx("ul", { className: "anlyx-list anlyx-list--compact", "aria-label": "Backend service list", children: services.map((node) => (_jsxs("li", { className: "anlyx-service-row", children: [_jsx(Box, { size: 14, strokeWidth: 2.3 }), _jsx("span", { children: node.label })] }, node.id))) })] }));
|
|
6
22
|
}
|
|
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
|