@arcote.tech/arc-process 0.7.28 → 0.7.29
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/package.json +5 -4
- package/src/layout/layout.ts +60 -9
- package/src/mdx/arc.tsx +50 -10
- package/src/mdx/contexts.tsx +11 -0
- package/src/mdx/mdx-view.tsx +4 -0
- package/src/mdx/pin.tsx +101 -0
- package/src/model/build.ts +73 -22
- package/src/model/collapse.ts +2 -0
- package/src/model/profile.ts +15 -0
- package/src/model/types.ts +37 -0
- package/src/profiles/arc.ts +29 -1
- package/src/react/drawer.tsx +374 -0
- package/src/react/index.ts +5 -0
- package/src/react/nodes.tsx +65 -10
- package/src/react/process-diagram.tsx +47 -4
- package/tests/build.test.ts +98 -4
- package/tests/layout.test.ts +34 -0
package/src/profiles/arc.ts
CHANGED
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { defineProfile } from "../model/profile";
|
|
14
14
|
import type {
|
|
15
|
+
CatalogEntry,
|
|
15
16
|
ContextInfo,
|
|
16
17
|
MethodRef,
|
|
17
18
|
ProcessGraph,
|
|
@@ -32,6 +33,7 @@ export const ARC_ELEMENT_COLOR: Record<string, string> = {
|
|
|
32
33
|
listener: "#7048e8", // grape/lilac — policy / reaction
|
|
33
34
|
route: "#e64980", // pink — external boundary
|
|
34
35
|
page: "#4263eb", // indigo — UI / page
|
|
36
|
+
tool: "#0891b2", // cyan — AI tool (wołany przez LLM)
|
|
35
37
|
context: "#7c3aed", // violet — zwinięty kontekst
|
|
36
38
|
note: "#eab308", // yellow — notatka
|
|
37
39
|
unknown: "#868e96", // slate
|
|
@@ -59,6 +61,8 @@ export interface ArcRefProps {
|
|
|
59
61
|
staticView?: string;
|
|
60
62
|
listener?: string;
|
|
61
63
|
route?: string;
|
|
64
|
+
/** AI tool (element tool() z arc-ai, np. tool czatu). */
|
|
65
|
+
tool?: string;
|
|
62
66
|
aggregate?: string;
|
|
63
67
|
/** Z `aggregate`: metoda mutująca. */
|
|
64
68
|
mutationMethod?: string;
|
|
@@ -107,6 +111,9 @@ export function resolveArcRef(props: ArcRefProps): ResolvedRef | null {
|
|
|
107
111
|
if (props.route) {
|
|
108
112
|
return { nodeId: props.route, kind: "route", key: `route:${props.route}` };
|
|
109
113
|
}
|
|
114
|
+
if (props.tool) {
|
|
115
|
+
return { nodeId: props.tool, kind: "tool", key: `tool:${props.tool}` };
|
|
116
|
+
}
|
|
110
117
|
if (props.page) {
|
|
111
118
|
const path = normalizePagePath(props.page);
|
|
112
119
|
return { nodeId: path, kind: "page", key: `page:${path}` };
|
|
@@ -192,6 +199,7 @@ export interface ArcGraphInput {
|
|
|
192
199
|
components?: ArcGraphComponent[];
|
|
193
200
|
componentEdges?: ArcGraphComponentEdge[];
|
|
194
201
|
contexts?: ContextInfo[];
|
|
202
|
+
catalog?: CatalogEntry[];
|
|
195
203
|
}
|
|
196
204
|
|
|
197
205
|
/** Przekształca dane mapy Arka na generyczny graf procesu. */
|
|
@@ -222,7 +230,12 @@ export function toProcessGraph(input: ArcGraphInput): ProcessGraph {
|
|
|
222
230
|
kind: e.usage === "query" ? "uiQuery" : "uiMutation",
|
|
223
231
|
})),
|
|
224
232
|
];
|
|
225
|
-
return {
|
|
233
|
+
return {
|
|
234
|
+
nodes,
|
|
235
|
+
relations,
|
|
236
|
+
contexts: input.contexts ?? [],
|
|
237
|
+
catalog: input.catalog ?? [],
|
|
238
|
+
};
|
|
226
239
|
}
|
|
227
240
|
|
|
228
241
|
// ---------------------------------------------------------------------------
|
|
@@ -259,6 +272,7 @@ export const arcProfile = defineProfile({
|
|
|
259
272
|
listener: { color: ARC_ELEMENT_COLOR.listener, label: "listener" },
|
|
260
273
|
route: { color: ARC_ELEMENT_COLOR.route, label: "route" },
|
|
261
274
|
page: { color: ARC_ELEMENT_COLOR.page, label: "page" },
|
|
275
|
+
tool: { color: ARC_ELEMENT_COLOR.tool, label: "tool" },
|
|
262
276
|
context: { color: ARC_ELEMENT_COLOR.context, label: "context" },
|
|
263
277
|
note: { color: ARC_ELEMENT_COLOR.note, label: "note" },
|
|
264
278
|
unknown: { color: ARC_ELEMENT_COLOR.unknown },
|
|
@@ -327,6 +341,20 @@ export const arcProfile = defineProfile({
|
|
|
327
341
|
label: "używany w UI",
|
|
328
342
|
},
|
|
329
343
|
},
|
|
344
|
+
pinKinds: {
|
|
345
|
+
persona: {
|
|
346
|
+
icon: "👤",
|
|
347
|
+
color: "#0e7490",
|
|
348
|
+
label: "persona",
|
|
349
|
+
catalogKind: "personas",
|
|
350
|
+
},
|
|
351
|
+
metric: {
|
|
352
|
+
icon: "📈",
|
|
353
|
+
color: "#9333ea",
|
|
354
|
+
label: "metryka",
|
|
355
|
+
catalogKind: "metrics",
|
|
356
|
+
},
|
|
357
|
+
},
|
|
330
358
|
resolveRef: (props) => resolveArcRef(props as ArcRefProps),
|
|
331
359
|
forwardDeps: arcForwardDeps,
|
|
332
360
|
});
|
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drawer szczegółów kroku — overlay z prawej NAD diagramem.
|
|
3
|
+
*
|
|
4
|
+
* Otwierany klikiem w nod lub chip prozy; zamykany Esc/×/klik w tło.
|
|
5
|
+
* Sekcje: nagłówek (rodzaj, status istnienia), Przypięcia (dane z katalogu),
|
|
6
|
+
* Wystąpienie (etykieta, faza, tor, notatki), Element (opis/schema/metody/
|
|
7
|
+
* relacje z grafu) i OTWARTY kod źródłowy (przez `sourceProvider`,
|
|
8
|
+
* kolorowany Shiki ładowanym leniwie — pierwsze otwarcie kodu, nie start).
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
useEffect,
|
|
13
|
+
useMemo,
|
|
14
|
+
useState,
|
|
15
|
+
type CSSProperties,
|
|
16
|
+
type ReactNode,
|
|
17
|
+
} from "react";
|
|
18
|
+
import { kindColor, type ProcessProfile } from "../model/profile";
|
|
19
|
+
import type { ProcessNode, ProcessStep } from "../model/types";
|
|
20
|
+
import { GLASS, tint } from "./theme";
|
|
21
|
+
|
|
22
|
+
/** Dostawca kodu źródłowego dla noda (np. fetch /api/source w arc-map). */
|
|
23
|
+
export type SourceProvider = (
|
|
24
|
+
node: ProcessNode,
|
|
25
|
+
) => Promise<{ file: string; line?: number; code: string } | null>;
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Shiki — leniwy singleton (moduł ładowany dopiero przy pierwszym kodzie)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
let shikiPromise: Promise<
|
|
32
|
+
((code: string) => string) | null
|
|
33
|
+
> | null = null;
|
|
34
|
+
|
|
35
|
+
function getHighlighter() {
|
|
36
|
+
if (!shikiPromise) {
|
|
37
|
+
shikiPromise = import("shiki")
|
|
38
|
+
.then(async (shiki) => {
|
|
39
|
+
const highlighter = await shiki.createHighlighter({
|
|
40
|
+
themes: ["github-light"],
|
|
41
|
+
langs: ["typescript", "tsx"],
|
|
42
|
+
});
|
|
43
|
+
return (code: string) =>
|
|
44
|
+
highlighter.codeToHtml(code, { lang: "tsx", theme: "github-light" });
|
|
45
|
+
})
|
|
46
|
+
.catch(() => null);
|
|
47
|
+
}
|
|
48
|
+
return shikiPromise;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function CodeBlock({ code }: { code: string }) {
|
|
52
|
+
const [html, setHtml] = useState<string | null>(null);
|
|
53
|
+
useEffect(() => {
|
|
54
|
+
let cancelled = false;
|
|
55
|
+
getHighlighter().then((highlight) => {
|
|
56
|
+
if (!cancelled && highlight) setHtml(highlight(code));
|
|
57
|
+
});
|
|
58
|
+
return () => {
|
|
59
|
+
cancelled = true;
|
|
60
|
+
};
|
|
61
|
+
}, [code]);
|
|
62
|
+
|
|
63
|
+
const base: CSSProperties = {
|
|
64
|
+
margin: 0,
|
|
65
|
+
padding: "10px 12px",
|
|
66
|
+
borderRadius: 10,
|
|
67
|
+
border: `1px solid ${GLASS.border}`,
|
|
68
|
+
background: "rgba(255,255,255,0.75)",
|
|
69
|
+
fontSize: 11,
|
|
70
|
+
lineHeight: 1.55,
|
|
71
|
+
overflow: "auto",
|
|
72
|
+
maxHeight: 420,
|
|
73
|
+
};
|
|
74
|
+
if (html) {
|
|
75
|
+
return (
|
|
76
|
+
<div
|
|
77
|
+
style={base}
|
|
78
|
+
// Shiki generuje zaufany HTML z naszego lokalnego kodu źródłowego.
|
|
79
|
+
dangerouslySetInnerHTML={{ __html: html }}
|
|
80
|
+
/>
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
return (
|
|
84
|
+
<pre style={{ ...base, fontFamily: "ui-monospace, monospace" }}>{code}</pre>
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ---------------------------------------------------------------------------
|
|
89
|
+
// Sekcje (wzorzec detail-panel z arc-map)
|
|
90
|
+
// ---------------------------------------------------------------------------
|
|
91
|
+
|
|
92
|
+
function Section({ title, children }: { title: string; children: ReactNode }) {
|
|
93
|
+
return (
|
|
94
|
+
<div style={{ marginBottom: 14 }}>
|
|
95
|
+
<div
|
|
96
|
+
style={{
|
|
97
|
+
fontSize: 10.5,
|
|
98
|
+
textTransform: "uppercase",
|
|
99
|
+
letterSpacing: 0.6,
|
|
100
|
+
fontWeight: 700,
|
|
101
|
+
color: GLASS.textFaint,
|
|
102
|
+
marginBottom: 5,
|
|
103
|
+
}}
|
|
104
|
+
>
|
|
105
|
+
{title}
|
|
106
|
+
</div>
|
|
107
|
+
{children}
|
|
108
|
+
</div>
|
|
109
|
+
);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
function Json({ value }: { value: unknown }) {
|
|
113
|
+
if (value === undefined || value === null) {
|
|
114
|
+
return <em style={{ color: GLASS.textFaint }}>—</em>;
|
|
115
|
+
}
|
|
116
|
+
return (
|
|
117
|
+
<pre
|
|
118
|
+
style={{
|
|
119
|
+
margin: 0,
|
|
120
|
+
padding: "8px 10px",
|
|
121
|
+
borderRadius: 8,
|
|
122
|
+
border: `1px solid ${GLASS.border}`,
|
|
123
|
+
background: "rgba(255,255,255,0.6)",
|
|
124
|
+
fontSize: 10.5,
|
|
125
|
+
maxHeight: 220,
|
|
126
|
+
overflow: "auto",
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
{JSON.stringify(value, null, 2)}
|
|
130
|
+
</pre>
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// ---------------------------------------------------------------------------
|
|
135
|
+
// Drawer
|
|
136
|
+
// ---------------------------------------------------------------------------
|
|
137
|
+
|
|
138
|
+
export interface ProcessDrawerProps {
|
|
139
|
+
step: ProcessStep;
|
|
140
|
+
profile: ProcessProfile;
|
|
141
|
+
onClose: () => void;
|
|
142
|
+
sourceProvider?: SourceProvider;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function ProcessDrawer({
|
|
146
|
+
step,
|
|
147
|
+
profile,
|
|
148
|
+
onClose,
|
|
149
|
+
sourceProvider,
|
|
150
|
+
}: ProcessDrawerProps) {
|
|
151
|
+
const [source, setSource] = useState<
|
|
152
|
+
{ file: string; line?: number; code: string } | null | "loading"
|
|
153
|
+
>(sourceProvider && step.node ? "loading" : null);
|
|
154
|
+
|
|
155
|
+
useEffect(() => {
|
|
156
|
+
if (!sourceProvider || !step.node) {
|
|
157
|
+
setSource(null);
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
let cancelled = false;
|
|
161
|
+
setSource("loading");
|
|
162
|
+
sourceProvider(step.node)
|
|
163
|
+
.then((s) => {
|
|
164
|
+
if (!cancelled) setSource(s);
|
|
165
|
+
})
|
|
166
|
+
.catch(() => {
|
|
167
|
+
if (!cancelled) setSource(null);
|
|
168
|
+
});
|
|
169
|
+
return () => {
|
|
170
|
+
cancelled = true;
|
|
171
|
+
};
|
|
172
|
+
}, [sourceProvider, step.node]);
|
|
173
|
+
|
|
174
|
+
useEffect(() => {
|
|
175
|
+
const onKey = (e: KeyboardEvent) => {
|
|
176
|
+
if (e.key === "Escape") onClose();
|
|
177
|
+
};
|
|
178
|
+
window.addEventListener("keydown", onKey);
|
|
179
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
180
|
+
}, [onClose]);
|
|
181
|
+
|
|
182
|
+
const kind = step.node?.kind ?? step.ref?.kind ?? "unknown";
|
|
183
|
+
const color = kindColor(profile, kind);
|
|
184
|
+
const meta = (step.node?.meta ?? {}) as Record<string, unknown>;
|
|
185
|
+
|
|
186
|
+
const metaSections = useMemo(
|
|
187
|
+
() =>
|
|
188
|
+
(
|
|
189
|
+
[
|
|
190
|
+
["Opis", meta.description],
|
|
191
|
+
["Schema", meta.schema],
|
|
192
|
+
["Parametry", meta.params],
|
|
193
|
+
["Payload", meta.payload],
|
|
194
|
+
["Metody", meta.methods],
|
|
195
|
+
["Protekcje", meta.protections],
|
|
196
|
+
] as [string, unknown][]
|
|
197
|
+
).filter(([, v]) => v !== undefined && v !== null),
|
|
198
|
+
[meta],
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
return (
|
|
202
|
+
<>
|
|
203
|
+
{/* Tło — klik zamyka. */}
|
|
204
|
+
<div
|
|
205
|
+
onClick={onClose}
|
|
206
|
+
style={{ position: "absolute", inset: 0, zIndex: 20 }}
|
|
207
|
+
/>
|
|
208
|
+
<div
|
|
209
|
+
style={{
|
|
210
|
+
position: "absolute",
|
|
211
|
+
top: 12,
|
|
212
|
+
right: 12,
|
|
213
|
+
bottom: 12,
|
|
214
|
+
width: 480,
|
|
215
|
+
maxWidth: "85%",
|
|
216
|
+
zIndex: 21,
|
|
217
|
+
borderRadius: 16,
|
|
218
|
+
border: `1px solid ${GLASS.border}`,
|
|
219
|
+
background: GLASS.cardStrong,
|
|
220
|
+
backdropFilter: GLASS.blur,
|
|
221
|
+
boxShadow: GLASS.shadowStrong,
|
|
222
|
+
display: "flex",
|
|
223
|
+
flexDirection: "column",
|
|
224
|
+
overflow: "hidden",
|
|
225
|
+
}}
|
|
226
|
+
>
|
|
227
|
+
{/* Nagłówek */}
|
|
228
|
+
<div
|
|
229
|
+
style={{
|
|
230
|
+
padding: "12px 16px",
|
|
231
|
+
borderBottom: `1px solid ${GLASS.border}`,
|
|
232
|
+
display: "flex",
|
|
233
|
+
alignItems: "center",
|
|
234
|
+
gap: 8,
|
|
235
|
+
}}
|
|
236
|
+
>
|
|
237
|
+
<span
|
|
238
|
+
style={{
|
|
239
|
+
width: 10,
|
|
240
|
+
height: 10,
|
|
241
|
+
borderRadius: 3,
|
|
242
|
+
background: color,
|
|
243
|
+
boxShadow: `0 0 0 3px ${tint(color, 0.18)}`,
|
|
244
|
+
}}
|
|
245
|
+
/>
|
|
246
|
+
<div style={{ minWidth: 0 }}>
|
|
247
|
+
<div
|
|
248
|
+
style={{
|
|
249
|
+
fontWeight: 800,
|
|
250
|
+
fontSize: 14,
|
|
251
|
+
color: GLASS.text,
|
|
252
|
+
overflow: "hidden",
|
|
253
|
+
textOverflow: "ellipsis",
|
|
254
|
+
whiteSpace: "nowrap",
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
{step.node?.label ?? step.ref?.nodeId ?? step.label}
|
|
258
|
+
</div>
|
|
259
|
+
<div style={{ fontSize: 11, color: GLASS.textMuted }}>
|
|
260
|
+
{kind}
|
|
261
|
+
{step.method ? ` · ${step.method.name} (${step.method.kind})` : ""}
|
|
262
|
+
{!step.exists ? (
|
|
263
|
+
<span style={{ color, fontWeight: 700 }}> · niezaimplementowany</span>
|
|
264
|
+
) : null}
|
|
265
|
+
</div>
|
|
266
|
+
</div>
|
|
267
|
+
<button
|
|
268
|
+
onClick={onClose}
|
|
269
|
+
style={{
|
|
270
|
+
marginLeft: "auto",
|
|
271
|
+
border: "none",
|
|
272
|
+
background: "transparent",
|
|
273
|
+
cursor: "pointer",
|
|
274
|
+
fontSize: 16,
|
|
275
|
+
color: GLASS.textMuted,
|
|
276
|
+
}}
|
|
277
|
+
>
|
|
278
|
+
×
|
|
279
|
+
</button>
|
|
280
|
+
</div>
|
|
281
|
+
|
|
282
|
+
{/* Treść */}
|
|
283
|
+
<div style={{ padding: 16, overflow: "auto", flex: 1, minHeight: 0 }}>
|
|
284
|
+
{step.pins.length ? (
|
|
285
|
+
<Section title="Przypięcia">
|
|
286
|
+
{step.pins.map((pin) => {
|
|
287
|
+
const spec = profile.pinKinds?.[pin.kind];
|
|
288
|
+
const pinColor =
|
|
289
|
+
(typeof pin.data?.color === "string"
|
|
290
|
+
? pin.data.color
|
|
291
|
+
: undefined) ?? spec?.color ?? "#64748b";
|
|
292
|
+
return (
|
|
293
|
+
<div
|
|
294
|
+
key={`${pin.kind}:${pin.name}`}
|
|
295
|
+
style={{
|
|
296
|
+
display: "flex",
|
|
297
|
+
alignItems: "baseline",
|
|
298
|
+
gap: 6,
|
|
299
|
+
fontSize: 12,
|
|
300
|
+
color: GLASS.text,
|
|
301
|
+
marginBottom: 4,
|
|
302
|
+
}}
|
|
303
|
+
>
|
|
304
|
+
<span style={{ color: pinColor }}>{spec?.icon ?? "◦"}</span>
|
|
305
|
+
<b>{pin.label}</b>
|
|
306
|
+
<span style={{ color: GLASS.textFaint, fontSize: 10.5 }}>
|
|
307
|
+
{spec?.label ?? pin.kind} · {pin.name}
|
|
308
|
+
{pin.resolved ? "" : " · brak w katalogu"}
|
|
309
|
+
</span>
|
|
310
|
+
{typeof pin.data?.description === "string" ? (
|
|
311
|
+
<span style={{ color: GLASS.textMuted, fontSize: 11 }}>
|
|
312
|
+
— {pin.data.description}
|
|
313
|
+
</span>
|
|
314
|
+
) : null}
|
|
315
|
+
</div>
|
|
316
|
+
);
|
|
317
|
+
})}
|
|
318
|
+
</Section>
|
|
319
|
+
) : null}
|
|
320
|
+
|
|
321
|
+
<Section title="Wystąpienie">
|
|
322
|
+
<div style={{ fontSize: 12, color: GLASS.text }}>
|
|
323
|
+
<div>
|
|
324
|
+
#{step.seq} — <b>{step.label || "(bez etykiety)"}</b>
|
|
325
|
+
</div>
|
|
326
|
+
{step.stepTitle ? <div>Faza: {step.stepTitle}</div> : null}
|
|
327
|
+
{step.entryTitle ? <div>Wejście: {step.entryTitle}</div> : null}
|
|
328
|
+
{step.laneTitle ? <div>Ścieżka: {step.laneTitle}</div> : null}
|
|
329
|
+
{step.notes.map((n) => (
|
|
330
|
+
<div
|
|
331
|
+
key={n.occId}
|
|
332
|
+
style={{
|
|
333
|
+
marginTop: 4,
|
|
334
|
+
padding: "4px 8px",
|
|
335
|
+
borderRadius: 6,
|
|
336
|
+
background: "rgba(234,179,8,0.10)",
|
|
337
|
+
borderLeft: "3px solid #eab308",
|
|
338
|
+
fontSize: 11.5,
|
|
339
|
+
color: GLASS.textMuted,
|
|
340
|
+
}}
|
|
341
|
+
>
|
|
342
|
+
{n.title ? <b>{n.title}: </b> : "📝 "}
|
|
343
|
+
{n.text}
|
|
344
|
+
</div>
|
|
345
|
+
))}
|
|
346
|
+
</div>
|
|
347
|
+
</Section>
|
|
348
|
+
|
|
349
|
+
{metaSections.map(([title, value]) => (
|
|
350
|
+
<Section key={title} title={title}>
|
|
351
|
+
{typeof value === "string" ? (
|
|
352
|
+
<div style={{ fontSize: 12, color: GLASS.text }}>{value}</div>
|
|
353
|
+
) : (
|
|
354
|
+
<Json value={value} />
|
|
355
|
+
)}
|
|
356
|
+
</Section>
|
|
357
|
+
))}
|
|
358
|
+
|
|
359
|
+
{source === "loading" ? (
|
|
360
|
+
<Section title="Kod">
|
|
361
|
+
<div style={{ color: GLASS.textMuted, fontSize: 12 }}>Ładuję…</div>
|
|
362
|
+
</Section>
|
|
363
|
+
) : source ? (
|
|
364
|
+
<Section
|
|
365
|
+
title={`Kod — ${source.file}${source.line ? `:${source.line}` : ""}`}
|
|
366
|
+
>
|
|
367
|
+
<CodeBlock code={source.code} />
|
|
368
|
+
</Section>
|
|
369
|
+
) : null}
|
|
370
|
+
</div>
|
|
371
|
+
</div>
|
|
372
|
+
</>
|
|
373
|
+
);
|
|
374
|
+
}
|
package/src/react/index.ts
CHANGED
|
@@ -3,7 +3,9 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
export { ProcessDiagram, type ProcessDiagramProps } from "./process-diagram";
|
|
6
|
+
export { ProcessDrawer, type ProcessDrawerProps, type SourceProvider } from "./drawer";
|
|
6
7
|
export { makeNodeTypes } from "./nodes";
|
|
8
|
+
export { Pin, Person, Metric, type PinProps } from "../mdx/pin";
|
|
7
9
|
export {
|
|
8
10
|
cardFor,
|
|
9
11
|
contextCard,
|
|
@@ -28,15 +30,18 @@ export {
|
|
|
28
30
|
FocusContext,
|
|
29
31
|
ProcessDataContext,
|
|
30
32
|
RegisterContext,
|
|
33
|
+
SelectionContext,
|
|
31
34
|
StructureContext,
|
|
32
35
|
ViewStateContext,
|
|
33
36
|
useFocus,
|
|
34
37
|
useProcessData,
|
|
35
38
|
useRegister,
|
|
39
|
+
useSelection,
|
|
36
40
|
useStructure,
|
|
37
41
|
useViewState,
|
|
38
42
|
type FocusState,
|
|
39
43
|
type ProcessData,
|
|
40
44
|
type RegEntry,
|
|
45
|
+
type SelectionState,
|
|
41
46
|
type ViewStateActions,
|
|
42
47
|
} from "../mdx/contexts";
|
package/src/react/nodes.tsx
CHANGED
|
@@ -7,10 +7,15 @@
|
|
|
7
7
|
|
|
8
8
|
import { Handle, Position, type NodeProps } from "@xyflow/react";
|
|
9
9
|
import { kindColor, type ProcessProfile } from "../model/profile";
|
|
10
|
-
import type {
|
|
10
|
+
import type {
|
|
11
|
+
ContextInfo,
|
|
12
|
+
ProcessNode,
|
|
13
|
+
ProcessStep,
|
|
14
|
+
ResolvedPin,
|
|
15
|
+
} from "../model/types";
|
|
11
16
|
import { useViewState } from "../mdx/contexts";
|
|
12
17
|
import { cardFor, DefaultContextCard } from "./context-card";
|
|
13
|
-
import { GLASS,
|
|
18
|
+
import { GLASS, tint } from "./theme";
|
|
14
19
|
|
|
15
20
|
const KIND_ICON: Record<string, string> = {
|
|
16
21
|
aggregate: "◆",
|
|
@@ -21,6 +26,7 @@ const KIND_ICON: Record<string, string> = {
|
|
|
21
26
|
listener: "♪",
|
|
22
27
|
route: "⇄",
|
|
23
28
|
page: "▢",
|
|
29
|
+
tool: "⚙",
|
|
24
30
|
context: "▣",
|
|
25
31
|
note: "✎",
|
|
26
32
|
unknown: "•",
|
|
@@ -36,6 +42,50 @@ function hiddenHandles() {
|
|
|
36
42
|
);
|
|
37
43
|
}
|
|
38
44
|
|
|
45
|
+
/** Badge'e przypięć (persony/metryki/…) — kolor z katalogu lub rodzaju pinu. */
|
|
46
|
+
function PinBadges({
|
|
47
|
+
profile,
|
|
48
|
+
pins,
|
|
49
|
+
}: {
|
|
50
|
+
profile: ProcessProfile;
|
|
51
|
+
pins: ResolvedPin[];
|
|
52
|
+
}) {
|
|
53
|
+
if (!pins.length) return null;
|
|
54
|
+
return (
|
|
55
|
+
<div style={{ display: "flex", flexWrap: "wrap", gap: 3, marginTop: 2 }}>
|
|
56
|
+
{pins.map((pin) => {
|
|
57
|
+
const spec = profile.pinKinds?.[pin.kind];
|
|
58
|
+
const color =
|
|
59
|
+
(typeof pin.data?.color === "string" ? pin.data.color : undefined) ??
|
|
60
|
+
spec?.color ??
|
|
61
|
+
"#64748b";
|
|
62
|
+
return (
|
|
63
|
+
<span
|
|
64
|
+
key={`${pin.kind}:${pin.name}`}
|
|
65
|
+
title={`${spec?.label ?? pin.kind}: ${pin.name}${
|
|
66
|
+
pin.resolved ? "" : " (brak w katalogu)"
|
|
67
|
+
}`}
|
|
68
|
+
style={{
|
|
69
|
+
fontSize: 9,
|
|
70
|
+
fontWeight: 700,
|
|
71
|
+
color: GLASS.textMuted,
|
|
72
|
+
background: tint(color, 0.1),
|
|
73
|
+
border: `1px ${pin.resolved ? "solid" : "dashed"} ${tint(color, 0.65)}`,
|
|
74
|
+
borderRadius: 99,
|
|
75
|
+
padding: "0 6px",
|
|
76
|
+
lineHeight: "14px",
|
|
77
|
+
whiteSpace: "nowrap",
|
|
78
|
+
opacity: pin.resolved ? 1 : 0.8,
|
|
79
|
+
}}
|
|
80
|
+
>
|
|
81
|
+
{spec?.icon ?? "◦"} {pin.label}
|
|
82
|
+
</span>
|
|
83
|
+
);
|
|
84
|
+
})}
|
|
85
|
+
</div>
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
39
89
|
/** Badge'e grup doczepień — toggle rozwinięcia (wspólne dla kroku i karty). */
|
|
40
90
|
function GroupBadges({
|
|
41
91
|
profile,
|
|
@@ -114,7 +164,12 @@ export function makeNodeTypes(profile: ProcessProfile) {
|
|
|
114
164
|
);
|
|
115
165
|
}
|
|
116
166
|
|
|
117
|
-
|
|
167
|
+
// Kolor z ZADEKLAROWANEGO rodzaju — element nieistniejący (TODO) wygląda
|
|
168
|
+
// jak każdy inny nod swojego rodzaju, tylko z kreskowaną ramką i lekko
|
|
169
|
+
// wyblakły (parytet z chipami w prozie).
|
|
170
|
+
const declaredKind = s.node?.kind ?? s.ref?.kind ?? "unknown";
|
|
171
|
+
const color = kindColor(profile, declaredKind);
|
|
172
|
+
const missing = !s.exists;
|
|
118
173
|
return (
|
|
119
174
|
<div
|
|
120
175
|
style={{
|
|
@@ -122,8 +177,8 @@ export function makeNodeTypes(profile: ProcessProfile) {
|
|
|
122
177
|
height: "100%",
|
|
123
178
|
boxSizing: "border-box",
|
|
124
179
|
borderRadius: 13,
|
|
125
|
-
border: `1px solid ${GLASS.border}`,
|
|
126
|
-
borderLeft: `4px solid ${color}`,
|
|
180
|
+
border: `1px ${missing ? "dashed" : "solid"} ${missing ? color : GLASS.border}`,
|
|
181
|
+
borderLeft: `4px ${missing ? "dashed" : "solid"} ${color}`,
|
|
127
182
|
background: GLASS.card,
|
|
128
183
|
backdropFilter: GLASS.blurSoft,
|
|
129
184
|
boxShadow: GLASS.shadow,
|
|
@@ -132,6 +187,7 @@ export function makeNodeTypes(profile: ProcessProfile) {
|
|
|
132
187
|
flexDirection: "column",
|
|
133
188
|
gap: 2,
|
|
134
189
|
cursor: "pointer",
|
|
190
|
+
opacity: missing ? 0.8 : 1,
|
|
135
191
|
}}
|
|
136
192
|
>
|
|
137
193
|
{hiddenHandles()}
|
|
@@ -166,12 +222,11 @@ export function makeNodeTypes(profile: ProcessProfile) {
|
|
|
166
222
|
{s.label || s.ref?.nodeId || "?"}
|
|
167
223
|
</span>
|
|
168
224
|
</div>
|
|
169
|
-
<div style={{ fontSize: 10, color:
|
|
170
|
-
{KIND_ICON[
|
|
171
|
-
{s.
|
|
172
|
-
? `${s.node!.kind}${s.method ? ` · ${s.method.name}` : ""}`
|
|
173
|
-
: `TODO: ${s.ref?.nodeId ?? "brak referencji"}`}
|
|
225
|
+
<div style={{ fontSize: 10, color: GLASS.textMuted }}>
|
|
226
|
+
{KIND_ICON[declaredKind] ?? "•"} {declaredKind}
|
|
227
|
+
{s.method ? ` · ${s.method.name}` : ""}
|
|
174
228
|
</div>
|
|
229
|
+
<PinBadges profile={profile} pins={s.pins} />
|
|
175
230
|
{s.exists ? (
|
|
176
231
|
<GroupBadges
|
|
177
232
|
profile={profile}
|