@cwygoda/service-catalog-ui 0.17.1
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/__mocks__/app-environment.d.ts +4 -0
- package/dist/__mocks__/app-environment.js +4 -0
- package/dist/__mocks__/app-state.d.ts +12 -0
- package/dist/__mocks__/app-state.js +10 -0
- package/dist/adapters/index.d.ts +1 -0
- package/dist/adapters/index.js +1 -0
- package/dist/adapters/static-json.adapter.d.ts +2 -0
- package/dist/adapters/static-json.adapter.js +34 -0
- package/dist/components/BpmnDiagram.svelte +496 -0
- package/dist/components/BpmnDiagram.svelte.d.ts +18 -0
- package/dist/components/Breadcrumbs.svelte +32 -0
- package/dist/components/Breadcrumbs.svelte.d.ts +11 -0
- package/dist/components/DataStoreCard.svelte +26 -0
- package/dist/components/DataStoreCard.svelte.d.ts +7 -0
- package/dist/components/DataStoreShield.svelte +67 -0
- package/dist/components/DataStoreShield.svelte.d.ts +9 -0
- package/dist/components/DomainCard.svelte +34 -0
- package/dist/components/DomainCard.svelte.d.ts +9 -0
- package/dist/components/DomainCard.test.d.ts +1 -0
- package/dist/components/DomainCard.test.js +45 -0
- package/dist/components/Header.svelte +144 -0
- package/dist/components/Header.svelte.d.ts +3 -0
- package/dist/components/NavModeToggle.svelte +43 -0
- package/dist/components/NavModeToggle.svelte.d.ts +18 -0
- package/dist/components/NavTree.svelte +245 -0
- package/dist/components/NavTree.svelte.d.ts +10 -0
- package/dist/components/SearchModal.svelte +288 -0
- package/dist/components/SearchModal.svelte.d.ts +3 -0
- package/dist/components/ServiceCard.svelte +36 -0
- package/dist/components/ServiceCard.svelte.d.ts +7 -0
- package/dist/components/ServiceCard.test.d.ts +1 -0
- package/dist/components/ServiceCard.test.js +43 -0
- package/dist/components/ServiceGraph.svelte +437 -0
- package/dist/components/ServiceGraph.svelte.d.ts +10 -0
- package/dist/components/ServiceTypeShield.svelte +32 -0
- package/dist/components/ServiceTypeShield.svelte.d.ts +8 -0
- package/dist/components/Shield.svelte +118 -0
- package/dist/components/Shield.svelte.d.ts +7 -0
- package/dist/components/ThemeToggle.svelte +44 -0
- package/dist/components/ThemeToggle.svelte.d.ts +18 -0
- package/dist/components/ThemeToggle.test.d.ts +1 -0
- package/dist/components/ThemeToggle.test.js +52 -0
- package/dist/components/UseCaseCard.svelte +61 -0
- package/dist/components/UseCaseCard.svelte.d.ts +7 -0
- package/dist/components/UseCaseCard.test.d.ts +1 -0
- package/dist/components/UseCaseCard.test.js +87 -0
- package/dist/components/index.d.ts +15 -0
- package/dist/components/index.js +15 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +5 -0
- package/dist/ports/catalog.port.d.ts +5 -0
- package/dist/ports/catalog.port.js +1 -0
- package/dist/ports/index.d.ts +1 -0
- package/dist/ports/index.js +1 -0
- package/dist/source.css +1 -0
- package/dist/stores/index.d.ts +4 -0
- package/dist/stores/index.js +3 -0
- package/dist/stores/nav-mode.svelte.d.ts +7 -0
- package/dist/stores/nav-mode.svelte.js +32 -0
- package/dist/stores/search.svelte.d.ts +9 -0
- package/dist/stores/search.svelte.js +14 -0
- package/dist/stores/theme.svelte.d.ts +8 -0
- package/dist/stores/theme.svelte.js +61 -0
- package/dist/utils/fetch-catalog.d.ts +6 -0
- package/dist/utils/fetch-catalog.js +26 -0
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/package.json +63 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createStaticJsonAdapter } from './static-json.adapter.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createStaticJsonAdapter } from './static-json.adapter.js';
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Value } from '@sinclair/typebox/value';
|
|
2
|
+
import { findService } from '@cwygoda/service-catalog-core/domain';
|
|
3
|
+
import { CatalogSchema } from '@cwygoda/service-catalog-core/schemas';
|
|
4
|
+
const CACHE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
5
|
+
export function createStaticJsonAdapter(baseUrl = '') {
|
|
6
|
+
let cache = null;
|
|
7
|
+
async function fetchCatalog() {
|
|
8
|
+
const now = Date.now();
|
|
9
|
+
if (cache && now - cache.timestamp < CACHE_TTL_MS) {
|
|
10
|
+
return cache.data;
|
|
11
|
+
}
|
|
12
|
+
const response = await fetch(`${baseUrl}/catalog.json`);
|
|
13
|
+
if (!response.ok) {
|
|
14
|
+
throw new Error(`Failed to fetch catalog: ${String(response.status)}`);
|
|
15
|
+
}
|
|
16
|
+
const data = await response.json();
|
|
17
|
+
if (!Value.Check(CatalogSchema, data)) {
|
|
18
|
+
const errors = [...Value.Errors(CatalogSchema, data)];
|
|
19
|
+
const message = errors.map((e) => `${e.path}: ${e.message}`).join('; ');
|
|
20
|
+
throw new Error(`Invalid catalog data: ${message}`);
|
|
21
|
+
}
|
|
22
|
+
cache = { data: data, timestamp: now };
|
|
23
|
+
return cache.data;
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
async getCatalog() {
|
|
27
|
+
return fetchCatalog();
|
|
28
|
+
},
|
|
29
|
+
async getService(id) {
|
|
30
|
+
const catalog = await fetchCatalog();
|
|
31
|
+
return findService(catalog, id);
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
}
|
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, onDestroy } from 'svelte';
|
|
3
|
+
import { browser } from '$app/environment';
|
|
4
|
+
|
|
5
|
+
interface DocLink {
|
|
6
|
+
elementId: string;
|
|
7
|
+
anchor: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
interface ElementClickEvent {
|
|
11
|
+
elementId: string;
|
|
12
|
+
docAnchor?: string | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface Props {
|
|
16
|
+
xml: string;
|
|
17
|
+
interactive?: boolean;
|
|
18
|
+
class?: string;
|
|
19
|
+
docLinks?: DocLink[];
|
|
20
|
+
onElementClick?: (event: ElementClickEvent) => void;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
interface Bounds {
|
|
24
|
+
x: number;
|
|
25
|
+
y: number;
|
|
26
|
+
width: number;
|
|
27
|
+
height: number;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface BpmnCanvas {
|
|
31
|
+
zoom(level: 'fit-viewport' | number): void;
|
|
32
|
+
zoom(): number; // get current zoom
|
|
33
|
+
viewbox(): Bounds & { scale: number; inner: Bounds; outer: Bounds };
|
|
34
|
+
viewbox(bounds: Partial<Bounds>): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const FIT_PADDING = 40; // pixels of padding around diagram when fitting
|
|
38
|
+
const FULLSCREEN_TRANSITION_MS = 100;
|
|
39
|
+
|
|
40
|
+
interface BpmnViewer {
|
|
41
|
+
importXML: (xml: string) => Promise<unknown>;
|
|
42
|
+
get: (name: string) => unknown;
|
|
43
|
+
destroy?: () => void;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let {
|
|
47
|
+
xml,
|
|
48
|
+
interactive = false,
|
|
49
|
+
class: className = '',
|
|
50
|
+
docLinks,
|
|
51
|
+
onElementClick,
|
|
52
|
+
}: Props = $props();
|
|
53
|
+
|
|
54
|
+
let wrapper: HTMLDivElement | undefined = $state();
|
|
55
|
+
let container: HTMLDivElement | undefined = $state();
|
|
56
|
+
let resizeObserver: ResizeObserver | null = null;
|
|
57
|
+
let viewer: BpmnViewer | null = $state(null);
|
|
58
|
+
let error: string | null = $state(null);
|
|
59
|
+
let ready = $state(false);
|
|
60
|
+
|
|
61
|
+
// Fullscreen state
|
|
62
|
+
let isFullscreen = $state(false);
|
|
63
|
+
|
|
64
|
+
function setBodyOverflow(hidden: boolean) {
|
|
65
|
+
if (!browser) return;
|
|
66
|
+
document.body.style.overflow = hidden ? 'hidden' : '';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function handleEscapeKey(e: KeyboardEvent) {
|
|
70
|
+
if (e.key === 'Escape' && isFullscreen) {
|
|
71
|
+
toggleFullscreen();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function toggleFullscreen() {
|
|
76
|
+
if (!browser) return;
|
|
77
|
+
isFullscreen = !isFullscreen;
|
|
78
|
+
setBodyOverflow(isFullscreen);
|
|
79
|
+
|
|
80
|
+
if (isFullscreen) {
|
|
81
|
+
document.addEventListener('keydown', handleEscapeKey);
|
|
82
|
+
} else {
|
|
83
|
+
document.removeEventListener('keydown', handleEscapeKey);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Re-fit diagram after layout settles
|
|
87
|
+
if (ready) {
|
|
88
|
+
setTimeout(fitWithPadding, FULLSCREEN_TRANSITION_MS);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function getCanvas(): BpmnCanvas | null {
|
|
93
|
+
if (!viewer) return null;
|
|
94
|
+
return viewer.get('canvas') as BpmnCanvas;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function fitWithPadding() {
|
|
98
|
+
const canvas = getCanvas();
|
|
99
|
+
if (!canvas) return;
|
|
100
|
+
|
|
101
|
+
// First fit to viewport to get the content bounds
|
|
102
|
+
canvas.zoom('fit-viewport');
|
|
103
|
+
|
|
104
|
+
// Get current viewbox info - inner is the diagram bounds, outer is container
|
|
105
|
+
const vb = canvas.viewbox();
|
|
106
|
+
const inner = vb.inner;
|
|
107
|
+
const outer = vb.outer;
|
|
108
|
+
|
|
109
|
+
// Calculate scale to fit content with padding
|
|
110
|
+
const availableWidth = outer.width - FIT_PADDING * 2;
|
|
111
|
+
const availableHeight = outer.height - FIT_PADDING * 2;
|
|
112
|
+
const scaleX = availableWidth / inner.width;
|
|
113
|
+
const scaleY = availableHeight / inner.height;
|
|
114
|
+
const scale = Math.min(scaleX, scaleY);
|
|
115
|
+
|
|
116
|
+
// Calculate centered viewbox dimensions
|
|
117
|
+
const viewWidth = outer.width / scale;
|
|
118
|
+
const viewHeight = outer.height / scale;
|
|
119
|
+
|
|
120
|
+
// Center the content
|
|
121
|
+
const centerX = inner.x + inner.width / 2;
|
|
122
|
+
const centerY = inner.y + inner.height / 2;
|
|
123
|
+
|
|
124
|
+
canvas.viewbox({
|
|
125
|
+
x: centerX - viewWidth / 2,
|
|
126
|
+
y: centerY - viewHeight / 2,
|
|
127
|
+
width: viewWidth,
|
|
128
|
+
height: viewHeight,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
function zoomIn() {
|
|
133
|
+
const canvas = getCanvas();
|
|
134
|
+
if (!canvas) return;
|
|
135
|
+
const current = canvas.zoom();
|
|
136
|
+
canvas.zoom(current * 1.25);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function zoomOut() {
|
|
140
|
+
const canvas = getCanvas();
|
|
141
|
+
if (!canvas) return;
|
|
142
|
+
const current = canvas.zoom();
|
|
143
|
+
canvas.zoom(current * 0.8);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
function resetZoom() {
|
|
147
|
+
fitWithPadding();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Re-center inline message-flow labels rendered by bpmn-js.
|
|
152
|
+
* bpmn-js positions these labels using getBBox() which can return
|
|
153
|
+
* incorrect widths before fonts are loaded, causing left-aligned labels.
|
|
154
|
+
*/
|
|
155
|
+
function fixMessageFlowLabels() {
|
|
156
|
+
if (!viewer) return;
|
|
157
|
+
|
|
158
|
+
const elementRegistry = viewer.get('elementRegistry') as {
|
|
159
|
+
forEach: (cb: (el: { id: string; type: string }) => void) => void;
|
|
160
|
+
get: (id: string) => { id: string } | undefined;
|
|
161
|
+
getGraphics: (el: { id: string }) => SVGElement | null;
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
elementRegistry.forEach((el) => {
|
|
165
|
+
if (el.type !== 'bpmn:MessageFlow') return;
|
|
166
|
+
|
|
167
|
+
// Get the connection's SVG group
|
|
168
|
+
const connGfx = elementRegistry.getGraphics(el);
|
|
169
|
+
if (!connGfx) return;
|
|
170
|
+
|
|
171
|
+
const visual = connGfx.querySelector('.djs-visual');
|
|
172
|
+
if (!visual) return;
|
|
173
|
+
|
|
174
|
+
// The actual line path is a direct child of .djs-visual;
|
|
175
|
+
// the arrow marker path lives inside <defs> and must be skipped.
|
|
176
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- needed for svelte-check
|
|
177
|
+
const linePath = visual.querySelector(':scope > path') as SVGPathElement | null;
|
|
178
|
+
if (!linePath?.getTotalLength) return;
|
|
179
|
+
|
|
180
|
+
// bpmn-js renders labels as separate shape elements: {flowId}_label
|
|
181
|
+
const labelEl = elementRegistry.get(el.id + '_label');
|
|
182
|
+
if (!labelEl) return;
|
|
183
|
+
|
|
184
|
+
const labelGfx = elementRegistry.getGraphics(labelEl);
|
|
185
|
+
if (!labelGfx) return;
|
|
186
|
+
|
|
187
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion -- needed for svelte-check
|
|
188
|
+
const textEl = labelGfx.querySelector('text') as SVGTextElement | null;
|
|
189
|
+
if (!textEl) return;
|
|
190
|
+
|
|
191
|
+
const bbox = textEl.getBBox();
|
|
192
|
+
if (bbox.width <= 0) return;
|
|
193
|
+
|
|
194
|
+
// Center the label on the midpoint of the connection path
|
|
195
|
+
const midPoint = linePath.getPointAtLength(linePath.getTotalLength() / 2);
|
|
196
|
+
const newX = midPoint.x - bbox.width / 2 - bbox.x;
|
|
197
|
+
const newY = midPoint.y - bbox.height / 2 - bbox.y;
|
|
198
|
+
labelGfx.setAttribute('transform', `matrix(1 0 0 1 ${String(newX)} ${String(newY)})`);
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
onMount(async () => {
|
|
203
|
+
if (!browser || !xml || !container) return;
|
|
204
|
+
|
|
205
|
+
try {
|
|
206
|
+
// Dynamic import to avoid SSR issues
|
|
207
|
+
const { default: BpmnViewer } = await import('bpmn-js/lib/NavigatedViewer');
|
|
208
|
+
|
|
209
|
+
viewer = new BpmnViewer({
|
|
210
|
+
container,
|
|
211
|
+
keyboard: { bindTo: interactive ? document : undefined },
|
|
212
|
+
}) as BpmnViewer;
|
|
213
|
+
|
|
214
|
+
await viewer.importXML(xml);
|
|
215
|
+
fixMessageFlowLabels();
|
|
216
|
+
|
|
217
|
+
// Register click handler for doc links
|
|
218
|
+
if (onElementClick) {
|
|
219
|
+
const eventBus = viewer.get('eventBus') as {
|
|
220
|
+
on: (event: string, callback: (e: { element: { id: string } }) => void) => void;
|
|
221
|
+
};
|
|
222
|
+
const docLinkMap = new Map((docLinks ?? []).map((l) => [l.elementId, l.anchor]));
|
|
223
|
+
|
|
224
|
+
eventBus.on('element.click', (e) => {
|
|
225
|
+
const id = e.element.id;
|
|
226
|
+
onElementClick({
|
|
227
|
+
elementId: id,
|
|
228
|
+
docAnchor: docLinkMap.get(id),
|
|
229
|
+
});
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Add pointer cursor on clickable elements
|
|
233
|
+
if (docLinks?.length) {
|
|
234
|
+
const elementRegistry = viewer.get('elementRegistry') as {
|
|
235
|
+
forEach: (cb: (el: { id: string; type: string }) => void) => void;
|
|
236
|
+
getGraphics: (el: { id: string }) => SVGElement | null;
|
|
237
|
+
};
|
|
238
|
+
elementRegistry.forEach((el) => {
|
|
239
|
+
if (docLinkMap.has(el.id)) {
|
|
240
|
+
const gfx = elementRegistry.getGraphics(el);
|
|
241
|
+
if (gfx) gfx.style.cursor = 'pointer';
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Fit diagram to container with padding
|
|
248
|
+
fitWithPadding();
|
|
249
|
+
ready = true;
|
|
250
|
+
|
|
251
|
+
// Re-fit on resize
|
|
252
|
+
resizeObserver = new ResizeObserver(() => {
|
|
253
|
+
if (ready) fitWithPadding();
|
|
254
|
+
});
|
|
255
|
+
resizeObserver.observe(container);
|
|
256
|
+
} catch (e) {
|
|
257
|
+
error = e instanceof Error ? e.message : 'Failed to render BPMN diagram';
|
|
258
|
+
console.error('BPMN render error:', e);
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
onDestroy(() => {
|
|
263
|
+
if (browser) {
|
|
264
|
+
document.removeEventListener('keydown', handleEscapeKey);
|
|
265
|
+
setBodyOverflow(false);
|
|
266
|
+
}
|
|
267
|
+
resizeObserver?.disconnect();
|
|
268
|
+
if (viewer?.destroy) {
|
|
269
|
+
viewer.destroy();
|
|
270
|
+
}
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
$effect(() => {
|
|
274
|
+
// Re-import when xml changes
|
|
275
|
+
if (browser && viewer && xml) {
|
|
276
|
+
viewer
|
|
277
|
+
.importXML(xml)
|
|
278
|
+
.then(() => {
|
|
279
|
+
fixMessageFlowLabels();
|
|
280
|
+
fitWithPadding();
|
|
281
|
+
})
|
|
282
|
+
.catch((e: unknown) => {
|
|
283
|
+
error = e instanceof Error ? e.message : 'Failed to update diagram';
|
|
284
|
+
});
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
</script>
|
|
288
|
+
|
|
289
|
+
{#if error}
|
|
290
|
+
<div
|
|
291
|
+
class="rounded-lg border border-red-200 bg-red-50 p-4 text-red-700 dark:border-red-800 dark:bg-red-900/20 dark:text-red-400"
|
|
292
|
+
>
|
|
293
|
+
<p class="font-medium">Failed to render diagram</p>
|
|
294
|
+
<p class="mt-1 text-sm">{error}</p>
|
|
295
|
+
</div>
|
|
296
|
+
{:else}
|
|
297
|
+
<div
|
|
298
|
+
bind:this={wrapper}
|
|
299
|
+
class="bpmn-wrapper {isFullscreen
|
|
300
|
+
? 'fixed inset-0 z-50 bg-white dark:bg-gray-900'
|
|
301
|
+
: 'relative'}"
|
|
302
|
+
>
|
|
303
|
+
<div
|
|
304
|
+
bind:this={container}
|
|
305
|
+
class="bpmn-container w-full {isFullscreen
|
|
306
|
+
? 'h-full bg-white dark:bg-gray-900'
|
|
307
|
+
: 'bpmn-container--inline rounded-lg border border-gray-200 bg-white dark:border-gray-700 dark:bg-gray-800'} {className}"
|
|
308
|
+
class:cursor-grab={interactive}
|
|
309
|
+
role="img"
|
|
310
|
+
aria-label="BPMN process diagram"
|
|
311
|
+
></div>
|
|
312
|
+
|
|
313
|
+
<!-- Zoom controls -->
|
|
314
|
+
{#if ready}
|
|
315
|
+
<div
|
|
316
|
+
class="absolute right-3 top-3 flex flex-col gap-1 rounded-lg border border-gray-200 bg-white/90 p-1 shadow-sm backdrop-blur-sm dark:border-gray-600 dark:bg-gray-800/90"
|
|
317
|
+
>
|
|
318
|
+
<button
|
|
319
|
+
onclick={zoomIn}
|
|
320
|
+
class="flex min-h-11 min-w-11 items-center justify-center rounded p-1.5 text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary-600 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
|
321
|
+
aria-label="Zoom in"
|
|
322
|
+
title="Zoom in"
|
|
323
|
+
>
|
|
324
|
+
<svg
|
|
325
|
+
class="h-5 w-5"
|
|
326
|
+
fill="none"
|
|
327
|
+
viewBox="0 0 24 24"
|
|
328
|
+
stroke="currentColor"
|
|
329
|
+
stroke-width="2"
|
|
330
|
+
aria-hidden="true"
|
|
331
|
+
>
|
|
332
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M12 4v16m8-8H4" />
|
|
333
|
+
</svg>
|
|
334
|
+
</button>
|
|
335
|
+
<button
|
|
336
|
+
onclick={zoomOut}
|
|
337
|
+
class="flex min-h-11 min-w-11 items-center justify-center rounded p-1.5 text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary-600 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
|
338
|
+
aria-label="Zoom out"
|
|
339
|
+
title="Zoom out"
|
|
340
|
+
>
|
|
341
|
+
<svg
|
|
342
|
+
class="h-5 w-5"
|
|
343
|
+
fill="none"
|
|
344
|
+
viewBox="0 0 24 24"
|
|
345
|
+
stroke="currentColor"
|
|
346
|
+
stroke-width="2"
|
|
347
|
+
aria-hidden="true"
|
|
348
|
+
>
|
|
349
|
+
<path stroke-linecap="round" stroke-linejoin="round" d="M20 12H4" />
|
|
350
|
+
</svg>
|
|
351
|
+
</button>
|
|
352
|
+
<div class="my-0.5 border-t border-gray-200 dark:border-gray-600"></div>
|
|
353
|
+
<button
|
|
354
|
+
onclick={resetZoom}
|
|
355
|
+
class="flex min-h-11 min-w-11 items-center justify-center rounded p-1.5 text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary-600 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
|
356
|
+
aria-label="Fit to view"
|
|
357
|
+
title="Fit to view"
|
|
358
|
+
>
|
|
359
|
+
<!-- Viewfinder/target icon for fit-to-view -->
|
|
360
|
+
<svg
|
|
361
|
+
class="h-5 w-5"
|
|
362
|
+
fill="none"
|
|
363
|
+
viewBox="0 0 24 24"
|
|
364
|
+
stroke="currentColor"
|
|
365
|
+
stroke-width="2"
|
|
366
|
+
aria-hidden="true"
|
|
367
|
+
>
|
|
368
|
+
<path
|
|
369
|
+
stroke-linecap="round"
|
|
370
|
+
stroke-linejoin="round"
|
|
371
|
+
d="M7.5 3.75H6A2.25 2.25 0 003.75 6v1.5M16.5 3.75H18A2.25 2.25 0 0120.25 6v1.5m0 9V18A2.25 2.25 0 0118 20.25h-1.5m-9 0H6A2.25 2.25 0 013.75 18v-1.5M15 12a3 3 0 11-6 0 3 3 0 016 0z"
|
|
372
|
+
/>
|
|
373
|
+
</svg>
|
|
374
|
+
</button>
|
|
375
|
+
<div class="my-0.5 border-t border-gray-200 dark:border-gray-600"></div>
|
|
376
|
+
<button
|
|
377
|
+
onclick={toggleFullscreen}
|
|
378
|
+
class="flex min-h-11 min-w-11 items-center justify-center rounded p-1.5 text-gray-600 transition-colors hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-primary-600 dark:text-gray-400 dark:hover:bg-gray-700 dark:hover:text-white"
|
|
379
|
+
aria-label={isFullscreen ? 'Exit fullscreen' : 'Enter fullscreen'}
|
|
380
|
+
title={isFullscreen ? 'Exit fullscreen' : 'Fullscreen'}
|
|
381
|
+
>
|
|
382
|
+
{#if isFullscreen}
|
|
383
|
+
<svg
|
|
384
|
+
class="h-5 w-5"
|
|
385
|
+
fill="none"
|
|
386
|
+
viewBox="0 0 24 24"
|
|
387
|
+
stroke="currentColor"
|
|
388
|
+
stroke-width="2"
|
|
389
|
+
aria-hidden="true"
|
|
390
|
+
>
|
|
391
|
+
<path
|
|
392
|
+
stroke-linecap="round"
|
|
393
|
+
stroke-linejoin="round"
|
|
394
|
+
d="M9 9V4.5M9 9H4.5M9 9L3.75 3.75M9 15v4.5M9 15H4.5M9 15l-5.25 5.25M15 9h4.5M15 9V4.5M15 9l5.25-5.25M15 15h4.5M15 15v4.5m0-4.5l5.25 5.25"
|
|
395
|
+
/>
|
|
396
|
+
</svg>
|
|
397
|
+
{:else}
|
|
398
|
+
<svg
|
|
399
|
+
class="h-5 w-5"
|
|
400
|
+
fill="none"
|
|
401
|
+
viewBox="0 0 24 24"
|
|
402
|
+
stroke="currentColor"
|
|
403
|
+
stroke-width="2"
|
|
404
|
+
aria-hidden="true"
|
|
405
|
+
>
|
|
406
|
+
<path
|
|
407
|
+
stroke-linecap="round"
|
|
408
|
+
stroke-linejoin="round"
|
|
409
|
+
d="M3.75 3.75v4.5m0-4.5h4.5m-4.5 0L9 9M3.75 20.25v-4.5m0 4.5h4.5m-4.5 0L9 15M20.25 3.75h-4.5m4.5 0v4.5m0-4.5L15 9m5.25 11.25h-4.5m4.5 0v-4.5m0 4.5L15 15"
|
|
410
|
+
/>
|
|
411
|
+
</svg>
|
|
412
|
+
{/if}
|
|
413
|
+
</button>
|
|
414
|
+
</div>
|
|
415
|
+
{/if}
|
|
416
|
+
</div>
|
|
417
|
+
{/if}
|
|
418
|
+
|
|
419
|
+
<style>
|
|
420
|
+
.bpmn-container--inline {
|
|
421
|
+
aspect-ratio: 1;
|
|
422
|
+
max-height: 75vh;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
.bpmn-container :global(.djs-container) {
|
|
426
|
+
height: 100% !important;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/* Dark mode styles for BPMN diagram */
|
|
430
|
+
:global(.dark) .bpmn-container :global(.djs-visual) {
|
|
431
|
+
/* Invert strokes: black -> light gray */
|
|
432
|
+
--bpmn-stroke: #d1d5db;
|
|
433
|
+
--bpmn-fill: #374151;
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
/* Participant/Pool borders and labels */
|
|
437
|
+
:global(.dark) .bpmn-container :global(.djs-group .djs-visual > rect) {
|
|
438
|
+
stroke: #9ca3af !important;
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
:global(.dark) .bpmn-container :global(.djs-group .djs-visual > path) {
|
|
442
|
+
stroke: #9ca3af !important;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
/* Task boxes */
|
|
446
|
+
:global(.dark) .bpmn-container :global(.djs-shape .djs-visual > rect) {
|
|
447
|
+
stroke: #9ca3af !important;
|
|
448
|
+
fill: #374151 !important;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/* Events (circles) */
|
|
452
|
+
:global(.dark) .bpmn-container :global(.djs-shape .djs-visual > circle) {
|
|
453
|
+
stroke: #9ca3af !important;
|
|
454
|
+
fill: #374151 !important;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/* Gateways (diamonds) */
|
|
458
|
+
:global(.dark) .bpmn-container :global(.djs-shape .djs-visual > polygon) {
|
|
459
|
+
stroke: #9ca3af !important;
|
|
460
|
+
fill: #374151 !important;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
/* Sequence flows (solid lines) */
|
|
464
|
+
:global(.dark) .bpmn-container :global(.djs-connection .djs-visual > path) {
|
|
465
|
+
stroke: #9ca3af !important;
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
/* Message flows (dashed lines) */
|
|
469
|
+
:global(.dark) .bpmn-container :global(.djs-connection .djs-visual > polyline) {
|
|
470
|
+
stroke: #9ca3af !important;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
/* Arrow markers */
|
|
474
|
+
:global(.dark) .bpmn-container :global(marker path) {
|
|
475
|
+
fill: #9ca3af !important;
|
|
476
|
+
stroke: #9ca3af !important;
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/* Text labels */
|
|
480
|
+
:global(.dark) .bpmn-container :global(.djs-label text),
|
|
481
|
+
:global(.dark) .bpmn-container :global(.djs-visual text),
|
|
482
|
+
:global(.dark) .bpmn-container :global(text) {
|
|
483
|
+
fill: #e5e7eb !important;
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
/* Participant header background (the title band) */
|
|
487
|
+
:global(.dark) .bpmn-container :global(.djs-visual > rect:first-child) {
|
|
488
|
+
fill: #1f2937 !important;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
/* bpmn.io logo/watermark in corner */
|
|
492
|
+
:global(.dark) .bpmn-container :global(.bjs-powered-by) {
|
|
493
|
+
filter: invert(1) hue-rotate(180deg);
|
|
494
|
+
opacity: 0.7;
|
|
495
|
+
}
|
|
496
|
+
</style>
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
interface DocLink {
|
|
2
|
+
elementId: string;
|
|
3
|
+
anchor: string;
|
|
4
|
+
}
|
|
5
|
+
interface ElementClickEvent {
|
|
6
|
+
elementId: string;
|
|
7
|
+
docAnchor?: string | undefined;
|
|
8
|
+
}
|
|
9
|
+
interface Props {
|
|
10
|
+
xml: string;
|
|
11
|
+
interactive?: boolean;
|
|
12
|
+
class?: string;
|
|
13
|
+
docLinks?: DocLink[];
|
|
14
|
+
onElementClick?: (event: ElementClickEvent) => void;
|
|
15
|
+
}
|
|
16
|
+
declare const BpmnDiagram: import("svelte").Component<Props, {}, "">;
|
|
17
|
+
type BpmnDiagram = ReturnType<typeof BpmnDiagram>;
|
|
18
|
+
export default BpmnDiagram;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
interface BreadcrumbItem {
|
|
3
|
+
label: string;
|
|
4
|
+
href: string;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
interface Props {
|
|
8
|
+
items: BreadcrumbItem[];
|
|
9
|
+
current: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let { items, current }: Props = $props();
|
|
13
|
+
</script>
|
|
14
|
+
|
|
15
|
+
<nav aria-label="Breadcrumb" class="mb-6">
|
|
16
|
+
<ol class="flex flex-wrap items-center gap-1 text-xs sm:text-sm">
|
|
17
|
+
{#each items as item (item.href)}
|
|
18
|
+
<li class="flex items-center">
|
|
19
|
+
<a
|
|
20
|
+
href={item.href}
|
|
21
|
+
class="text-primary-600 hover:text-primary-800 hover:underline dark:text-primary-400 dark:hover:text-primary-200"
|
|
22
|
+
>
|
|
23
|
+
{item.label}
|
|
24
|
+
</a>
|
|
25
|
+
<span class="mx-1 sm:mx-2 text-gray-400" aria-hidden="true">/</span>
|
|
26
|
+
</li>
|
|
27
|
+
{/each}
|
|
28
|
+
<li class="text-gray-700 dark:text-gray-300" aria-current="page">
|
|
29
|
+
{current}
|
|
30
|
+
</li>
|
|
31
|
+
</ol>
|
|
32
|
+
</nav>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
interface BreadcrumbItem {
|
|
2
|
+
label: string;
|
|
3
|
+
href: string;
|
|
4
|
+
}
|
|
5
|
+
interface Props {
|
|
6
|
+
items: BreadcrumbItem[];
|
|
7
|
+
current: string;
|
|
8
|
+
}
|
|
9
|
+
declare const Breadcrumbs: import("svelte").Component<Props, {}, "">;
|
|
10
|
+
type Breadcrumbs = ReturnType<typeof Breadcrumbs>;
|
|
11
|
+
export default Breadcrumbs;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import type { DataStore } from '@cwygoda/service-catalog-core/domain';
|
|
3
|
+
import DataStoreShield from './DataStoreShield.svelte';
|
|
4
|
+
|
|
5
|
+
let { dataStore }: { dataStore: DataStore } = $props();
|
|
6
|
+
</script>
|
|
7
|
+
|
|
8
|
+
<a
|
|
9
|
+
href="/data-stores/{dataStore.id}"
|
|
10
|
+
aria-label="View {dataStore.name} data store"
|
|
11
|
+
class="block rounded-lg border border-gray-200 bg-white p-6 shadow-sm transition-shadow hover:shadow-md active:shadow-sm dark:border-gray-700 dark:bg-gray-800"
|
|
12
|
+
>
|
|
13
|
+
<div class="mb-3 flex items-start gap-4">
|
|
14
|
+
<DataStoreShield type={dataStore.type} technology={dataStore.technology} size={44} />
|
|
15
|
+
<div class="min-w-0 flex-1">
|
|
16
|
+
<div class="flex items-center justify-between gap-2">
|
|
17
|
+
<h3 class="truncate text-lg font-semibold text-gray-900 dark:text-white">
|
|
18
|
+
{dataStore.name}
|
|
19
|
+
</h3>
|
|
20
|
+
</div>
|
|
21
|
+
<p class="mt-1 text-sm text-gray-600 dark:text-gray-400">
|
|
22
|
+
{dataStore.description}
|
|
23
|
+
</p>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
</a>
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { DataStore } from '@cwygoda/service-catalog-core/domain';
|
|
2
|
+
type $$ComponentProps = {
|
|
3
|
+
dataStore: DataStore;
|
|
4
|
+
};
|
|
5
|
+
declare const DataStoreCard: import("svelte").Component<$$ComponentProps, {}, "">;
|
|
6
|
+
type DataStoreCard = ReturnType<typeof DataStoreCard>;
|
|
7
|
+
export default DataStoreCard;
|