@signalsafe/tree-spec-editor 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +659 -4
- package/dist/index.d.ts +48 -7
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +39 -5
- package/dist/lib/PanelHeaderCollapseCarets.d.ts +10 -0
- package/dist/lib/PanelHeaderCollapseCarets.d.ts.map +1 -0
- package/dist/lib/PanelHeaderCollapseCarets.js +13 -0
- package/dist/lib/buildDefaultToolbarSpec.d.ts +157 -0
- package/dist/lib/buildDefaultToolbarSpec.d.ts.map +1 -0
- package/dist/lib/buildDefaultToolbarSpec.js +202 -0
- package/dist/lib/panelHelpers.d.ts +9 -0
- package/dist/lib/panelHelpers.d.ts.map +1 -0
- package/dist/lib/panelHelpers.js +22 -0
- package/dist/modals/AuditLogModal.d.ts +21 -0
- package/dist/modals/AuditLogModal.d.ts.map +1 -0
- package/dist/modals/AuditLogModal.js +23 -0
- package/dist/modals/DraftHistoryModal.d.ts +34 -0
- package/dist/modals/DraftHistoryModal.d.ts.map +1 -0
- package/dist/modals/DraftHistoryModal.js +27 -0
- package/dist/modals/PublishReviewModal.d.ts +37 -0
- package/dist/modals/PublishReviewModal.d.ts.map +1 -0
- package/dist/modals/PublishReviewModal.js +25 -0
- package/dist/panels/AdvancedJsonPanel.d.ts +27 -0
- package/dist/panels/AdvancedJsonPanel.d.ts.map +1 -0
- package/dist/panels/AdvancedJsonPanel.js +11 -0
- package/dist/panels/GraphEditorInfoPanel.d.ts +20 -0
- package/dist/panels/GraphEditorInfoPanel.d.ts.map +1 -0
- package/dist/panels/GraphEditorInfoPanel.js +25 -0
- package/dist/panels/InspectorPanel.d.ts +79 -0
- package/dist/panels/InspectorPanel.d.ts.map +1 -0
- package/dist/panels/InspectorPanel.js +63 -0
- package/dist/panels/IssuesPanel.d.ts +33 -0
- package/dist/panels/IssuesPanel.d.ts.map +1 -0
- package/dist/panels/IssuesPanel.js +46 -0
- package/dist/panels/NodesPanel.d.ts +28 -0
- package/dist/panels/NodesPanel.d.ts.map +1 -0
- package/dist/panels/NodesPanel.js +56 -0
- package/dist/panels/SelectedEdgePanel.d.ts +18 -0
- package/dist/panels/SelectedEdgePanel.d.ts.map +1 -0
- package/dist/panels/SelectedEdgePanel.js +9 -0
- package/dist/panels/ToolbarPanel.d.ts +90 -0
- package/dist/panels/ToolbarPanel.d.ts.map +1 -0
- package/dist/panels/ToolbarPanel.js +59 -0
- package/package.json +7 -3
- package/dist/TreeSpecGraphEditor.d.ts +0 -16
- package/dist/TreeSpecGraphEditor.d.ts.map +0 -1
- package/dist/TreeSpecGraphEditor.js +0 -272
- package/dist/editorHelpers.d.ts +0 -16
- package/dist/editorHelpers.d.ts.map +0 -1
- package/dist/editorHelpers.js +0 -67
- package/dist/model.d.ts +0 -23
- package/dist/model.d.ts.map +0 -1
- package/dist/model.js +0 -1
- package/dist/treeLayout.d.ts +0 -11
- package/dist/treeLayout.d.ts.map +0 -1
- package/dist/treeLayout.js +0 -63
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { Button, CloseButton } from 'react-bootstrap';
|
|
3
|
+
const DEFAULT_TITLE = 'Draft history';
|
|
4
|
+
const DEFAULT_SUBTITLE = 'Restore an earlier snapshot into the current draft. Creating a snapshot saves the current graph state.';
|
|
5
|
+
const DEFAULT_LIST_CAPTION = 'Snapshots (newest first)';
|
|
6
|
+
const DEFAULT_CREATE_LABEL = 'Create snapshot';
|
|
7
|
+
const DEFAULT_CREATING_LABEL = 'Creating…';
|
|
8
|
+
const DEFAULT_RESTORE_LABEL = 'Restore';
|
|
9
|
+
const DEFAULT_RESTORING_LABEL = 'Restoring…';
|
|
10
|
+
const DEFAULT_LOADING_TEXT = 'Loading…';
|
|
11
|
+
const DEFAULT_EMPTY_STATE_TEXT = 'No snapshots yet. Create one to save the current draft.';
|
|
12
|
+
const DEFAULT_CLOSE_LABEL = 'Close';
|
|
13
|
+
export default function DraftHistoryModal({ show, loadingSnapshots, snapshots, restoringSnapshotId, creatingSnapshot, tree, onClose, onCreateSnapshot, onRestoreSnapshot, title = DEFAULT_TITLE, subtitle = DEFAULT_SUBTITLE, listCaption = DEFAULT_LIST_CAPTION, createSnapshotLabel = DEFAULT_CREATE_LABEL, creatingSnapshotLabel = DEFAULT_CREATING_LABEL, restoreLabel = DEFAULT_RESTORE_LABEL, restoringLabel = DEFAULT_RESTORING_LABEL, loadingText = DEFAULT_LOADING_TEXT, emptyStateText = DEFAULT_EMPTY_STATE_TEXT, closeLabel = DEFAULT_CLOSE_LABEL, }) {
|
|
14
|
+
if (!show)
|
|
15
|
+
return null;
|
|
16
|
+
let snapshotsContent;
|
|
17
|
+
if (loadingSnapshots) {
|
|
18
|
+
snapshotsContent = _jsx("div", { className: "text-muted", children: loadingText });
|
|
19
|
+
}
|
|
20
|
+
else if (snapshots.length === 0) {
|
|
21
|
+
snapshotsContent = (_jsx("div", { className: "text-muted", children: _jsx("em", { children: emptyStateText }) }));
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
snapshotsContent = (_jsx("ul", { className: "list-unstyled mb-0 overflow-auto-max-h-320", children: snapshots.map((s) => (_jsxs("li", { className: "d-flex justify-content-between align-items-center border-bottom py-2", children: [_jsxs("div", { children: [_jsx("span", { className: "text-muted font-size-12", children: new Date(s.created_at).toLocaleString() }), s.label ? _jsx("span", { className: "ms-2", children: s.label }) : null, s.spec_hash ? (_jsxs("span", { className: "ms-2 text-muted font-size-11", children: [s.spec_hash.slice(0, 8), "\u2026"] })) : null] }), _jsx(Button, { variant: "outline-warning", onClick: () => onRestoreSnapshot(s.id), disabled: restoringSnapshotId !== null, children: restoringSnapshotId === s.id ? restoringLabel : restoreLabel })] }, s.id))) }));
|
|
25
|
+
}
|
|
26
|
+
return (_jsx("dialog", { className: "modal d-block modal-backdrop-dark", open: true, children: _jsx("div", { className: "modal-dialog modal-lg", children: _jsxs("div", { className: "modal-content", children: [_jsxs("div", { className: "modal-header", children: [_jsx("h5", { className: "modal-title", children: title }), _jsx(CloseButton, { "aria-label": "Close", onClick: onClose })] }), _jsxs("div", { className: "modal-body", children: [_jsx("p", { className: "text-muted font-size-13", children: subtitle }), _jsxs("div", { className: "d-flex justify-content-between align-items-center mb-3", children: [_jsx("span", { className: "text-muted font-size-13", children: listCaption }), _jsx(Button, { variant: "outline-primary", onClick: onCreateSnapshot, disabled: !tree || creatingSnapshot, children: creatingSnapshot ? creatingSnapshotLabel : createSnapshotLabel })] }), snapshotsContent] }), _jsx("div", { className: "modal-footer", children: _jsx(Button, { variant: "secondary", onClick: onClose, children: closeLabel }) })] }) }) }));
|
|
27
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Publish-review modal for the TreeSpec graph editor.
|
|
3
|
+
*
|
|
4
|
+
* Presentational only: receives the current tree, the baseline tree (loaded from the
|
|
5
|
+
* server before edits), the current validation issues, and callbacks. The diff
|
|
6
|
+
* computation is injected via `computeDiffSummary` so the package stays free of
|
|
7
|
+
* host-specific diff logic.
|
|
8
|
+
*/
|
|
9
|
+
import { type TreeSpecIssue } from '@signalsafe/tree-spec';
|
|
10
|
+
import { type EditorTree } from '@signalsafe/tree-spec-editor-core';
|
|
11
|
+
export interface PublishReviewModalProps {
|
|
12
|
+
show: boolean;
|
|
13
|
+
tree: EditorTree | null;
|
|
14
|
+
baselineTree: EditorTree | null;
|
|
15
|
+
issues: TreeSpecIssue[];
|
|
16
|
+
publishing: boolean;
|
|
17
|
+
onClose: () => void;
|
|
18
|
+
onPublish: () => void;
|
|
19
|
+
computeDiffSummary: (before: EditorTree | null, after: EditorTree) => {
|
|
20
|
+
lines: string[];
|
|
21
|
+
hasChanges: boolean;
|
|
22
|
+
};
|
|
23
|
+
/** Override the card title (default "Review changes before publishing"). */
|
|
24
|
+
title?: string;
|
|
25
|
+
/** Override the info alert text shown above the diff summary. */
|
|
26
|
+
summaryText?: string;
|
|
27
|
+
/** Override the error alert shown when validation issues exist. */
|
|
28
|
+
errorAlertText?: string;
|
|
29
|
+
/** Override the Cancel button label. */
|
|
30
|
+
cancelLabel?: string;
|
|
31
|
+
/** Override the Publish button label (default "Publish now"). */
|
|
32
|
+
publishLabel?: string;
|
|
33
|
+
/** Override the Publish button label while publishing (default "Publishing\u2026"). */
|
|
34
|
+
publishingLabel?: string;
|
|
35
|
+
}
|
|
36
|
+
export default function PublishReviewModal({ show, tree, baselineTree, issues, publishing, onClose, onPublish, computeDiffSummary, title, summaryText, errorAlertText, cancelLabel, publishLabel, publishingLabel, }: Readonly<PublishReviewModalProps>): import("react/jsx-runtime").JSX.Element | null;
|
|
37
|
+
//# sourceMappingURL=PublishReviewModal.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"PublishReviewModal.d.ts","sourceRoot":"","sources":["../../src/modals/PublishReviewModal.tsx"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,EAA4B,KAAK,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGrF,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAExF,MAAM,WAAW,uBAAuB;IACpC,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,UAAU,EAAE,OAAO,CAAC;IACpB,OAAO,EAAE,MAAM,IAAI,CAAC;IACpB,SAAS,EAAE,MAAM,IAAI,CAAC;IACtB,kBAAkB,EAAE,CAChB,MAAM,EAAE,UAAU,GAAG,IAAI,EACzB,KAAK,EAAE,UAAU,KAChB;QAAE,KAAK,EAAE,MAAM,EAAE,CAAC;QAAC,UAAU,EAAE,OAAO,CAAA;KAAE,CAAC;IAE9C,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,iEAAiE;IACjE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,mEAAmE;IACnE,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,wCAAwC;IACxC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,eAAe,CAAC,EAAE,MAAM,CAAC;CAC5B;AAUD,MAAM,CAAC,OAAO,UAAU,kBAAkB,CAAC,EACvC,IAAI,EACJ,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,UAAU,EACV,OAAO,EACP,SAAS,EACT,kBAAkB,EAClB,KAAqB,EACrB,WAAkC,EAClC,cAAyC,EACzC,WAAkC,EAClC,YAAoC,EACpC,eAA0C,GAC7C,EAAE,QAAQ,CAAC,uBAAuB,CAAC,kDAyCnC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* Publish-review modal for the TreeSpec graph editor.
|
|
4
|
+
*
|
|
5
|
+
* Presentational only: receives the current tree, the baseline tree (loaded from the
|
|
6
|
+
* server before edits), the current validation issues, and callbacks. The diff
|
|
7
|
+
* computation is injected via `computeDiffSummary` so the package stays free of
|
|
8
|
+
* host-specific diff logic.
|
|
9
|
+
*/
|
|
10
|
+
import { TREE_SPEC_ISSUE_SEVERITY } from '@signalsafe/tree-spec';
|
|
11
|
+
import { Button, CloseButton } from 'react-bootstrap';
|
|
12
|
+
import { buildStableEntries } from '@signalsafe/tree-spec-editor-core';
|
|
13
|
+
const DEFAULT_TITLE = 'Review changes before publishing';
|
|
14
|
+
const DEFAULT_SUMMARY_TEXT = 'Publishing is immutable. This summary compares the loaded version spec to your current graph.';
|
|
15
|
+
const DEFAULT_ERROR_ALERT_TEXT = 'Validation errors exist. Fix them before publishing.';
|
|
16
|
+
const DEFAULT_CANCEL_LABEL = 'Cancel';
|
|
17
|
+
const DEFAULT_PUBLISH_LABEL = 'Publish now';
|
|
18
|
+
const DEFAULT_PUBLISHING_LABEL = 'Publishing…';
|
|
19
|
+
export default function PublishReviewModal({ show, tree, baselineTree, issues, publishing, onClose, onPublish, computeDiffSummary, title = DEFAULT_TITLE, summaryText = DEFAULT_SUMMARY_TEXT, errorAlertText = DEFAULT_ERROR_ALERT_TEXT, cancelLabel = DEFAULT_CANCEL_LABEL, publishLabel = DEFAULT_PUBLISH_LABEL, publishingLabel = DEFAULT_PUBLISHING_LABEL, }) {
|
|
20
|
+
if (!show)
|
|
21
|
+
return null;
|
|
22
|
+
const hasErrors = issues.some((i) => i.severity === TREE_SPEC_ISSUE_SEVERITY.ERROR);
|
|
23
|
+
const diffLineEntries = buildStableEntries(tree ? computeDiffSummary(baselineTree, tree).lines : [], (line) => line);
|
|
24
|
+
return (_jsx("dialog", { className: "modal d-block modal-backdrop-dark", open: true, children: _jsx("div", { className: "modal-dialog modal-lg", children: _jsxs("div", { className: "modal-content", children: [_jsxs("div", { className: "modal-header", children: [_jsx("h5", { className: "modal-title", children: title }), _jsx(CloseButton, { "aria-label": "Close", onClick: onClose })] }), _jsxs("div", { className: "modal-body", children: [_jsx("div", { className: "alert alert-info font-size-13", children: summaryText }), _jsx("ul", { children: diffLineEntries.map(({ item: line, key }) => (_jsx("li", { children: line }, key))) }), hasErrors ? (_jsx("div", { className: "alert alert-danger", children: errorAlertText })) : null] }), _jsxs("div", { className: "modal-footer", children: [_jsx(Button, { variant: "outline-secondary", onClick: onClose, children: cancelLabel }), _jsx(Button, { variant: "success", disabled: publishing || hasErrors, onClick: onPublish, children: publishing ? publishingLabel : publishLabel })] })] }) }) }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
export interface AdvancedJsonPanelProps {
|
|
3
|
+
/** The JSON editor (or any content) rendered inside the panel body. */
|
|
4
|
+
children: ReactNode;
|
|
5
|
+
/** Optional header title. Defaults to "Advanced JSON (read-only)". */
|
|
6
|
+
title?: string;
|
|
7
|
+
/** Optional muted subtitle line below the title. */
|
|
8
|
+
subtitle?: string;
|
|
9
|
+
/**
|
|
10
|
+
* Called when the user clicks the "Expand all" button. The button is rendered only when
|
|
11
|
+
* this callback is provided. Hosts typically wire this to a `JsonEditorHandle.expandAll()` ref.
|
|
12
|
+
*/
|
|
13
|
+
onExpandAll?: () => void;
|
|
14
|
+
/**
|
|
15
|
+
* Called when the user clicks the "Compress all" button. The button is rendered only when
|
|
16
|
+
* this callback is provided. Hosts typically wire this to a `JsonEditorHandle.collapseAll()` ref.
|
|
17
|
+
*/
|
|
18
|
+
onCollapseAll?: () => void;
|
|
19
|
+
/** Optional override for the expand button label (default "Expand all"). */
|
|
20
|
+
expandAllLabel?: string;
|
|
21
|
+
/** Optional override for the collapse button label (default "Compress all"). */
|
|
22
|
+
collapseAllLabel?: string;
|
|
23
|
+
/** Optional extra class names applied to the outer `card` element. */
|
|
24
|
+
className?: string;
|
|
25
|
+
}
|
|
26
|
+
export default function AdvancedJsonPanel({ children, title, subtitle, onExpandAll, onCollapseAll, expandAllLabel, collapseAllLabel, className, }: Readonly<AdvancedJsonPanelProps>): import("react/jsx-runtime").JSX.Element;
|
|
27
|
+
//# sourceMappingURL=AdvancedJsonPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"AdvancedJsonPanel.d.ts","sourceRoot":"","sources":["../../src/panels/AdvancedJsonPanel.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAMvC,MAAM,WAAW,sBAAsB;IACnC,uEAAuE;IACvE,QAAQ,EAAE,SAAS,CAAC;IACpB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;IACzB;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,IAAI,CAAC;IAC3B,4EAA4E;IAC5E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,gFAAgF;IAChF,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAID,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EACtC,QAAQ,EACR,KAAqB,EACrB,QAAQ,EACR,WAAW,EACX,aAAa,EACb,cAA6B,EAC7B,gBAAiC,EACjC,SAAS,GACZ,EAAE,QAAQ,CAAC,sBAAsB,CAAC,2CAsClC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Button } from 'react-bootstrap';
|
|
4
|
+
import PanelHeaderCollapseCarets from '../lib/PanelHeaderCollapseCarets';
|
|
5
|
+
const DEFAULT_TITLE = 'Advanced JSON (read-only)';
|
|
6
|
+
export default function AdvancedJsonPanel({ children, title = DEFAULT_TITLE, subtitle, onExpandAll, onCollapseAll, expandAllLabel = 'Expand all', collapseAllLabel = 'Compress all', className, }) {
|
|
7
|
+
const [expanded, setExpanded] = useState(true);
|
|
8
|
+
const hasButtons = Boolean(onExpandAll) || Boolean(onCollapseAll);
|
|
9
|
+
const cardClassName = ['card', 'mt-3', className].filter(Boolean).join(' ');
|
|
10
|
+
return (_jsxs("div", { className: cardClassName, children: [_jsxs("div", { className: "card-header d-flex justify-content-between align-items-start gap-2 flex-wrap bg-body-secondary py-2 px-2", children: [_jsxs("div", { className: "min-w-0", children: [_jsxs("div", { className: "d-flex align-items-center flex-wrap gap-1", children: [_jsx("span", { className: "fw-bold", children: title }), _jsx(PanelHeaderCollapseCarets, { expanded: expanded, onToggle: () => setExpanded((v) => !v) })] }), subtitle ? _jsx("div", { className: "text-muted font-size-12", children: subtitle }) : null] }), hasButtons ? (_jsxs("div", { className: "btn-group btn-group-sm flex-shrink-0 ms-auto", children: [onExpandAll ? (_jsx(Button, { type: "button", variant: "outline-secondary", onClick: onExpandAll, children: expandAllLabel })) : null, onCollapseAll ? (_jsx(Button, { type: "button", variant: "outline-secondary", onClick: onCollapseAll, children: collapseAllLabel })) : null] })) : null] }), _jsx("div", { className: `card-body p-0${expanded ? '' : ' d-none'}`, "aria-hidden": !expanded, children: children })] }));
|
|
11
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type AutosaveStatus } from '@signalsafe/tree-spec-editor-core';
|
|
2
|
+
export interface GraphEditorInfoPanelProps {
|
|
3
|
+
scenarioId: string;
|
|
4
|
+
versionId: string;
|
|
5
|
+
name: string;
|
|
6
|
+
createdAt: string | null;
|
|
7
|
+
updatedAt: string | null;
|
|
8
|
+
/** Whether this version is the published (read-only) revision. */
|
|
9
|
+
isPublished: boolean;
|
|
10
|
+
/** Autosave lifecycle state from the graph editor hook. */
|
|
11
|
+
autosaveStatus: AutosaveStatus;
|
|
12
|
+
/** Last validation time (ISO), or `null` if never validated in this session. */
|
|
13
|
+
lastValidatedAt: string | null;
|
|
14
|
+
/** Optional card header title. Defaults to "Info". */
|
|
15
|
+
title?: string;
|
|
16
|
+
/** Optional formatter for ISO timestamps. Defaults to `new Date(iso).toLocaleString()`. */
|
|
17
|
+
formatTimestamp?: (iso: string) => string;
|
|
18
|
+
}
|
|
19
|
+
export default function GraphEditorInfoPanel({ scenarioId, versionId, name, createdAt, updatedAt, isPublished, autosaveStatus, lastValidatedAt, title, formatTimestamp, }: Readonly<GraphEditorInfoPanelProps>): import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
//# sourceMappingURL=GraphEditorInfoPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GraphEditorInfoPanel.d.ts","sourceRoot":"","sources":["../../src/panels/GraphEditorInfoPanel.tsx"],"names":[],"mappings":"AAKA,OAAO,EAA0B,KAAK,cAAc,EAAE,MAAM,mCAAmC,CAAC;AAEhG,MAAM,WAAW,yBAAyB;IACtC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;IACzB,kEAAkE;IAClE,WAAW,EAAE,OAAO,CAAC;IACrB,2DAA2D;IAC3D,cAAc,EAAE,cAAc,CAAC;IAC/B,gFAAgF;IAChF,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,sDAAsD;IACtD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,2FAA2F;IAC3F,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;CAC7C;AA0BD,MAAM,CAAC,OAAO,UAAU,oBAAoB,CAAC,EACzC,UAAU,EACV,SAAS,EACT,IAAI,EACJ,SAAS,EACT,SAAS,EACT,WAAW,EACX,cAAc,EACd,eAAe,EACf,KAAc,EACd,eAAwC,GAC3C,EAAE,QAAQ,CAAC,yBAAyB,CAAC,2CA4DrC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Badge, Table } from 'react-bootstrap';
|
|
4
|
+
import PanelHeaderCollapseCarets from '../lib/PanelHeaderCollapseCarets';
|
|
5
|
+
import { getAutosaveStatusLabel } from '@signalsafe/tree-spec-editor-core';
|
|
6
|
+
function defaultFormatTimestamp(iso) {
|
|
7
|
+
return new Date(iso).toLocaleString();
|
|
8
|
+
}
|
|
9
|
+
function PropertyRow({ label, value }) {
|
|
10
|
+
return (_jsxs("tr", { children: [_jsx("td", { className: "align-top", children: _jsx("strong", { children: label }) }), _jsx("td", { children: value })] }));
|
|
11
|
+
}
|
|
12
|
+
const STATUS_DRAFT = 'Draft';
|
|
13
|
+
const STATUS_PUBLISHED = 'Published (read-only)';
|
|
14
|
+
function autosaveDisplay(isPublished, status) {
|
|
15
|
+
if (isPublished)
|
|
16
|
+
return '—';
|
|
17
|
+
const label = getAutosaveStatusLabel(status);
|
|
18
|
+
return label || '—';
|
|
19
|
+
}
|
|
20
|
+
export default function GraphEditorInfoPanel({ scenarioId, versionId, name, createdAt, updatedAt, isPublished, autosaveStatus, lastValidatedAt, title = 'Info', formatTimestamp = defaultFormatTimestamp, }) {
|
|
21
|
+
const [expanded, setExpanded] = useState(true);
|
|
22
|
+
return (_jsxs("div", { className: "card mb-3", children: [_jsx("div", { className: "card-header bg-body-secondary py-2 px-2", children: _jsxs("div", { className: "d-flex align-items-center flex-wrap gap-1", children: [_jsx("h5", { className: "mb-0", children: title }), _jsx(PanelHeaderCollapseCarets, { expanded: expanded, onToggle: () => setExpanded((v) => !v) })] }) }), _jsx("div", { className: `card-body p-0${expanded ? '' : ' d-none'}`, "aria-hidden": !expanded, children: _jsx(Table, { striped: true, bordered: true, hover: true, responsive: true, className: "m-0", children: _jsxs("tbody", { children: [_jsx(PropertyRow, { label: "Name", value: name }), _jsx(PropertyRow, { label: "Status", value: _jsx(Badge, { bg: isPublished ? 'success' : 'secondary', children: isPublished ? STATUS_PUBLISHED : STATUS_DRAFT }) }), _jsx(PropertyRow, { label: "Save state", value: _jsx("span", { className: "text-muted", children: autosaveDisplay(isPublished, autosaveStatus) }) }), _jsx(PropertyRow, { label: "Validated", value: _jsx("span", { className: "text-muted", children: lastValidatedAt
|
|
23
|
+
? `Validated ${formatTimestamp(lastValidatedAt)}`
|
|
24
|
+
: 'Not validated' }) }), _jsx(PropertyRow, { label: "Scenario ID", value: _jsx("span", { className: "font-monospace small text-break", children: scenarioId }) }), _jsx(PropertyRow, { label: "Version ID", value: _jsx("span", { className: "font-monospace small text-break", children: versionId }) }), _jsx(PropertyRow, { label: "Created", value: createdAt ? formatTimestamp(createdAt) : '—' }), _jsx(PropertyRow, { label: "Updated", value: updatedAt ? formatTimestamp(updatedAt) : '—' })] }) }) })] }));
|
|
25
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Inspector panel for the TreeSpec graph editor.
|
|
3
|
+
*
|
|
4
|
+
* Renders a **Required** card (Type, Prompt, optional `renderExtraNodeFields`) and a **Choices**
|
|
5
|
+
* card (list-group rows with Label / Next / Outcome / delete). Hosts inject project-specific UI through optional render-prop slots:
|
|
6
|
+
*
|
|
7
|
+
* - `outcomeOptions`: override the END-transition outcome `<select>` options
|
|
8
|
+
* (default = the three canonical `TerminalOutcome` values from `@signalsafe/tree-spec`).
|
|
9
|
+
* - `hideOutcomeField`: hide the outcome `<select>` entirely when the host manages outcomes
|
|
10
|
+
* elsewhere.
|
|
11
|
+
* - `renderExtraNodeFields`: render additional UI below the prompt textarea, scoped to the
|
|
12
|
+
* currently-selected node.
|
|
13
|
+
* - `renderExtraChoiceFields`: render additional UI below each choice card.
|
|
14
|
+
*/
|
|
15
|
+
import type { ReactNode } from 'react';
|
|
16
|
+
import { type EditorChoice, type EditorNode, type EditorTransition, type EditorTree } from '@signalsafe/tree-spec-editor-core';
|
|
17
|
+
/**
|
|
18
|
+
* Canonical outcome values from the `@signalsafe/tree-spec` wire contract.
|
|
19
|
+
* Hosts can override via the `outcomeOptions` prop.
|
|
20
|
+
*/
|
|
21
|
+
export declare const DEFAULT_OUTCOME_OPTIONS: ReadonlyArray<{
|
|
22
|
+
value: string;
|
|
23
|
+
label: string;
|
|
24
|
+
}>;
|
|
25
|
+
export interface InspectorNodeRenderContext {
|
|
26
|
+
tree: EditorTree;
|
|
27
|
+
node: EditorNode;
|
|
28
|
+
isPublished: boolean;
|
|
29
|
+
onUpdateNode: (patch: Partial<EditorNode>) => void;
|
|
30
|
+
}
|
|
31
|
+
export interface InspectorChoiceRenderContext {
|
|
32
|
+
tree: EditorTree;
|
|
33
|
+
node: EditorNode;
|
|
34
|
+
choice: EditorChoice;
|
|
35
|
+
transition: EditorTransition | undefined;
|
|
36
|
+
isPublished: boolean;
|
|
37
|
+
onUpdateNode: (patch: Partial<EditorNode>) => void;
|
|
38
|
+
}
|
|
39
|
+
export interface InspectorPanelProps {
|
|
40
|
+
tree: EditorTree;
|
|
41
|
+
selectedNode: EditorNode | null;
|
|
42
|
+
focusChoiceId?: string | null;
|
|
43
|
+
isPublished?: boolean;
|
|
44
|
+
onUpdateSelectedNode: (patch: Partial<EditorNode>) => void;
|
|
45
|
+
onAddChoice: () => void;
|
|
46
|
+
onDeleteChoice: (choiceId: string) => void;
|
|
47
|
+
onSetChoiceTarget: (choiceId: string, targetNodeId: string) => void;
|
|
48
|
+
onSetChoiceOutcome: (choiceId: string, outcome: string) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Override the END-transition outcome `<select>` options.
|
|
51
|
+
* Defaults to the canonical TreeSpec `TerminalOutcome` values.
|
|
52
|
+
*/
|
|
53
|
+
outcomeOptions?: ReadonlyArray<{
|
|
54
|
+
value: string;
|
|
55
|
+
label: string;
|
|
56
|
+
}>;
|
|
57
|
+
/** Hide the outcome selector entirely. Useful when the host manages outcomes elsewhere. */
|
|
58
|
+
hideOutcomeField?: boolean;
|
|
59
|
+
/** Render extra UI below the prompt textarea, scoped to the current node. */
|
|
60
|
+
renderExtraNodeFields?: (ctx: InspectorNodeRenderContext) => ReactNode;
|
|
61
|
+
/** Render extra UI below each choice card (after Next/Outcome). */
|
|
62
|
+
renderExtraChoiceFields?: (ctx: InspectorChoiceRenderContext) => ReactNode;
|
|
63
|
+
/**
|
|
64
|
+
* When set, shows a delete control in the Required card header (grid-style icon button).
|
|
65
|
+
* Typical host: `editor.actions.deleteSelectedNode`. Disabled when `isPublished` is true.
|
|
66
|
+
*/
|
|
67
|
+
onDeleteSelectedNode?: () => void;
|
|
68
|
+
/** Override the Required card header (default "Required"). */
|
|
69
|
+
title?: string;
|
|
70
|
+
/** Override the empty-state message (default "Select a node to edit it."). */
|
|
71
|
+
emptyStateText?: string;
|
|
72
|
+
/**
|
|
73
|
+
* Override the muted helper text under the Type field.
|
|
74
|
+
* Defaults to: "Type is stored as a string in tree_spec. You can use presets or any custom value."
|
|
75
|
+
*/
|
|
76
|
+
typeHelperText?: string;
|
|
77
|
+
}
|
|
78
|
+
export default function InspectorPanel({ tree, selectedNode, focusChoiceId, isPublished, onUpdateSelectedNode, onAddChoice, onDeleteChoice, onSetChoiceTarget, onSetChoiceOutcome, outcomeOptions, hideOutcomeField, renderExtraNodeFields, renderExtraChoiceFields, title, emptyStateText, typeHelperText, onDeleteSelectedNode, }: Readonly<InspectorPanelProps>): import("react/jsx-runtime").JSX.Element;
|
|
79
|
+
//# sourceMappingURL=InspectorPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"InspectorPanel.d.ts","sourceRoot":"","sources":["../../src/panels/InspectorPanel.tsx"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AACH,OAAO,KAAK,EAAe,SAAS,EAAE,MAAM,OAAO,CAAC;AAKpD,OAAO,EAIH,KAAK,YAAY,EACjB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,UAAU,EAClB,MAAM,mCAAmC,CAAC;AAI3C;;;GAGG;AACH,eAAO,MAAM,uBAAuB,EAAE,aAAa,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAInF,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACvC,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,4BAA4B;IACzC,IAAI,EAAE,UAAU,CAAC;IACjB,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,EAAE,YAAY,CAAC;IACrB,UAAU,EAAE,gBAAgB,GAAG,SAAS,CAAC;IACzC,WAAW,EAAE,OAAO,CAAC;IACrB,YAAY,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;CACtD;AAED,MAAM,WAAW,mBAAmB;IAChC,IAAI,EAAE,UAAU,CAAC;IACjB,YAAY,EAAE,UAAU,GAAG,IAAI,CAAC;IAChC,aAAa,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,WAAW,CAAC,EAAE,OAAO,CAAC;IAEtB,oBAAoB,EAAE,CAAC,KAAK,EAAE,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI,CAAC;IAC3D,WAAW,EAAE,MAAM,IAAI,CAAC;IACxB,cAAc,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3C,iBAAiB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,KAAK,IAAI,CAAC;IACpE,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhE;;;OAGG;IACH,cAAc,CAAC,EAAE,aAAa,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACjE,2FAA2F;IAC3F,gBAAgB,CAAC,EAAE,OAAO,CAAC;IAE3B,6EAA6E;IAC7E,qBAAqB,CAAC,EAAE,CAAC,GAAG,EAAE,0BAA0B,KAAK,SAAS,CAAC;IACvE,mEAAmE;IACnE,uBAAuB,CAAC,EAAE,CAAC,GAAG,EAAE,4BAA4B,KAAK,SAAS,CAAC;IAE3E;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,IAAI,CAAC;IAElC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;CAC3B;AAoPD,MAAM,CAAC,OAAO,UAAU,cAAc,CAAC,EACnC,IAAI,EACJ,YAAY,EACZ,aAAoB,EACpB,WAAmB,EACnB,oBAAoB,EACpB,WAAW,EACX,cAAc,EACd,iBAAiB,EACjB,kBAAkB,EAClB,cAAwC,EACxC,gBAAwB,EACxB,qBAAqB,EACrB,uBAAuB,EACvB,KAAkB,EAClB,cAA4C,EAC5C,cAAyC,EACzC,oBAAoB,GACvB,EAAE,QAAQ,CAAC,mBAAmB,CAAC,2CAkF/B"}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Form } from 'react-bootstrap';
|
|
4
|
+
import { TERMINAL_OUTCOME } from '@signalsafe/tree-spec';
|
|
5
|
+
import { END_NODE_ID, getTransition, TREE_SPEC_NODE_TYPE_PRESETS, } from '@signalsafe/tree-spec-editor-core';
|
|
6
|
+
import PanelHeaderCollapseCarets from '../lib/PanelHeaderCollapseCarets';
|
|
7
|
+
/**
|
|
8
|
+
* Canonical outcome values from the `@signalsafe/tree-spec` wire contract.
|
|
9
|
+
* Hosts can override via the `outcomeOptions` prop.
|
|
10
|
+
*/
|
|
11
|
+
export const DEFAULT_OUTCOME_OPTIONS = [
|
|
12
|
+
{ value: TERMINAL_OUTCOME.SAFE, label: TERMINAL_OUTCOME.SAFE },
|
|
13
|
+
{ value: TERMINAL_OUTCOME.AT_RISK, label: TERMINAL_OUTCOME.AT_RISK },
|
|
14
|
+
{ value: TERMINAL_OUTCOME.COMPROMISED, label: TERMINAL_OUTCOME.COMPROMISED },
|
|
15
|
+
];
|
|
16
|
+
const DEFAULT_TYPE_HELPER_TEXT = 'Type is stored as a string in tree_spec. You can use presets or any custom value.';
|
|
17
|
+
function NodeTypeField({ selectedNode, isPublished, onUpdateSelectedNode, typeHelperText, }) {
|
|
18
|
+
const isPreset = TREE_SPEC_NODE_TYPE_PRESETS.includes(selectedNode.type);
|
|
19
|
+
return (_jsxs(Form.Group, { className: "mb-2", children: [_jsx(Form.Label, { children: "Type" }), _jsxs(Form.Select, { value: isPreset ? selectedNode.type : '__custom__', onChange: (e) => {
|
|
20
|
+
const nextType = e.target.value;
|
|
21
|
+
if (nextType === '__custom__')
|
|
22
|
+
return;
|
|
23
|
+
onUpdateSelectedNode({ type: nextType });
|
|
24
|
+
}, disabled: isPublished, children: [TREE_SPEC_NODE_TYPE_PRESETS.map((t) => (_jsx("option", { value: t, children: t }, t))), _jsx("option", { value: "__custom__", children: "custom\u2026" })] }), isPreset ? null : (_jsx(Form.Control, { className: "mt-2", value: selectedNode.type, onChange: (e) => onUpdateSelectedNode({ type: e.target.value }), placeholder: "Enter custom type\u2026", disabled: isPublished })), _jsx("div", { className: "text-muted mt-1 font-size-12", children: typeHelperText })] }));
|
|
25
|
+
}
|
|
26
|
+
function ChoiceEditorCard({ choice, tree, selectedNode, focusChoiceId, isPublished, onUpdateSelectedNode, onDeleteChoice, onSetChoiceTarget, onSetChoiceOutcome, outcomeOptions, hideOutcomeField, renderExtraChoiceFields, }) {
|
|
27
|
+
const transition = getTransition(tree, selectedNode.id, choice.id);
|
|
28
|
+
const targetNodeId = transition?.toNodeId ?? '';
|
|
29
|
+
const outcomeValue = transition?.outcome ?? outcomeOptions[0]?.value ?? '';
|
|
30
|
+
const isFocused = focusChoiceId === choice.id;
|
|
31
|
+
const showOutcome = !hideOutcomeField && targetNodeId === END_NODE_ID;
|
|
32
|
+
return (_jsxs("div", { id: `choice-editor-${selectedNode.id}-${choice.id}`, className: `list-group-item py-3 px-3${isFocused ? ' list-group-item-primary' : ''}`, children: [_jsxs("div", { className: "d-flex w-100 justify-content-between align-items-start gap-2 mb-2", children: [_jsx("h5", { className: `mb-0 text-uppercase fs-6 fw-semibold${isFocused ? '' : ' text-body-secondary'}`, children: choice.id }), _jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Delete choice", title: "Delete choice", onClick: () => onDeleteChoice(choice.id), children: _jsx("i", { className: "bi bi-trash text-danger action-icon cursor-pointer", "aria-hidden": true }) })] }), _jsx(Form.Label, { className: "mt-0", children: "Label" }), _jsx(Form.Control, { value: choice.label, onChange: (event) => {
|
|
33
|
+
const updatedChoices = (selectedNode.choices ?? []).map((existingChoice) => existingChoice.id === choice.id
|
|
34
|
+
? { ...existingChoice, label: event.target.value }
|
|
35
|
+
: existingChoice);
|
|
36
|
+
onUpdateSelectedNode({ choices: updatedChoices });
|
|
37
|
+
} }), _jsx(Form.Label, { className: "mt-2", children: "Next" }), _jsxs(Form.Select, { value: targetNodeId, onChange: (e) => onSetChoiceTarget(choice.id, e.target.value), children: [_jsx("option", { value: "", children: "(not connected)" }), Object.values(tree.nodes).map((n) => (_jsxs("option", { value: n.id, children: [n.id, " \u00B7 ", n.prompt?.slice(0, 40) || n.type] }, n.id))), _jsx("option", { value: END_NODE_ID, children: "END" })] }), showOutcome ? (_jsxs(_Fragment, { children: [_jsx(Form.Label, { className: "mt-2", children: "Outcome" }), _jsx(Form.Select, { value: outcomeValue, onChange: (e) => onSetChoiceOutcome(choice.id, e.target.value), children: outcomeOptions.map((opt) => (_jsx("option", { value: opt.value, children: opt.label }, opt.value))) })] })) : null, renderExtraChoiceFields
|
|
38
|
+
? renderExtraChoiceFields({
|
|
39
|
+
tree,
|
|
40
|
+
node: selectedNode,
|
|
41
|
+
choice,
|
|
42
|
+
transition,
|
|
43
|
+
isPublished,
|
|
44
|
+
onUpdateNode: onUpdateSelectedNode,
|
|
45
|
+
})
|
|
46
|
+
: null] }));
|
|
47
|
+
}
|
|
48
|
+
function ChoiceEditorList({ tree, selectedNode, focusChoiceId, isPublished, onAddChoice, onUpdateSelectedNode, onDeleteChoice, onSetChoiceTarget, onSetChoiceOutcome, outcomeOptions, hideOutcomeField, renderExtraChoiceFields, }) {
|
|
49
|
+
const choices = selectedNode.choices ?? [];
|
|
50
|
+
const [choicesExpanded, setChoicesExpanded] = useState(true);
|
|
51
|
+
return (_jsxs("div", { className: "card mt-3", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: "Choices" }), _jsx(PanelHeaderCollapseCarets, { expanded: choicesExpanded, onToggle: () => setChoicesExpanded((v) => !v) })] }), _jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Add choice", title: "Add choice", onClick: onAddChoice, children: _jsx("i", { className: "bi bi-plus-lg text-primary action-icon cursor-pointer", "aria-hidden": true }) })] }), _jsx("div", { className: `card-body p-0${choicesExpanded ? '' : ' d-none'}`, "aria-hidden": !choicesExpanded, children: choices.length === 0 ? (_jsx("div", { className: "text-muted small py-3 px-3", children: _jsx("em", { children: "No choices" }) })) : (_jsx("div", { className: "list-group list-group-flush", children: choices.map((choice) => (_jsx(ChoiceEditorCard, { choice: choice, tree: tree, selectedNode: selectedNode, focusChoiceId: focusChoiceId, isPublished: isPublished, onUpdateSelectedNode: onUpdateSelectedNode, onDeleteChoice: onDeleteChoice, onSetChoiceTarget: onSetChoiceTarget, onSetChoiceOutcome: onSetChoiceOutcome, outcomeOptions: outcomeOptions, hideOutcomeField: hideOutcomeField, renderExtraChoiceFields: renderExtraChoiceFields }, choice.id))) })) })] }));
|
|
52
|
+
}
|
|
53
|
+
export default function InspectorPanel({ tree, selectedNode, focusChoiceId = null, isPublished = false, onUpdateSelectedNode, onAddChoice, onDeleteChoice, onSetChoiceTarget, onSetChoiceOutcome, outcomeOptions = DEFAULT_OUTCOME_OPTIONS, hideOutcomeField = false, renderExtraNodeFields, renderExtraChoiceFields, title = 'Required', emptyStateText = 'Select a node to edit it.', typeHelperText = DEFAULT_TYPE_HELPER_TEXT, onDeleteSelectedNode, }) {
|
|
54
|
+
const [requiredExpanded, setRequiredExpanded] = useState(true);
|
|
55
|
+
return (_jsx("div", { children: selectedNode ? (_jsxs(_Fragment, { children: [_jsxs("div", { className: "card", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: title }), _jsx(PanelHeaderCollapseCarets, { expanded: requiredExpanded, onToggle: () => setRequiredExpanded((v) => !v) })] }), onDeleteSelectedNode ? (_jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Delete node", title: "Delete node", disabled: isPublished, onClick: onDeleteSelectedNode, children: _jsx("i", { className: `bi bi-trash action-icon cursor-pointer${isPublished ? ' text-secondary opacity-50' : ' text-danger'}`, "aria-hidden": true }) })) : null] }), _jsxs("div", { className: `card-body${requiredExpanded ? '' : ' d-none'}`, "aria-hidden": !requiredExpanded, children: [_jsx(NodeTypeField, { selectedNode: selectedNode, isPublished: isPublished, onUpdateSelectedNode: onUpdateSelectedNode, typeHelperText: typeHelperText }), _jsxs(Form.Group, { className: "mb-2", children: [_jsx(Form.Label, { children: "Prompt" }), _jsx(Form.Control, { as: "textarea", rows: 4, value: selectedNode.prompt, onChange: (e) => onUpdateSelectedNode({ prompt: e.target.value }) })] }), renderExtraNodeFields
|
|
56
|
+
? renderExtraNodeFields({
|
|
57
|
+
tree,
|
|
58
|
+
node: selectedNode,
|
|
59
|
+
isPublished,
|
|
60
|
+
onUpdateNode: onUpdateSelectedNode,
|
|
61
|
+
})
|
|
62
|
+
: null] })] }), _jsx(ChoiceEditorList, { tree: tree, selectedNode: selectedNode, focusChoiceId: focusChoiceId, isPublished: isPublished, onAddChoice: onAddChoice, onUpdateSelectedNode: onUpdateSelectedNode, onDeleteChoice: onDeleteChoice, onSetChoiceTarget: onSetChoiceTarget, onSetChoiceOutcome: onSetChoiceOutcome, outcomeOptions: outcomeOptions, hideOutcomeField: hideOutcomeField, renderExtraChoiceFields: renderExtraChoiceFields })] })) : (_jsx("div", { className: "text-muted", children: _jsx("em", { children: emptyStateText }) })) }));
|
|
63
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { TreeSpecIssue } from '@signalsafe/tree-spec';
|
|
2
|
+
import { type EditorTree } from '@signalsafe/tree-spec-editor-core';
|
|
3
|
+
export interface IssuesPanelProps {
|
|
4
|
+
issues: TreeSpecIssue[];
|
|
5
|
+
lastValidatedAt: string | null;
|
|
6
|
+
onSelectIssue: (issue: TreeSpecIssue) => void;
|
|
7
|
+
/**
|
|
8
|
+
* Search text for filtering the list (same pattern as {@link NodesPanelProps.nodeSearch}).
|
|
9
|
+
*/
|
|
10
|
+
issueSearch: string;
|
|
11
|
+
/** Called when the user edits the issues search field. */
|
|
12
|
+
onIssueSearchChange: (value: string) => void;
|
|
13
|
+
/**
|
|
14
|
+
* When set, each issue row header shows **node id**, **type** (from `tree.nodes`), and a
|
|
15
|
+
* severity **badge** on the right (same header rhythm as **NodesPanel**). Omit when you do
|
|
16
|
+
* not have a tree in scope; type falls back to **—**.
|
|
17
|
+
*/
|
|
18
|
+
tree?: EditorTree;
|
|
19
|
+
/** Optional override for the card header title. Defaults to "Issues". */
|
|
20
|
+
title?: string;
|
|
21
|
+
/** Optional placeholder for the search field. Defaults to "Search issues…". */
|
|
22
|
+
searchPlaceholder?: string;
|
|
23
|
+
/** Optional formatter for the validated-at timestamp. Defaults to `new Date(iso).toLocaleString()`. */
|
|
24
|
+
formatTimestamp?: (iso: string) => string;
|
|
25
|
+
/**
|
|
26
|
+
* When `false`, the "Validated …" / "Not validated" summary is not shown in
|
|
27
|
+
* this card header (e.g. when the host shows it in an info panel instead).
|
|
28
|
+
* Default: `true`.
|
|
29
|
+
*/
|
|
30
|
+
showValidationSummaryInHeader?: boolean;
|
|
31
|
+
}
|
|
32
|
+
export default function IssuesPanel({ issues, lastValidatedAt, onSelectIssue, issueSearch, onIssueSearchChange, tree, title, searchPlaceholder, formatTimestamp, showValidationSummaryInHeader, }: Readonly<IssuesPanelProps>): import("react/jsx-runtime").JSX.Element;
|
|
33
|
+
//# sourceMappingURL=IssuesPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"IssuesPanel.d.ts","sourceRoot":"","sources":["../../src/panels/IssuesPanel.tsx"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAE3D,OAAO,EAAsB,KAAK,UAAU,EAAE,MAAM,mCAAmC,CAAC;AAKxF,MAAM,WAAW,gBAAgB;IAC7B,MAAM,EAAE,aAAa,EAAE,CAAC;IACxB,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,aAAa,EAAE,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;IAC9C;;OAEG;IACH,WAAW,EAAE,MAAM,CAAC;IACpB,0DAA0D;IAC1D,mBAAmB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC7C;;;;OAIG;IACH,IAAI,CAAC,EAAE,UAAU,CAAC;IAClB,yEAAyE;IACzE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,uGAAuG;IACvG,eAAe,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC;IAC1C;;;;OAIG;IACH,6BAA6B,CAAC,EAAE,OAAO,CAAC;CAC3C;AA4BD,MAAM,CAAC,OAAO,UAAU,WAAW,CAAC,EAChC,MAAM,EACN,eAAe,EACf,aAAa,EACb,WAAW,EACX,mBAAmB,EACnB,IAAI,EACJ,KAAgB,EAChB,iBAAyC,EACzC,eAAwC,EACxC,6BAAoC,GACvC,EAAE,QAAQ,CAAC,gBAAgB,CAAC,2CA0G5B"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useMemo, useState } from 'react';
|
|
3
|
+
import { Form } from 'react-bootstrap';
|
|
4
|
+
import { buildStableEntries } from '@signalsafe/tree-spec-editor-core';
|
|
5
|
+
import { getIssueSeverityBadgeClass } from '../lib/panelHelpers';
|
|
6
|
+
import PanelHeaderCollapseCarets from '../lib/PanelHeaderCollapseCarets';
|
|
7
|
+
function defaultFormatTimestamp(iso) {
|
|
8
|
+
return new Date(iso).toLocaleString();
|
|
9
|
+
}
|
|
10
|
+
function nodeTypeLabel(tree, nodeId) {
|
|
11
|
+
if (!nodeId || !tree?.nodes[nodeId])
|
|
12
|
+
return '—';
|
|
13
|
+
return tree.nodes[nodeId]?.type || 'unknown';
|
|
14
|
+
}
|
|
15
|
+
function issueHeaderTitle(issue, typeLabel) {
|
|
16
|
+
const idPart = issue.node_id ?? '—';
|
|
17
|
+
return `${idPart}, ${typeLabel}`;
|
|
18
|
+
}
|
|
19
|
+
function issueMatchesQuery(issue, qLower, tree) {
|
|
20
|
+
if (!qLower)
|
|
21
|
+
return true;
|
|
22
|
+
const parts = [
|
|
23
|
+
issue.severity,
|
|
24
|
+
issue.message,
|
|
25
|
+
issue.node_id ?? '',
|
|
26
|
+
issue.choice_id ?? '',
|
|
27
|
+
nodeTypeLabel(tree, issue.node_id),
|
|
28
|
+
];
|
|
29
|
+
return parts.some((p) => p.toLowerCase().includes(qLower));
|
|
30
|
+
}
|
|
31
|
+
export default function IssuesPanel({ issues, lastValidatedAt, onSelectIssue, issueSearch, onIssueSearchChange, tree, title = 'Issues', searchPlaceholder = 'Search issues\u2026', formatTimestamp = defaultFormatTimestamp, showValidationSummaryInHeader = true, }) {
|
|
32
|
+
const [expanded, setExpanded] = useState(true);
|
|
33
|
+
const qLower = issueSearch.trim().toLowerCase();
|
|
34
|
+
const filteredIssues = useMemo(() => issues.filter((issue) => issueMatchesQuery(issue, qLower, tree)), [issues, qLower, tree]);
|
|
35
|
+
const issueEntries = buildStableEntries(filteredIssues, (issue) => `${issue.severity}-${issue.node_id ?? ''}-${issue.message}`);
|
|
36
|
+
return (_jsxs("div", { className: "card", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: title }), _jsx(PanelHeaderCollapseCarets, { expanded: expanded, onToggle: () => setExpanded((v) => !v) })] }), showValidationSummaryInHeader ? (_jsx("span", { className: "text-muted font-size-12 text-end flex-shrink-0", children: lastValidatedAt ? `Validated ${formatTimestamp(lastValidatedAt)}` : 'Not validated' })) : null] }), _jsx("div", { className: `card-body p-0${expanded ? '' : ' d-none'}`, "aria-hidden": !expanded, children: issues.length === 0 ? (_jsx("div", { className: "list-group list-group-flush", children: _jsx("div", { className: "list-group-item text-muted small py-2 px-3 border-0", children: _jsx("em", { children: "No issues" }) }) })) : (_jsxs(_Fragment, { children: [_jsx(Form.Control, { className: "rounded-0 border-0 border-bottom shadow-none", placeholder: searchPlaceholder, value: issueSearch, onChange: (e) => onIssueSearchChange(e.target.value) }), _jsx("div", { className: "overflow-auto-max-h-320", children: issueEntries.length === 0 ? (_jsx("div", { className: "list-group list-group-flush", children: _jsx("div", { className: "list-group-item text-muted small py-2 px-3 border-0", children: _jsx("em", { children: "No matching issues." }) }) })) : (_jsx("div", { className: "list-group list-group-flush", children: issueEntries.map(({ item: issue, key }) => {
|
|
37
|
+
const typeLabel = nodeTypeLabel(tree, issue.node_id);
|
|
38
|
+
const headerTitle = issueHeaderTitle(issue, typeLabel);
|
|
39
|
+
return (_jsxs("div", { role: "button", tabIndex: 0, className: "list-group-item list-group-item-action px-3 py-2 text-start", onClick: () => onSelectIssue(issue), onKeyDown: (e) => {
|
|
40
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
41
|
+
e.preventDefault();
|
|
42
|
+
onSelectIssue(issue);
|
|
43
|
+
}
|
|
44
|
+
}, children: [_jsxs("div", { className: "d-flex w-100 justify-content-between align-items-center gap-2 mb-1", children: [_jsxs("div", { className: "min-w-0 flex-grow-1 text-truncate text-start", title: headerTitle, children: [_jsx("span", { className: "fs-6 fw-semibold text-uppercase", children: issue.node_id ?? '—' }), _jsx("span", { className: "fs-6 text-uppercase text-body-secondary", children: `, ${typeLabel}` })] }), _jsx("span", { className: `badge flex-shrink-0 text-uppercase ${getIssueSeverityBadgeClass(issue.severity)}`, children: issue.severity })] }), _jsx("p", { className: "mb-1 small text-truncate font-monospace", style: { minWidth: 0 }, children: issue.message }), issue.choice_id ? (_jsx("small", { className: "text-body-secondary", children: issue.choice_id })) : null] }, key));
|
|
45
|
+
}) })) })] })) })] }));
|
|
46
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { type EditorTree, type GraphSelection } from '@signalsafe/tree-spec-editor-core';
|
|
2
|
+
export interface NodesPanelProps {
|
|
3
|
+
tree: EditorTree;
|
|
4
|
+
nodeSearch: string;
|
|
5
|
+
selection: GraphSelection;
|
|
6
|
+
showMiniMap: boolean;
|
|
7
|
+
onNodeSearchChange: (value: string) => void;
|
|
8
|
+
onNodeSelect: (nodeId: string) => void;
|
|
9
|
+
onShowMiniMapChange: (checked: boolean) => void;
|
|
10
|
+
/** Optional placeholder text for the search input. */
|
|
11
|
+
searchPlaceholder?: string;
|
|
12
|
+
/** Optional tip text shown below the node list. */
|
|
13
|
+
tipText?: string;
|
|
14
|
+
/**
|
|
15
|
+
* When set, shows an add-node control in the card header (grid-style icon button).
|
|
16
|
+
* Typical host: `() => { void editor.actions.addNodeOfType('prompt'); }`.
|
|
17
|
+
*/
|
|
18
|
+
onAddNode?: () => void;
|
|
19
|
+
/**
|
|
20
|
+
* When set, each node row shows a delete icon; invoke to remove that node id (e.g.
|
|
21
|
+
* `editor.actions.deleteNodeById`). Disabled when `isPublished` is true.
|
|
22
|
+
*/
|
|
23
|
+
onDeleteNode?: (nodeId: string) => void;
|
|
24
|
+
/** When true, node delete controls are disabled (read-only published revision). */
|
|
25
|
+
isPublished?: boolean;
|
|
26
|
+
}
|
|
27
|
+
export default function NodesPanel({ tree, nodeSearch, selection, showMiniMap, onNodeSearchChange, onNodeSelect, onShowMiniMapChange, searchPlaceholder, tipText, onAddNode, onDeleteNode, isPublished, }: Readonly<NodesPanelProps>): import("react/jsx-runtime").JSX.Element;
|
|
28
|
+
//# sourceMappingURL=NodesPanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"NodesPanel.d.ts","sourceRoot":"","sources":["../../src/panels/NodesPanel.tsx"],"names":[],"mappings":"AAIA,OAAO,EAIH,KAAK,UAAU,EACf,KAAK,cAAc,EACtB,MAAM,mCAAmC,CAAC;AAI3C,MAAM,WAAW,eAAe;IAC5B,IAAI,EAAE,UAAU,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,cAAc,CAAC;IAC1B,WAAW,EAAE,OAAO,CAAC;IACrB,kBAAkB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC5C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,mBAAmB,EAAE,CAAC,OAAO,EAAE,OAAO,KAAK,IAAI,CAAC;IAChD,sDAAsD;IACtD,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB;;;OAGG;IACH,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;IACvB;;;OAGG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACxC,mFAAmF;IACnF,WAAW,CAAC,EAAE,OAAO,CAAC;CACzB;AA0BD,MAAM,CAAC,OAAO,UAAU,UAAU,CAAC,EAC/B,IAAI,EACJ,UAAU,EACV,SAAS,EACT,WAAW,EACX,kBAAkB,EAClB,YAAY,EACZ,mBAAmB,EACnB,iBAAwC,EACxC,OAA0B,EAC1B,SAAS,EACT,YAAY,EACZ,WAAmB,GACtB,EAAE,QAAQ,CAAC,eAAe,CAAC,2CAyI3B"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Form } from 'react-bootstrap';
|
|
4
|
+
import { GRAPH_SELECTION_KIND, TREE_SPEC_NODE_TYPE_PRESETS, } from '@signalsafe/tree-spec-editor-core';
|
|
5
|
+
import PanelHeaderCollapseCarets from '../lib/PanelHeaderCollapseCarets';
|
|
6
|
+
const DEFAULT_TIP_TEXT = 'Tip: connect choices by dragging the blue dots. Shortcuts: \u2318S save, \u2318\u21E7V validate, \u2318D duplicate, Del delete.';
|
|
7
|
+
const TYPE_SORT_ORDER = new Map(TREE_SPEC_NODE_TYPE_PRESETS.map((t, i) => [t, i]));
|
|
8
|
+
function choiceLine(n) {
|
|
9
|
+
const count = n.choices?.length ?? 0;
|
|
10
|
+
if (count === 0)
|
|
11
|
+
return 'No choices';
|
|
12
|
+
if (count === 1)
|
|
13
|
+
return '1 choice';
|
|
14
|
+
return `${count} choices`;
|
|
15
|
+
}
|
|
16
|
+
function compareNodesByTypeThenId(a, b) {
|
|
17
|
+
const ta = a.type || 'unknown';
|
|
18
|
+
const tb = b.type || 'unknown';
|
|
19
|
+
const oa = TYPE_SORT_ORDER.has(ta) ? TYPE_SORT_ORDER.get(ta) : 1000;
|
|
20
|
+
const ob = TYPE_SORT_ORDER.has(tb) ? TYPE_SORT_ORDER.get(tb) : 1000;
|
|
21
|
+
if (oa !== ob)
|
|
22
|
+
return oa - ob;
|
|
23
|
+
if (ta !== tb)
|
|
24
|
+
return ta.localeCompare(tb);
|
|
25
|
+
return a.id.localeCompare(b.id);
|
|
26
|
+
}
|
|
27
|
+
export default function NodesPanel({ tree, nodeSearch, selection, showMiniMap, onNodeSearchChange, onNodeSelect, onShowMiniMapChange, searchPlaceholder = 'Search nodes\u2026', tipText = DEFAULT_TIP_TEXT, onAddNode, onDeleteNode, isPublished = false, }) {
|
|
28
|
+
const [expanded, setExpanded] = useState(true);
|
|
29
|
+
const filteredNodes = Object.values(tree.nodes)
|
|
30
|
+
.filter((n) => {
|
|
31
|
+
const q = nodeSearch.trim().toLowerCase();
|
|
32
|
+
if (!q)
|
|
33
|
+
return true;
|
|
34
|
+
const hitPrompt = (n.prompt || '').toLowerCase().includes(q);
|
|
35
|
+
const hitId = n.id.toLowerCase().includes(q);
|
|
36
|
+
const hitType = (n.type || '').toLowerCase().includes(q);
|
|
37
|
+
const hitChoices = (n.choices ?? []).some((c) => (c.label || '').toLowerCase().includes(q) || c.id.toLowerCase().includes(q));
|
|
38
|
+
return hitPrompt || hitId || hitType || hitChoices;
|
|
39
|
+
})
|
|
40
|
+
.sort(compareNodesByTypeThenId);
|
|
41
|
+
return (_jsxs("div", { className: "card mt-3", children: [_jsxs("div", { className: "card-header bg-body-secondary py-2 px-2 d-flex justify-content-between align-items-center gap-2", children: [_jsxs("div", { className: "d-flex align-items-center min-w-0", children: [_jsx("span", { className: "fw-semibold", children: "Nodes" }), _jsx(PanelHeaderCollapseCarets, { expanded: expanded, onToggle: () => setExpanded((v) => !v) })] }), onAddNode ? (_jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": "Add node", title: "Add node", onClick: onAddNode, children: _jsx("i", { className: "bi bi-plus-lg text-primary action-icon cursor-pointer", "aria-hidden": true }) })) : null] }), _jsxs("div", { className: expanded ? undefined : 'd-none', "aria-hidden": !expanded, children: [_jsx(Form.Control, { className: "rounded-0 border-0 border-bottom shadow-none", placeholder: searchPlaceholder, value: nodeSearch, onChange: (e) => onNodeSearchChange(e.target.value) }), _jsx("div", { className: "overflow-auto-max-h-320", children: filteredNodes.length === 0 ? (_jsx("div", { className: "list-group list-group-flush", children: _jsx("div", { className: "list-group-item text-muted small py-2 px-3", children: "No matching nodes." }) })) : (_jsx("div", { className: "list-group list-group-flush", children: filteredNodes.map((n) => {
|
|
42
|
+
const isSelected = selection.kind === GRAPH_SELECTION_KIND.NODE && selection.id === n.id;
|
|
43
|
+
const typeLabel = n.type || 'unknown';
|
|
44
|
+
const mutedTop = isSelected ? '' : ' text-body-secondary';
|
|
45
|
+
const mutedMeta = isSelected ? 'opacity-75' : 'text-body-secondary';
|
|
46
|
+
return (_jsxs("div", { role: "button", tabIndex: 0, className: `list-group-item list-group-item-action text-start px-3 py-2${isSelected ? ' active' : ''}`, "aria-current": isSelected ? 'true' : undefined, onClick: () => onNodeSelect(n.id), onKeyDown: (e) => {
|
|
47
|
+
if (e.key === 'Enter' || e.key === ' ') {
|
|
48
|
+
e.preventDefault();
|
|
49
|
+
onNodeSelect(n.id);
|
|
50
|
+
}
|
|
51
|
+
}, children: [_jsxs("div", { className: "d-flex w-100 justify-content-between align-items-center gap-2 mb-1", children: [_jsxs("div", { className: "min-w-0 flex-grow-1 text-truncate text-start", title: `${n.id}, ${typeLabel}`, children: [_jsx("span", { className: `fs-6 fw-semibold text-uppercase${mutedTop}`, children: n.id }), _jsx("span", { className: `fs-6 text-uppercase ${isSelected ? 'opacity-75' : 'text-body-secondary'}`, children: `, ${typeLabel}` })] }), onDeleteNode ? (_jsx("button", { type: "button", className: "btn p-0 border-0 bg-transparent flex-shrink-0", "aria-label": `Delete node ${n.id}`, title: "Delete node", disabled: isPublished, onClick: (e) => {
|
|
52
|
+
e.stopPropagation();
|
|
53
|
+
onDeleteNode(n.id);
|
|
54
|
+
}, children: _jsx("i", { className: `bi bi-trash action-icon cursor-pointer${isPublished ? ' text-secondary opacity-50' : ' text-danger'}`, "aria-hidden": true }) })) : null] }), _jsx("p", { className: "mb-1 small text-truncate", style: { minWidth: 0 }, children: n.prompt || '(empty)' }), _jsx("small", { className: mutedMeta, children: choiceLine(n) })] }, n.id));
|
|
55
|
+
}) })) }), _jsx("div", { className: "card-footer p-2 border-top", children: _jsxs("div", { className: "d-flex justify-content-between align-items-center", children: [_jsx("div", { className: "text-muted font-size-12", children: tipText }), _jsx(Form.Check, { type: "switch", id: "showMiniMapToggle", label: "Mini-map", checked: showMiniMap, onChange: (e) => onShowMiniMapChange(e.target.checked), className: "font-size-12" })] }) })] })] }));
|
|
56
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Compact read-only display of the currently selected transition.
|
|
3
|
+
* Returns `null` when no edge is selected, so the host can render it unconditionally.
|
|
4
|
+
*/
|
|
5
|
+
import type { ReactNode } from 'react';
|
|
6
|
+
import type { EditorTransition } from '@signalsafe/tree-spec-editor-core';
|
|
7
|
+
export interface SelectedEdgePanelProps {
|
|
8
|
+
selectedEdge: EditorTransition | null;
|
|
9
|
+
/** Override the card title (default "Selected Edge"). */
|
|
10
|
+
title?: string;
|
|
11
|
+
/**
|
|
12
|
+
* Optional custom renderer for the edge details line. Defaults to
|
|
13
|
+
* `from \u00b7 choice \u2192 to`.
|
|
14
|
+
*/
|
|
15
|
+
renderDetails?: (edge: EditorTransition) => ReactNode;
|
|
16
|
+
}
|
|
17
|
+
export default function SelectedEdgePanel({ selectedEdge, title, renderDetails, }: Readonly<SelectedEdgePanelProps>): import("react/jsx-runtime").JSX.Element | null;
|
|
18
|
+
//# sourceMappingURL=SelectedEdgePanel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SelectedEdgePanel.d.ts","sourceRoot":"","sources":["../../src/panels/SelectedEdgePanel.tsx"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,MAAM,WAAW,sBAAsB;IACnC,YAAY,EAAE,gBAAgB,GAAG,IAAI,CAAC;IACtC,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,IAAI,EAAE,gBAAgB,KAAK,SAAS,CAAC;CACzD;AAUD,MAAM,CAAC,OAAO,UAAU,iBAAiB,CAAC,EACtC,YAAY,EACZ,KAAuB,EACvB,aAAoC,GACvC,EAAE,QAAQ,CAAC,sBAAsB,CAAC,kDAWlC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { Fragment as _Fragment, jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
function defaultRenderDetails(edge) {
|
|
3
|
+
return (_jsxs(_Fragment, { children: [edge.fromNodeId, " \u00B7 ", edge.fromChoiceId, " \u2192 ", edge.toNodeId] }));
|
|
4
|
+
}
|
|
5
|
+
export default function SelectedEdgePanel({ selectedEdge, title = 'Selected Edge', renderDetails = defaultRenderDetails, }) {
|
|
6
|
+
if (!selectedEdge)
|
|
7
|
+
return null;
|
|
8
|
+
return (_jsx("div", { className: "card mt-3", children: _jsxs("div", { className: "card-body", children: [_jsx("div", { className: "fw-bold", children: title }), _jsx("div", { className: "text-muted font-size-12", children: renderDetails(selectedEdge) })] }) }));
|
|
9
|
+
}
|