@ocelescope/resources 0.0.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 ADDED
@@ -0,0 +1,29 @@
1
+ # react-components-starter
2
+
3
+ A starter for creating a React component library.
4
+
5
+ ## Development
6
+
7
+ - Install dependencies:
8
+
9
+ ```bash
10
+ npm install
11
+ ```
12
+
13
+ - Run the playground:
14
+
15
+ ```bash
16
+ npm run play
17
+ ```
18
+
19
+ - Run the unit tests:
20
+
21
+ ```bash
22
+ npm run test
23
+ ```
24
+
25
+ - Build the library:
26
+
27
+ ```bash
28
+ npm run build
29
+ ```
@@ -0,0 +1,110 @@
1
+ import { t as CytoscapeContext } from "./CytoscapeContext-DrvVpY9-.js";
2
+ import { useEffect, useRef } from "react";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import CytoscapeComponent from "react-cytoscapejs";
5
+
6
+ //#region src/components/Cytoscape/Cytoscape.tsx
7
+ const CytoscapeGraph = ({ children, ...props }) => {
8
+ const cytoscapeRef = useRef(null);
9
+ useEffect(() => {
10
+ const cy = cytoscapeRef.current;
11
+ if (!cy) return;
12
+ const handleResize = () => {
13
+ cy.fit();
14
+ };
15
+ cy.on("resize", handleResize);
16
+ return () => {
17
+ cy.removeListener("resize", handleResize);
18
+ };
19
+ }, []);
20
+ const nodeStyles = [{
21
+ selector: "node",
22
+ css: {
23
+ "font-size": 14,
24
+ shape: "data(shape)",
25
+ label: "data(label)",
26
+ "text-valign": "data(label_pos)",
27
+ "text-halign": "center",
28
+ width: "data(width)",
29
+ height: "data(height)",
30
+ "background-color": "data(color)"
31
+ }
32
+ }, {
33
+ selector: "node[border_color]",
34
+ css: {
35
+ "border-width": 2,
36
+ "border-color": "data(border_color)",
37
+ "border-style": "solid"
38
+ }
39
+ }];
40
+ const edgeStyles = [
41
+ {
42
+ selector: "edge",
43
+ css: {
44
+ "line-color": "data(color)",
45
+ "target-arrow-color": "data(color)",
46
+ "source-arrow-color": "data(color)",
47
+ "arrow-scale": 1.5,
48
+ "curve-style": "bezier",
49
+ "font-size": 12,
50
+ "text-background-opacity": 1,
51
+ "text-background-shape": "roundrectangle",
52
+ "text-background-padding": "3px",
53
+ "text-background-color": "#888"
54
+ }
55
+ },
56
+ {
57
+ selector: "edge[color]",
58
+ css: { "text-background-color": "data(color)" }
59
+ },
60
+ {
61
+ selector: "edge[label]",
62
+ css: {
63
+ label: "data(label)",
64
+ "text-rotation": "autorotate"
65
+ }
66
+ },
67
+ {
68
+ selector: "edge[start_label]",
69
+ css: {
70
+ "source-label": "data(start_label)",
71
+ "source-text-rotation": "autorotate",
72
+ "source-text-offset": 20
73
+ }
74
+ },
75
+ {
76
+ selector: "edge[end_label]",
77
+ css: {
78
+ "target-label": "data(end_label)",
79
+ "target-text-rotation": "autorotate",
80
+ "target-text-offset": 20
81
+ }
82
+ },
83
+ {
84
+ selector: "edge[start_arrow]",
85
+ css: { "source-arrow-shape": "data(start_arrow)" }
86
+ },
87
+ {
88
+ selector: "edge[end_arrow]",
89
+ css: { "target-arrow-shape": "data(end_arrow)" }
90
+ }
91
+ ];
92
+ return /* @__PURE__ */ jsxs(CytoscapeContext.Provider, {
93
+ value: { cy: cytoscapeRef },
94
+ children: [/* @__PURE__ */ jsx(CytoscapeComponent, {
95
+ style: {
96
+ width: "100%",
97
+ height: "100%"
98
+ },
99
+ stylesheet: [...nodeStyles, ...edgeStyles],
100
+ cy: (cy) => {
101
+ cytoscapeRef.current = cy;
102
+ },
103
+ ...props
104
+ }), children]
105
+ });
106
+ };
107
+ var Cytoscape_default = CytoscapeGraph;
108
+
109
+ //#endregion
110
+ export { Cytoscape_default as default };
@@ -0,0 +1,10 @@
1
+ import { createContext, useContext } from "react";
2
+
3
+ //#region src/components/Cytoscape/CytoscapeContext.tsx
4
+ const CytoscapeContext = createContext(void 0);
5
+ const useCytoscapeContext = () => {
6
+ return useContext(CytoscapeContext);
7
+ };
8
+
9
+ //#endregion
10
+ export { useCytoscapeContext as n, CytoscapeContext as t };
@@ -0,0 +1,27 @@
1
+ import { GetResourceResponse } from "@ocelescope/api-base";
2
+
3
+ //#region src/types/index.d.ts
4
+ type RequireType<T> = T extends {
5
+ type?: infer L;
6
+ } ? Omit<T, "type"> & {
7
+ type: L;
8
+ } : T;
9
+ type VisulizationsType = RequireType<NonNullable<GetResourceResponse["visualization"]>>;
10
+ //#endregion
11
+ //#region src/components/ResourceView/index.d.ts
12
+ declare const Visualization: React.FC<{
13
+ visualization: VisulizationsType;
14
+ isPreview?: boolean;
15
+ }>;
16
+ declare const ResourceViewer: React.FC<{
17
+ id: string;
18
+ isPreview?: boolean;
19
+ }>;
20
+ //#endregion
21
+ //#region src/components/ResourceModal/ResourceModal.d.ts
22
+ declare const ResourceModal: React.FC<{
23
+ id: string;
24
+ onClose: () => void;
25
+ }>;
26
+ //#endregion
27
+ export { ResourceModal, ResourceViewer, Visualization };
package/dist/index.js ADDED
@@ -0,0 +1,694 @@
1
+ import { n as useCytoscapeContext } from "./CytoscapeContext-DrvVpY9-.js";
2
+ import { ActionIcon, Box, Button, ButtonGroup, Group, LoadingOverlay, Modal, Paper, Stack, ThemeIcon, Title } from "@mantine/core";
3
+ import dynamic from "next/dynamic";
4
+ import { DownloadIcon, EyeOffIcon, Maximize, MaximizeIcon, MinusIcon, PlusIcon, Save, Settings, XIcon } from "lucide-react";
5
+ import { Fragment, useEffect, useMemo, useRef, useState } from "react";
6
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
7
+ import { arrow, flip, offset, shift, useFloating } from "@floating-ui/react";
8
+ import { Graphviz } from "@hpcc-js/wasm-graphviz";
9
+ import { isNil, omitBy } from "lodash";
10
+ import { DataTable } from "mantine-datatable";
11
+ import dayjs from "dayjs";
12
+ import isBetween from "dayjs/plugin/isBetween";
13
+ import { saveAs } from "file-saver";
14
+ import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
15
+ import { useResource } from "@ocelescope/api-base";
16
+
17
+ //#region src/components/Cytoscape/index.tsx
18
+ var Cytoscape_default = dynamic(() => import("./Cytoscape-BpEZfi09.js"), { ssr: false });
19
+
20
+ //#endregion
21
+ //#region src/components/Cytoscape/components/ActionButtons.tsx
22
+ const ActionButtons = ({ toggleOptions, onSave }) => {
23
+ const context = useCytoscapeContext();
24
+ const downloadGraphAsPng = () => {
25
+ const cytoscape = context?.cy.current;
26
+ if (!cytoscape) return;
27
+ const pngData = cytoscape.png({
28
+ full: true,
29
+ scale: 2,
30
+ bg: "#ffffff"
31
+ });
32
+ const a = document.createElement("a");
33
+ a.href = pngData;
34
+ a.download = "graph.png";
35
+ a.dispatchEvent(new MouseEvent("click", {
36
+ bubbles: true,
37
+ cancelable: true,
38
+ view: window
39
+ }));
40
+ };
41
+ return /* @__PURE__ */ jsxs(ButtonGroup, {
42
+ style: { zIndex: 2 },
43
+ orientation: "vertical",
44
+ pos: "absolute",
45
+ bottom: 0,
46
+ left: 0,
47
+ children: [
48
+ /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
49
+ disabled: !context,
50
+ px: "xs",
51
+ variant: "default",
52
+ onClick: context && (() => {
53
+ if (context.cy.current) context.cy.current.fit();
54
+ }),
55
+ children: /* @__PURE__ */ jsx(Maximize, { size: 18 })
56
+ }), /* @__PURE__ */ jsx(Button, {
57
+ disabled: !context,
58
+ onClick: downloadGraphAsPng,
59
+ px: "xs",
60
+ variant: "default",
61
+ children: /* @__PURE__ */ jsx(DownloadIcon, { size: 18 })
62
+ })] }),
63
+ toggleOptions && /* @__PURE__ */ jsx(Button, {
64
+ onClick: toggleOptions,
65
+ px: "xs",
66
+ variant: "default",
67
+ children: /* @__PURE__ */ jsx(Settings, { size: 18 })
68
+ }),
69
+ onSave && /* @__PURE__ */ jsx(Button, {
70
+ onClick: onSave,
71
+ px: "xs",
72
+ variant: "default",
73
+ children: /* @__PURE__ */ jsx(Save, { size: 18 })
74
+ })
75
+ ]
76
+ });
77
+ };
78
+ var ActionButtons_default = ActionButtons;
79
+
80
+ //#endregion
81
+ //#region src/components/Cytoscape/hooks/useEntityClick.ts
82
+ const triggerActionToStartEvent = {
83
+ hover: "tapdragover",
84
+ rightClick: "cxttap",
85
+ leftClick: "tap",
86
+ doubleClick: "dbltap"
87
+ };
88
+ const useEntityClick = ({ trigger }) => {
89
+ const context = useCytoscapeContext();
90
+ const [entity, setEntity] = useState();
91
+ const containerRef = useRef(null);
92
+ useEffect(() => {
93
+ const cytoscape = context?.cy.current;
94
+ const container = cytoscape?.container();
95
+ if (!cytoscape || !container) return;
96
+ containerRef.current = container;
97
+ const handleEntitySelect = (event) => {
98
+ const target = event.target;
99
+ if (target.isNode()) setEntity({
100
+ type: "node",
101
+ id: target.id(),
102
+ boundingBox: target.renderedBoundingBox()
103
+ });
104
+ else if (target.isEdge()) {
105
+ const midpoint = target.renderedMidpoint();
106
+ setEntity({
107
+ type: "edge",
108
+ id: target.id(),
109
+ midpoint: {
110
+ x: midpoint.x,
111
+ y: midpoint.y
112
+ }
113
+ });
114
+ }
115
+ };
116
+ const handleClickOutside = (e) => {
117
+ if (e.target === context?.cy.current) setEntity(void 0);
118
+ };
119
+ const handleViewChange = () => {
120
+ setEntity((prev) => {
121
+ if (!prev) return prev;
122
+ const ele = context?.cy.current?.getElementById(prev.id);
123
+ if (!ele || ele.empty()) return prev;
124
+ if (prev.type === "node") return {
125
+ ...prev,
126
+ boundingBox: ele.renderedBoundingBox()
127
+ };
128
+ const midpoint = ele.renderedMidpoint();
129
+ return {
130
+ ...prev,
131
+ midpoint
132
+ };
133
+ });
134
+ };
135
+ cytoscape.on(triggerActionToStartEvent[trigger], "node, edge", handleEntitySelect);
136
+ cytoscape.on("pan zoom position", handleViewChange);
137
+ cytoscape.on("tap", handleClickOutside);
138
+ return () => {
139
+ cytoscape.removeListener(triggerActionToStartEvent[trigger], "node, edge", handleEntitySelect);
140
+ cytoscape.removeListener("pan zoom position", handleViewChange);
141
+ cytoscape.removeListener("tap", handleClickOutside);
142
+ };
143
+ }, [context, trigger]);
144
+ return {
145
+ entity,
146
+ container: containerRef,
147
+ resetEntity: () => setEntity(void 0)
148
+ };
149
+ };
150
+ var useEntityClick_default = useEntityClick;
151
+
152
+ //#endregion
153
+ //#region src/components/Cytoscape/components/FloatingAnnotation.tsx
154
+ const FloatingAnnotation = ({ trigger = "leftClick", children }) => {
155
+ const { entity, container } = useEntityClick_default({ trigger });
156
+ const arrowRef = useRef(null);
157
+ const virtualRef = useMemo(() => {
158
+ const containerRect = container.current?.getBoundingClientRect();
159
+ if (!entity || !containerRect) return;
160
+ if (entity.type === "node") {
161
+ const b = entity.boundingBox;
162
+ const width = b.x2 - b.x1;
163
+ const height = b.y2 - b.y1;
164
+ const top = containerRect.top + b.y1;
165
+ const left = containerRect.left + b.x1;
166
+ return { getBoundingClientRect: () => ({
167
+ x: left,
168
+ y: top,
169
+ width,
170
+ height,
171
+ top,
172
+ left,
173
+ right: left + width,
174
+ bottom: top + height
175
+ }) };
176
+ }
177
+ const m = entity.midpoint;
178
+ const x$1 = containerRect.left + m.x;
179
+ const y$1 = containerRect.top + m.y;
180
+ return { getBoundingClientRect: () => ({
181
+ x: x$1,
182
+ y: y$1,
183
+ width: 0,
184
+ height: 0,
185
+ top: y$1,
186
+ left: x$1,
187
+ right: x$1,
188
+ bottom: y$1
189
+ }) };
190
+ }, [entity, container]);
191
+ const { x, y, refs, strategy } = useFloating({
192
+ placement: "right",
193
+ strategy: "fixed",
194
+ middleware: [
195
+ flip(),
196
+ offset(10),
197
+ shift({ padding: 8 }),
198
+ arrow({ element: arrowRef })
199
+ ]
200
+ });
201
+ useEffect(() => {
202
+ refs.setReference(virtualRef);
203
+ }, [virtualRef, refs]);
204
+ const child = useMemo(() => entity ? children(entity) : void 0, [entity, children]);
205
+ if (!entity || !virtualRef || child == null) return null;
206
+ return /* @__PURE__ */ jsx(Paper, {
207
+ ref: refs.setFloating,
208
+ shadow: "md",
209
+ radius: "md",
210
+ p: "md",
211
+ withBorder: true,
212
+ style: {
213
+ position: strategy,
214
+ top: y ?? 0,
215
+ left: x ?? 0,
216
+ width: "min(90vw, 400px)",
217
+ height: "min(80vh, 300px)",
218
+ overflow: "auto",
219
+ zIndex: 9999
220
+ },
221
+ children: child
222
+ });
223
+ };
224
+ var FloatingAnnotation_default = FloatingAnnotation;
225
+
226
+ //#endregion
227
+ //#region src/hooks/useGraphvizLayout.ts
228
+ const esc = (val) => {
229
+ return val.replaceAll("\"", "\\\"");
230
+ };
231
+ const safeId = (id) => id.replace(/[^a-zA-Z0-9_]/g, "_");
232
+ const attrsToDot = (obj) => {
233
+ if (!obj) return "";
234
+ return Object.entries(obj).filter(([_, v]) => v !== null && v !== void 0).map(([k, v]) => typeof v === "string" ? `${k}="${esc(v)}"` : `${k}=${String(v)}`).join(",");
235
+ };
236
+ const toDot = (visualization) => {
237
+ const gAttrs = attrsToDot(visualization.layout_config?.graphAttrs ?? void 0);
238
+ const nAttrs = attrsToDot(visualization.layout_config?.nodeAttrs ?? void 0);
239
+ const eAttrs = attrsToDot(visualization.layout_config?.edgeAttrs ?? void 0);
240
+ const lines = [
241
+ "digraph G {",
242
+ `graph [${gAttrs}];`,
243
+ `node [${nAttrs}];`,
244
+ `edge [${eAttrs}];`
245
+ ];
246
+ for (const node of visualization.nodes ?? []) {
247
+ const id = safeId(node?.id ?? "");
248
+ const attrs = {
249
+ ...node.layout_attrs,
250
+ label: node.label ? esc(node.label) : void 0,
251
+ shape: node.shape,
252
+ color: node.color,
253
+ ...node.width && { width: (node.width / 72).toFixed(4) },
254
+ ...node.height && { height: (node.height / 72).toFixed(4) }
255
+ };
256
+ lines.push(`"${id}" [${attrsToDot(attrs)}];`);
257
+ }
258
+ for (const edge of visualization.edges ?? []) {
259
+ const attrs = {
260
+ ...edge.layout_attrs,
261
+ label: edge.label ? esc(edge.label) : void 0,
262
+ color: edge.color
263
+ };
264
+ lines.push(`"${esc(safeId(edge.source))}" -> "${esc(safeId(edge.target))}" [${attrsToDot(attrs)}];`);
265
+ }
266
+ const ranks = (visualization.nodes ?? []).filter(({ rank }) => rank != null).reduce((acc, current) => {
267
+ const key = current.rank;
268
+ acc[key] ??= [];
269
+ acc[key].push(esc(safeId(current?.id ?? "")));
270
+ return acc;
271
+ }, {});
272
+ lines.push(...Object.entries(ranks).map(([key, value]) => `{ rank=${["source", "sink"].includes(key) ? key : "same"}; ${(value ?? []).join(" ")} } `));
273
+ lines.push("}");
274
+ return lines.join("\n");
275
+ };
276
+ const parsePos = (s) => {
277
+ if (s) {
278
+ const [xs, ys] = s.split(",");
279
+ const x = Number.parseFloat(xs ?? "0");
280
+ const y = Number.parseFloat(ys ?? "0");
281
+ if (Number.isFinite(x) && Number.isFinite(y)) return {
282
+ x,
283
+ y
284
+ };
285
+ }
286
+ return {
287
+ x: 0,
288
+ y: 0
289
+ };
290
+ };
291
+ const useGraphvizLayout = (visualization) => {
292
+ const [elements, setElements] = useState(null);
293
+ const [error, setError] = useState(null);
294
+ const cancelRef = useRef({ cancelled: false });
295
+ const dot = useMemo(() => toDot(visualization), [visualization]);
296
+ useEffect(() => {
297
+ cancelRef.current.cancelled = false;
298
+ setError(null);
299
+ (async () => {
300
+ try {
301
+ const jsonStr = (await Graphviz.load()).layout(dot, "json", visualization.layout_config?.engine ?? "dot");
302
+ const gvJson = JSON.parse(jsonStr);
303
+ const nodePos = {};
304
+ for (const o of gvJson.objects ?? []) {
305
+ if (!o.name || !o.pos) return;
306
+ const p = parsePos(o.pos);
307
+ if (!p) return;
308
+ nodePos[o.name] = {
309
+ ...p,
310
+ width: o.width ? Number.parseFloat(o.width) * 72 : 0
311
+ };
312
+ }
313
+ const nodes = (visualization.nodes ?? []).map((node) => ({
314
+ data: {
315
+ ...omitBy(node, isNil),
316
+ label: node.label ?? "",
317
+ shape: node.shape !== "circle" ? node.shape : "ellipse",
318
+ width: node.width ?? nodePos[safeId(node.id ?? "")]?.width,
319
+ height: node.height ?? 30
320
+ },
321
+ position: nodePos[safeId(node.id ?? "")] ?? {
322
+ x: 0,
323
+ y: 0
324
+ }
325
+ }));
326
+ const edges = (visualization.edges ?? []).map((edge) => ({ data: omitBy(edge, isNil) }));
327
+ if (!cancelRef.current.cancelled) setElements([...nodes, ...edges]);
328
+ } catch (e) {
329
+ if (!cancelRef.current.cancelled) {
330
+ setError(e?.message ?? "Graphviz layout failed.");
331
+ setElements(null);
332
+ }
333
+ }
334
+ })();
335
+ return () => {
336
+ cancelRef.current.cancelled = true;
337
+ };
338
+ }, [dot, visualization]);
339
+ return {
340
+ elements,
341
+ error
342
+ };
343
+ };
344
+
345
+ //#endregion
346
+ //#region src/components/ResourceView/Viewers/Graph.tsx
347
+ const GraphViewer = ({ visualization, isPreview }) => {
348
+ const { elements } = useGraphvizLayout(visualization);
349
+ return /* @__PURE__ */ jsx(Box, {
350
+ h: "100%",
351
+ w: "100%",
352
+ pos: "relative",
353
+ style: { overflow: "hidden" },
354
+ children: elements && /* @__PURE__ */ jsx(Cytoscape_default, {
355
+ userZoomingEnabled: !isPreview,
356
+ userPanningEnabled: !isPreview,
357
+ style: {
358
+ width: "100%",
359
+ height: "100%"
360
+ },
361
+ elements,
362
+ layout: { name: "preset" },
363
+ children: !isPreview && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ActionButtons_default, {}), /* @__PURE__ */ jsx(FloatingAnnotation_default, { children: (entity) => {
364
+ const visualizationEntity = visualization[entity.type === "edge" ? "edges" : "nodes"]?.find(({ id }) => id === entity.id);
365
+ if (!visualizationEntity?.annotation) return;
366
+ return /* @__PURE__ */ jsx(Visualization, {
367
+ visualization: visualizationEntity.annotation,
368
+ isPreview: true
369
+ });
370
+ } })] })
371
+ })
372
+ });
373
+ };
374
+ var Graph_default = GraphViewer;
375
+
376
+ //#endregion
377
+ //#region src/util/dayjs.ts
378
+ dayjs.extend(isBetween);
379
+ var dayjs_default = dayjs;
380
+ const formatDateTime = (dateString) => dayjs(dateString).format("YYYY-MM-DD HH:mm");
381
+ const formatDate = (dateString) => dayjs(dateString).format("YYYY-MM-DD");
382
+
383
+ //#endregion
384
+ //#region src/components/ResourceView/Viewers/Table.tsx
385
+ const formatCell = (value, dataType) => {
386
+ if (value == null) return "";
387
+ switch (dataType) {
388
+ case "boolean": return value ? "✅" : "❌";
389
+ case "date": return dayjs_default(value).isValid() ? formatDate(value) : String(value);
390
+ case "datetime": return dayjs_default(value).isValid() ? formatDateTime(value) : String(value);
391
+ default: return String(value);
392
+ }
393
+ };
394
+ const PAGE_SIZES = [
395
+ 20,
396
+ 50,
397
+ 75
398
+ ];
399
+ const TableView = ({ visualization }) => {
400
+ const [pageSize, setPageSize] = useState(PAGE_SIZES[0]);
401
+ const columns = useMemo(() => visualization.columns.map((col) => ({
402
+ accessor: col.id,
403
+ title: col.label ?? col.id,
404
+ render: (record) => formatCell(record[col.id], col.data_type)
405
+ })), [visualization.columns]);
406
+ const [page, setPage] = useState(1);
407
+ const [records, setRecords] = useState(visualization.rows.slice(0, pageSize));
408
+ useEffect(() => {
409
+ const from = (page - 1) * pageSize;
410
+ const to = from + pageSize;
411
+ setRecords(visualization.rows.slice(from, to));
412
+ }, [
413
+ page,
414
+ pageSize,
415
+ visualization
416
+ ]);
417
+ return /* @__PURE__ */ jsx(DataTable, {
418
+ records,
419
+ columns,
420
+ withTableBorder: true,
421
+ withColumnBorders: true,
422
+ withRowBorders: true,
423
+ ...visualization.rows.length > PAGE_SIZES[0] ? {
424
+ totalRecords: visualization.rows.length,
425
+ recordsPerPage: pageSize,
426
+ onPageChange: (p) => setPage(p),
427
+ page,
428
+ recordsPerPageOptions: [...PAGE_SIZES],
429
+ onRecordsPerPageChange: (newPageSize) => {
430
+ setPageSize(newPageSize);
431
+ setPage(1);
432
+ }
433
+ } : {}
434
+ });
435
+ };
436
+ var Table_default = TableView;
437
+
438
+ //#endregion
439
+ //#region src/components/ResourceView/Viewers/SVG.tsx
440
+ const SvgPanWrapper = ({ children, onDownload }) => {
441
+ return /* @__PURE__ */ jsx(Box, {
442
+ w: "100%",
443
+ h: "100%",
444
+ pos: "relative",
445
+ children: /* @__PURE__ */ jsx(TransformWrapper, {
446
+ initialScale: 1,
447
+ minScale: .1,
448
+ maxScale: 10,
449
+ centerOnInit: true,
450
+ children: ({ zoomIn, zoomOut, resetTransform }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(TransformComponent, {
451
+ wrapperStyle: {
452
+ width: "100%",
453
+ height: "100%"
454
+ },
455
+ contentStyle: {
456
+ width: "100%",
457
+ height: "100%"
458
+ },
459
+ children
460
+ }), /* @__PURE__ */ jsxs(ButtonGroup, {
461
+ orientation: "vertical",
462
+ pos: "absolute",
463
+ bottom: 0,
464
+ children: [
465
+ /* @__PURE__ */ jsx(Button, {
466
+ px: "xs",
467
+ children: /* @__PURE__ */ jsx(MaximizeIcon, {
468
+ width: 18,
469
+ onClick: () => resetTransform(0)
470
+ })
471
+ }),
472
+ /* @__PURE__ */ jsx(Button, {
473
+ px: "xs",
474
+ children: /* @__PURE__ */ jsx(PlusIcon, {
475
+ width: 18,
476
+ onClick: () => zoomIn()
477
+ })
478
+ }),
479
+ /* @__PURE__ */ jsx(Button, {
480
+ px: "xs",
481
+ children: /* @__PURE__ */ jsx(MinusIcon, {
482
+ width: 18,
483
+ onClick: () => zoomOut()
484
+ })
485
+ }),
486
+ /* @__PURE__ */ jsx(Button, {
487
+ px: "xs",
488
+ onClick: onDownload,
489
+ children: /* @__PURE__ */ jsx(DownloadIcon, { width: 18 })
490
+ })
491
+ ]
492
+ })] })
493
+ })
494
+ });
495
+ };
496
+ const SvgViewer = ({ visualization, isPreview }) => {
497
+ const cleanedSvg = visualization.svg.replace(/width="[^"]+"/, "width=\"100%\"").replace(/height="[^"]+"/, "height=\"100%\"").replace(/<svg(?![^>]*xmlns=)/, "<svg xmlns=\"http://www.w3.org/2000/svg\"");
498
+ const Wrapper = isPreview ? Fragment : SvgPanWrapper;
499
+ const handleDownload = () => {
500
+ if (!cleanedSvg) return;
501
+ saveAs(new Blob([cleanedSvg], { type: "image/svg+xml;charset=utf-8" }), "resource.svg");
502
+ };
503
+ return /* @__PURE__ */ jsx(Wrapper, {
504
+ onDownload: handleDownload,
505
+ children: /* @__PURE__ */ jsx("div", {
506
+ style: {
507
+ width: "100%",
508
+ height: "100%"
509
+ },
510
+ dangerouslySetInnerHTML: { __html: cleanedSvg }
511
+ })
512
+ });
513
+ };
514
+ var SVG_default = SvgViewer;
515
+
516
+ //#endregion
517
+ //#region src/components/ResourceView/Viewers/Dot.tsx
518
+ const DotToSvgViewer = ({ isPreview, visualization }) => {
519
+ const [svg, setSvg] = useState(null);
520
+ const [error, setError] = useState(null);
521
+ const cancelRef = useRef({ cancelled: false });
522
+ useEffect(() => {
523
+ cancelRef.current.cancelled = false;
524
+ setError(null);
525
+ setSvg(null);
526
+ (async () => {
527
+ try {
528
+ const out = (await Graphviz.load()).layout(visualization.dot_str, "svg", visualization.layout_engine);
529
+ if (!cancelRef.current.cancelled) setSvg(out);
530
+ } catch (e) {
531
+ if (!cancelRef.current.cancelled) setError(e?.message ?? "Graphviz rendering failed.");
532
+ }
533
+ })();
534
+ return () => {
535
+ cancelRef.current.cancelled = true;
536
+ };
537
+ }, [visualization]);
538
+ const cleanedSvg = useMemo(() => {
539
+ if (!svg) return null;
540
+ let s = svg.replace(/width="[^"]+"/, "width=\"100%\"").replace(/height="[^"]+"/, "height=\"100%\"").replace(/<svg(?![^>]*xmlns=)/, "<svg xmlns=\"http://www.w3.org/2000/svg\"");
541
+ if (!s.trim().startsWith("<?xml")) s = `<?xml version="1.0" encoding="UTF-8"?>\n${s}`;
542
+ return s;
543
+ }, [svg]);
544
+ const handleDownload = () => {
545
+ if (!cleanedSvg) return;
546
+ saveAs(new Blob([cleanedSvg], { type: "image/svg+xml;charset=utf-8" }), "resource.svg");
547
+ };
548
+ return /* @__PURE__ */ jsx(isPreview ? Fragment : SvgPanWrapper, {
549
+ onDownload: handleDownload,
550
+ children: /* @__PURE__ */ jsxs("div", {
551
+ style: {
552
+ width: "100%",
553
+ height: "100%",
554
+ position: "relative"
555
+ },
556
+ children: [error && /* @__PURE__ */ jsxs("div", {
557
+ style: {
558
+ position: "absolute",
559
+ top: 8,
560
+ left: 8,
561
+ color: "red",
562
+ zIndex: 10
563
+ },
564
+ children: ["Graphviz error: ", error]
565
+ }), cleanedSvg ? /* @__PURE__ */ jsx("div", {
566
+ style: {
567
+ width: "100%",
568
+ height: "100%"
569
+ },
570
+ dangerouslySetInnerHTML: { __html: cleanedSvg }
571
+ }) : !error && /* @__PURE__ */ jsx("div", {
572
+ style: {
573
+ width: "100%",
574
+ height: "100%",
575
+ display: "grid",
576
+ placeItems: "center",
577
+ opacity: .6,
578
+ fontSize: 14
579
+ },
580
+ children: "Rendering…"
581
+ })]
582
+ })
583
+ });
584
+ };
585
+ var Dot_default = DotToSvgViewer;
586
+
587
+ //#endregion
588
+ //#region src/components/ResourceView/Viewers/Plotly.tsx
589
+ const Plot = dynamic(() => import("react-plotly.js"), { ssr: false });
590
+ const PlotlyViewer = ({ visualization, isPreview }) => {
591
+ const { data, layout } = visualization.data;
592
+ return /* @__PURE__ */ jsx(Plot, {
593
+ data,
594
+ layout: { ...layout },
595
+ config: {
596
+ responsive: true,
597
+ displaylogo: false,
598
+ staticPlot: !!isPreview,
599
+ showTips: false
600
+ },
601
+ useResizeHandler: true,
602
+ style: {
603
+ width: "100%",
604
+ height: "100%"
605
+ }
606
+ });
607
+ };
608
+ var Plotly_default = PlotlyViewer;
609
+
610
+ //#endregion
611
+ //#region src/components/ResourceView/index.tsx
612
+ const visulizationMap = {
613
+ table: Table_default,
614
+ svg: SVG_default,
615
+ graph: Graph_default,
616
+ dot: Dot_default,
617
+ plotly: Plotly_default
618
+ };
619
+ const Visualization = ({ visualization, isPreview = false }) => {
620
+ const Component = visulizationMap[visualization.type];
621
+ return /* @__PURE__ */ jsx(Component, {
622
+ visualization,
623
+ isPreview
624
+ });
625
+ };
626
+ const ResourceViewer = ({ id, isPreview = false }) => {
627
+ const { data } = useResource(id);
628
+ if (!data) return /* @__PURE__ */ jsx(LoadingOverlay, {});
629
+ if (!data.visualization?.type) return /* @__PURE__ */ jsx(Stack, {
630
+ justify: "center",
631
+ align: "center",
632
+ h: "100%",
633
+ children: /* @__PURE__ */ jsx(ThemeIcon, {
634
+ w: 150,
635
+ h: 150,
636
+ variant: "transparent",
637
+ color: "gray",
638
+ children: /* @__PURE__ */ jsx(EyeOffIcon, {
639
+ width: 150,
640
+ height: 150
641
+ })
642
+ })
643
+ });
644
+ return /* @__PURE__ */ jsx(Visualization, {
645
+ visualization: data.visualization,
646
+ isPreview
647
+ });
648
+ };
649
+
650
+ //#endregion
651
+ //#region src/components/ResourceModal/ResourceModal.tsx
652
+ const ResourceModal = ({ id, onClose }) => {
653
+ const { data } = useResource(id, { query: { enabled: !!id } });
654
+ return data?.resource.id && /* @__PURE__ */ jsx(Modal, {
655
+ opened: !!data,
656
+ onClose,
657
+ fullScreen: true,
658
+ styles: { body: {
659
+ height: "100%",
660
+ overflow: "hidden"
661
+ } },
662
+ withCloseButton: false,
663
+ transitionProps: {
664
+ transition: "fade",
665
+ duration: 200
666
+ },
667
+ pos: "relative",
668
+ children: /* @__PURE__ */ jsxs(Stack, {
669
+ w: "100%",
670
+ h: "100%",
671
+ children: [/* @__PURE__ */ jsxs(Group, {
672
+ justify: "space-between",
673
+ children: [/* @__PURE__ */ jsx(Title, {
674
+ size: "h5",
675
+ children: data.resource.name
676
+ }), /* @__PURE__ */ jsx(ActionIcon, {
677
+ variant: "subtle",
678
+ color: "red",
679
+ onClick: onClose,
680
+ children: /* @__PURE__ */ jsx(XIcon, {})
681
+ })]
682
+ }), /* @__PURE__ */ jsx(Box, {
683
+ w: "100%",
684
+ h: "100%",
685
+ flex: 1,
686
+ mih: 0,
687
+ children: /* @__PURE__ */ jsx(ResourceViewer, { id: data.resource.id })
688
+ })]
689
+ })
690
+ });
691
+ };
692
+
693
+ //#endregion
694
+ export { ResourceModal, ResourceViewer, Visualization };
package/package.json ADDED
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@ocelescope/resources",
3
+ "type": "module",
4
+ "version": "0.0.0",
5
+ "description": "Ocelescope resources: helpers and UI components for rendering and exporting data.",
6
+ "author": "Görkem-Emre Öztürk <goerkem.oeztuerk@rwth-aachen.de>",
7
+ "license": "MIT",
8
+ "homepage": "https://www.ocelescope.org",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/promi4s/ocelescope.git"
12
+ },
13
+ "bugs": {
14
+ "url": "git+https://github.com/promi4s/ocelescope.git"
15
+ },
16
+ "exports": {
17
+ ".": "./dist/index.js",
18
+ "./package.json": "./package.json"
19
+ },
20
+ "main": "./dist/index.js",
21
+ "module": "./dist/index.js",
22
+ "types": "./dist/index.d.ts",
23
+ "files": [
24
+ "dist"
25
+ ],
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "peerDependencies": {
30
+ "@hpcc-js/wasm-graphviz": "^1.21.0",
31
+ "@mantine/core": "^8.3.14",
32
+ "cytoscape": "^3.33.1",
33
+ "dayjs": "^1.11.19",
34
+ "file-saver": "^2.0.5",
35
+ "lodash": "^4.17.23",
36
+ "lucide-react": "^0.563.0",
37
+ "mantine-datatable": "^8.3.13",
38
+ "next": "16.1.6",
39
+ "react": "^19.2.0",
40
+ "react-cytoscapejs": "^2.0.0",
41
+ "react-dom": "^19.2.0",
42
+ "react-plotly.js": "^2.6.0",
43
+ "react-zoom-pan-pinch": "^3.7.0",
44
+ "@floating-ui/react": "^0.27.18",
45
+ "@ocelescope/api-base": "0.0.0"
46
+ },
47
+ "devDependencies": {
48
+ "@tsconfig/strictest": "^2.0.8",
49
+ "@types/file-saver": "^2.0.7",
50
+ "@types/lodash": "^4.17.23",
51
+ "@types/node": "^25.0.3",
52
+ "@types/react": "^19.2.7",
53
+ "@types/react-cytoscapejs": "^1.2.6",
54
+ "@types/react-dom": "^19.2.3",
55
+ "@types/react-plotly.js": "^2.6.4",
56
+ "bumpp": "^10.3.2",
57
+ "tsdown": "^0.18.1",
58
+ "typescript": "^5.9.3"
59
+ },
60
+ "scripts": {
61
+ "build": "tsdown",
62
+ "dev": "tsdown --watch",
63
+ "typecheck": "tsc --noEmit",
64
+ "release": "bumpp && pnpm publish"
65
+ }
66
+ }