@ocelescope/resources 0.11.0 → 0.12.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/dist/index.d.ts +19 -7
- package/dist/index.js +436 -92
- package/package.json +3 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,31 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { MultiSelectProps, SelectProps } from "@mantine/core";
|
|
2
|
+
import { resourceVisualization } from "@ocelescope/api-base";
|
|
3
|
+
import { ReactNode } from "react";
|
|
4
|
+
type Controlled<P extends SelectProps | MultiSelectProps> = Omit<P, "defaultValue"> & Required<Pick<P, "value" | "onChange">>;
|
|
5
|
+
type ActionSelectProps = {
|
|
6
|
+
actions?: ReactNode;
|
|
7
|
+
} & ((Controlled<SelectProps> & {
|
|
8
|
+
multiple?: false;
|
|
9
|
+
}) | (Controlled<MultiSelectProps> & {
|
|
10
|
+
multiple: true;
|
|
11
|
+
}));
|
|
12
|
+
export declare const ActionSelect: React.FC<ActionSelectProps>;
|
|
4
13
|
export declare const ResourceModal: React.FC<{
|
|
5
14
|
id: string;
|
|
6
15
|
onClose: () => void;
|
|
7
16
|
}>;
|
|
8
|
-
|
|
17
|
+
type ResourceSelectProps = ActionSelectProps & {
|
|
9
18
|
type?: string;
|
|
10
|
-
|
|
19
|
+
withUpload?: boolean;
|
|
20
|
+
withView?: boolean;
|
|
21
|
+
};
|
|
22
|
+
export declare const ResourceSelect: React.FC<ResourceSelectProps>;
|
|
11
23
|
type RequireType<T> = T extends {
|
|
12
24
|
type?: infer L;
|
|
13
25
|
} ? Omit<T, "type"> & {
|
|
14
26
|
type: L;
|
|
15
27
|
} : T;
|
|
16
|
-
type VisualizationsType = RequireType<NonNullable<
|
|
28
|
+
type VisualizationsType = RequireType<NonNullable<Awaited<ReturnType<typeof resourceVisualization>>>>;
|
|
17
29
|
type VisualizationsTypes = NonNullable<VisualizationsType["type"]>;
|
|
18
30
|
type VisualizationByType<T extends VisualizationsTypes> = Extract<VisualizationsType, {
|
|
19
31
|
type: T;
|
|
@@ -33,4 +45,4 @@ export declare const ResourceViewer: React.FC<{
|
|
|
33
45
|
id: string;
|
|
34
46
|
isPreview?: boolean;
|
|
35
47
|
}>;
|
|
36
|
-
export type { VisualizationsType };
|
|
48
|
+
export type { ActionSelectProps, ResourceSelectProps, VisualizationsType };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,13 @@
|
|
|
1
|
-
import { ActionIcon, Alert, Badge, Box, Button, ButtonGroup, Code, Group, LoadingOverlay, Menu, Modal, Popover, ScrollArea, Select, Stack,
|
|
2
|
-
import { useResource, useResources } from "@ocelescope/api-base";
|
|
3
|
-
import { DownloadIcon, EyeOffIcon, Maximize2Icon, MaximizeIcon, MinusIcon, PlusIcon, XIcon } from "lucide-react";
|
|
4
|
-
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
5
|
-
import dynamic from "next/dynamic.js";
|
|
1
|
+
import { ActionIcon, Alert, Autocomplete, Badge, Box, Button, ButtonGroup, Center, Code, FileButton, Group, LoadingOverlay, Menu, Modal, MultiSelect, Popover, ScrollArea, Scroller, Select, Stack, Tabs, Text, Title, Tooltip } from "@mantine/core";
|
|
6
2
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
3
|
+
import { useResource, useResourceTableColumnValues, useResourceTableRows, useResourceTables, useResourceVisualization, useResources, useUpload } from "@ocelescope/api-base";
|
|
4
|
+
import { DownloadIcon, EyeIcon, EyeOffIcon, Maximize2Icon, MaximizeIcon, MinusIcon, PlusIcon, SearchIcon, UploadIcon, XIcon } from "lucide-react";
|
|
5
|
+
import { useDebouncedValue, useDisclosure } from "@mantine/hooks";
|
|
6
|
+
import { DataTable } from "mantine-datatable";
|
|
7
|
+
import { memo, useCallback, useEffect, useEffectEvent, useMemo, useRef, useState } from "react";
|
|
8
|
+
import dayjs from "dayjs";
|
|
9
|
+
import isBetween from "dayjs/plugin/isBetween.js";
|
|
10
|
+
import dynamic from "next/dynamic.js";
|
|
7
11
|
import { Graphviz } from "@hpcc-js/wasm-graphviz";
|
|
8
12
|
import FileSaver from "file-saver";
|
|
9
13
|
import { TransformComponent, TransformWrapper } from "react-zoom-pan-pinch";
|
|
@@ -11,9 +15,296 @@ import { Background, BaseEdge, ControlButton, Controls, EdgeLabelRenderer, Handl
|
|
|
11
15
|
import { toPng, toSvg } from "html-to-image";
|
|
12
16
|
import { jsPDF } from "jspdf";
|
|
13
17
|
import ELK from "elkjs/lib/elk.bundled.js";
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
// src/components/ActionSelect/ActionSelect.tsx
|
|
19
|
+
const attachedStyles = { input: {
|
|
20
|
+
borderTopRightRadius: 0,
|
|
21
|
+
borderBottomRightRadius: 0
|
|
22
|
+
} };
|
|
23
|
+
const renderSelect = (props) => {
|
|
24
|
+
const styles = props.actions ? attachedStyles : void 0;
|
|
25
|
+
if (props.multiple) {
|
|
26
|
+
const { multiple: _, actions: __, ...rest } = props;
|
|
27
|
+
return /* @__PURE__ */ jsx(MultiSelect, {
|
|
28
|
+
flex: 1,
|
|
29
|
+
miw: 0,
|
|
30
|
+
styles,
|
|
31
|
+
...rest
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
const { multiple: _, actions: __, ...rest } = props;
|
|
35
|
+
return /* @__PURE__ */ jsx(Select, {
|
|
36
|
+
flex: 1,
|
|
37
|
+
miw: 0,
|
|
38
|
+
styles,
|
|
39
|
+
...rest
|
|
40
|
+
});
|
|
41
|
+
};
|
|
42
|
+
const ActionSelect = (props) => /* @__PURE__ */ jsxs(Group, {
|
|
43
|
+
wrap: "nowrap",
|
|
44
|
+
gap: 0,
|
|
45
|
+
align: "flex-end",
|
|
46
|
+
children: [renderSelect(props), props.actions && /* @__PURE__ */ jsxs(ActionIcon.Group, { children: [/* @__PURE__ */ jsx(ActionIcon.GroupSection, { hidden: true }), props.actions] })]
|
|
47
|
+
});
|
|
48
|
+
// src/util/dayjs.ts
|
|
49
|
+
dayjs.extend(isBetween);
|
|
50
|
+
var dayjs_default = dayjs;
|
|
51
|
+
const formatDateTime = (dateString) => dayjs(dateString).format("YYYY-MM-DD HH:mm");
|
|
52
|
+
const formatDate = (dateString) => dayjs(dateString).format("YYYY-MM-DD");
|
|
53
|
+
// src/components/ResourceView/Viewers/Table.tsx
|
|
54
|
+
const formatCell = (value, dataType) => {
|
|
55
|
+
if (value == null) return "";
|
|
56
|
+
switch (dataType) {
|
|
57
|
+
case "boolean": return value ? "✅" : "❌";
|
|
58
|
+
case "date": return dayjs_default(value).isValid() ? formatDate(value) : String(value);
|
|
59
|
+
case "datetime": return dayjs_default(value).isValid() ? formatDateTime(value) : String(value);
|
|
60
|
+
default: return String(value);
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
const PAGE_SIZES$1 = [
|
|
64
|
+
20,
|
|
65
|
+
50,
|
|
66
|
+
75
|
|
67
|
+
];
|
|
68
|
+
const TableView = ({ visualization }) => {
|
|
69
|
+
const [pageSize, setPageSize] = useState(PAGE_SIZES$1[0]);
|
|
70
|
+
const columns = useMemo(() => visualization.columns.map((col) => ({
|
|
71
|
+
accessor: col.id,
|
|
72
|
+
title: col.label ?? col.id,
|
|
73
|
+
render: (record) => formatCell(record[col.id], col.data_type)
|
|
74
|
+
})), [visualization.columns]);
|
|
75
|
+
const [page, setPage] = useState(1);
|
|
76
|
+
const [records, setRecords] = useState(visualization.rows.slice(0, pageSize));
|
|
77
|
+
useEffect(() => {
|
|
78
|
+
const from = (page - 1) * pageSize;
|
|
79
|
+
const to = from + pageSize;
|
|
80
|
+
setRecords(visualization.rows.slice(from, to));
|
|
81
|
+
}, [
|
|
82
|
+
page,
|
|
83
|
+
pageSize,
|
|
84
|
+
visualization
|
|
85
|
+
]);
|
|
86
|
+
const paginationProps = visualization.rows.length > PAGE_SIZES$1[0] ? {
|
|
87
|
+
totalRecords: visualization.rows.length,
|
|
88
|
+
recordsPerPage: pageSize,
|
|
89
|
+
onPageChange: (p) => setPage(p),
|
|
90
|
+
page,
|
|
91
|
+
recordsPerPageOptions: [...PAGE_SIZES$1],
|
|
92
|
+
onRecordsPerPageChange: (newPageSize) => {
|
|
93
|
+
setPageSize(newPageSize);
|
|
94
|
+
setPage(1);
|
|
95
|
+
}
|
|
96
|
+
} : {};
|
|
97
|
+
return /* @__PURE__ */ jsx(DataTable, {
|
|
98
|
+
records,
|
|
99
|
+
columns,
|
|
100
|
+
withTableBorder: true,
|
|
101
|
+
withColumnBorders: true,
|
|
102
|
+
withRowBorders: true,
|
|
103
|
+
...paginationProps
|
|
104
|
+
});
|
|
105
|
+
};
|
|
106
|
+
// src/components/ResourceView/TabularView.tsx
|
|
107
|
+
const PAGE_SIZES = [
|
|
108
|
+
20,
|
|
109
|
+
50,
|
|
110
|
+
100
|
|
111
|
+
];
|
|
112
|
+
const UNSORTED = {
|
|
113
|
+
columnAccessor: "",
|
|
114
|
+
direction: "asc"
|
|
115
|
+
};
|
|
116
|
+
const VALUE_LIMIT = 50;
|
|
117
|
+
const ColumnFilter = ({ resourceId, tableName, columnId, selected, onChange }) => {
|
|
118
|
+
const [search, setSearch] = useState("");
|
|
119
|
+
const [debouncedSearch] = useDebouncedValue(search, 250);
|
|
120
|
+
const { data, isFetching } = useResourceTableColumnValues(resourceId, tableName, columnId, {
|
|
121
|
+
size: VALUE_LIMIT,
|
|
122
|
+
...debouncedSearch && { search: debouncedSearch }
|
|
123
|
+
}, { query: { placeholderData: (previous) => previous } });
|
|
124
|
+
const options = useMemo(() => [.../* @__PURE__ */ new Set([...selected, ...data?.response ?? []])], [selected, data?.response]);
|
|
125
|
+
const total = data?.total_items ?? 0;
|
|
126
|
+
return /* @__PURE__ */ jsx(MultiSelect, {
|
|
127
|
+
w: 280,
|
|
128
|
+
searchable: true,
|
|
129
|
+
clearable: true,
|
|
130
|
+
data: options,
|
|
131
|
+
value: selected,
|
|
132
|
+
onChange,
|
|
133
|
+
searchValue: search,
|
|
134
|
+
onSearchChange: setSearch,
|
|
135
|
+
filter: ({ options: unfiltered }) => unfiltered,
|
|
136
|
+
placeholder: `Search ${columnId}`,
|
|
137
|
+
nothingFoundMessage: isFetching ? "Loading..." : "No values found",
|
|
138
|
+
description: total > VALUE_LIMIT ? `Showing ${VALUE_LIMIT} of ${total} values, type to narrow down` : void 0,
|
|
139
|
+
comboboxProps: { withinPortal: false }
|
|
140
|
+
});
|
|
141
|
+
};
|
|
142
|
+
const TableRows = ({ resourceId, table }) => {
|
|
143
|
+
const [page, setPage] = useState(1);
|
|
144
|
+
const [pageSize, setPageSize] = useState(PAGE_SIZES[0]);
|
|
145
|
+
const [sortStatus, setSortStatus] = useState(UNSORTED);
|
|
146
|
+
const [filters, setFilters] = useState({});
|
|
147
|
+
const hasFilters = Object.values(filters).some((values) => values.length > 0);
|
|
148
|
+
const { data, isError, isFetching } = useResourceTableRows(resourceId, table.name, {
|
|
149
|
+
page,
|
|
150
|
+
size: pageSize,
|
|
151
|
+
...sortStatus.columnAccessor && {
|
|
152
|
+
sort_by: sortStatus.columnAccessor,
|
|
153
|
+
descending: sortStatus.direction === "desc"
|
|
154
|
+
},
|
|
155
|
+
...hasFilters && { filters: JSON.stringify(filters) }
|
|
156
|
+
}, { query: { placeholderData: (previous) => previous } });
|
|
157
|
+
const columns = useMemo(() => table.columns.map((column) => ({
|
|
158
|
+
accessor: column.id,
|
|
159
|
+
title: column.id,
|
|
160
|
+
sortable: true,
|
|
161
|
+
render: (row) => formatCell(row[column.id], column.data_type),
|
|
162
|
+
filtering: (filters[column.id] ?? []).length > 0,
|
|
163
|
+
filter: /* @__PURE__ */ jsx(ColumnFilter, {
|
|
164
|
+
resourceId,
|
|
165
|
+
tableName: table.name,
|
|
166
|
+
columnId: column.id,
|
|
167
|
+
selected: filters[column.id] ?? [],
|
|
168
|
+
onChange: (values) => {
|
|
169
|
+
setFilters((current) => ({
|
|
170
|
+
...current,
|
|
171
|
+
[column.id]: values
|
|
172
|
+
}));
|
|
173
|
+
setPage(1);
|
|
174
|
+
}
|
|
175
|
+
})
|
|
176
|
+
})), [
|
|
177
|
+
resourceId,
|
|
178
|
+
table.name,
|
|
179
|
+
table.columns,
|
|
180
|
+
filters
|
|
181
|
+
]);
|
|
182
|
+
const records = useMemo(() => (data?.response ?? []).map((row, index) => ({
|
|
183
|
+
...row,
|
|
184
|
+
rowKey: (page - 1) * pageSize + index
|
|
185
|
+
})), [
|
|
186
|
+
data?.response,
|
|
187
|
+
page,
|
|
188
|
+
pageSize
|
|
189
|
+
]);
|
|
190
|
+
if (isError) return /* @__PURE__ */ jsx(Center, {
|
|
191
|
+
h: "100%",
|
|
192
|
+
p: "md",
|
|
193
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
194
|
+
c: "dimmed",
|
|
195
|
+
children: "Could not load the table"
|
|
196
|
+
})
|
|
197
|
+
});
|
|
198
|
+
return /* @__PURE__ */ jsx(DataTable, {
|
|
199
|
+
height: "100%",
|
|
200
|
+
idAccessor: "rowKey",
|
|
201
|
+
records,
|
|
202
|
+
columns,
|
|
203
|
+
fetching: isFetching,
|
|
204
|
+
withTableBorder: true,
|
|
205
|
+
withColumnBorders: true,
|
|
206
|
+
sortStatus,
|
|
207
|
+
onSortStatusChange: (status) => {
|
|
208
|
+
setSortStatus(status);
|
|
209
|
+
setPage(1);
|
|
210
|
+
},
|
|
211
|
+
totalRecords: data?.total_items ?? 0,
|
|
212
|
+
recordsPerPage: pageSize,
|
|
213
|
+
page,
|
|
214
|
+
onPageChange: setPage,
|
|
215
|
+
recordsPerPageOptions: PAGE_SIZES,
|
|
216
|
+
onRecordsPerPageChange: (size) => {
|
|
217
|
+
setPageSize(size);
|
|
218
|
+
setPage(1);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
};
|
|
222
|
+
const TableTabs = ({ resourceId, tables }) => {
|
|
223
|
+
const [activeTable, setActiveTable] = useState(tables[0]?.name ?? null);
|
|
224
|
+
const [searchOpened, { toggle: toggleSearch, close: closeSearch }] = useDisclosure(false);
|
|
225
|
+
return /* @__PURE__ */ jsxs(Tabs, {
|
|
226
|
+
keepMounted: false,
|
|
227
|
+
h: "100%",
|
|
228
|
+
style: {
|
|
229
|
+
display: "flex",
|
|
230
|
+
flexDirection: "column"
|
|
231
|
+
},
|
|
232
|
+
value: activeTable,
|
|
233
|
+
onChange: setActiveTable,
|
|
234
|
+
children: [tables.length > 1 && /* @__PURE__ */ jsxs(Group, {
|
|
235
|
+
wrap: "nowrap",
|
|
236
|
+
gap: "0",
|
|
237
|
+
style: { flexShrink: 0 },
|
|
238
|
+
children: [/* @__PURE__ */ jsxs(Popover, {
|
|
239
|
+
width: 300,
|
|
240
|
+
position: "bottom-start",
|
|
241
|
+
withArrow: true,
|
|
242
|
+
shadow: "md",
|
|
243
|
+
opened: searchOpened,
|
|
244
|
+
onChange: closeSearch,
|
|
245
|
+
trapFocus: true,
|
|
246
|
+
returnFocus: true,
|
|
247
|
+
children: [/* @__PURE__ */ jsx(Popover.Target, { children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
248
|
+
size: "lg",
|
|
249
|
+
h: "100%",
|
|
250
|
+
onClick: toggleSearch,
|
|
251
|
+
"aria-label": "Search tables",
|
|
252
|
+
bdrs: 0,
|
|
253
|
+
children: /* @__PURE__ */ jsx(SearchIcon, { size: 16 })
|
|
254
|
+
}) }), /* @__PURE__ */ jsx(Popover.Dropdown, { children: /* @__PURE__ */ jsx(Autocomplete, {
|
|
255
|
+
data: tables.map(({ name }) => name),
|
|
256
|
+
"aria-label": "Table",
|
|
257
|
+
placeholder: "Search tables",
|
|
258
|
+
selectFirstOptionOnChange: true,
|
|
259
|
+
comboboxProps: { withinPortal: false },
|
|
260
|
+
onOptionSubmit: (name) => {
|
|
261
|
+
setActiveTable(name);
|
|
262
|
+
closeSearch();
|
|
263
|
+
}
|
|
264
|
+
}) })]
|
|
265
|
+
}), /* @__PURE__ */ jsx(Tabs.List, {
|
|
266
|
+
flex: 1,
|
|
267
|
+
miw: 0,
|
|
268
|
+
children: /* @__PURE__ */ jsx(Scroller, { children: tables.map(({ name }) => /* @__PURE__ */ jsx(Tabs.Tab, {
|
|
269
|
+
value: name,
|
|
270
|
+
children: name
|
|
271
|
+
}, name)) })
|
|
272
|
+
})]
|
|
273
|
+
}), tables.map((table) => /* @__PURE__ */ jsx(Tabs.Panel, {
|
|
274
|
+
value: table.name,
|
|
275
|
+
flex: 1,
|
|
276
|
+
mih: 0,
|
|
277
|
+
children: /* @__PURE__ */ jsx(TableRows, {
|
|
278
|
+
resourceId,
|
|
279
|
+
table
|
|
280
|
+
})
|
|
281
|
+
}, table.name))]
|
|
282
|
+
});
|
|
283
|
+
};
|
|
284
|
+
const TabularView = ({ resourceId }) => {
|
|
285
|
+
const { data: tables, isError, isPending } = useResourceTables(resourceId);
|
|
286
|
+
if (isError) return /* @__PURE__ */ jsx(Center, {
|
|
287
|
+
h: "100%",
|
|
288
|
+
p: "md",
|
|
289
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
290
|
+
c: "dimmed",
|
|
291
|
+
children: "Could not load the tables"
|
|
292
|
+
})
|
|
293
|
+
});
|
|
294
|
+
if (isPending) return /* @__PURE__ */ jsx(LoadingOverlay, { visible: true });
|
|
295
|
+
if (tables.length === 0) return /* @__PURE__ */ jsx(Center, {
|
|
296
|
+
h: "100%",
|
|
297
|
+
p: "md",
|
|
298
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
299
|
+
c: "dimmed",
|
|
300
|
+
children: "This resource has no tables"
|
|
301
|
+
})
|
|
302
|
+
});
|
|
303
|
+
return /* @__PURE__ */ jsx(TableTabs, {
|
|
304
|
+
resourceId,
|
|
305
|
+
tables
|
|
306
|
+
});
|
|
307
|
+
};
|
|
17
308
|
// src/components/ResourceView/Viewers/R4pm.tsx
|
|
18
309
|
const r4pmViewer = (pick) => dynamic(async () => {
|
|
19
310
|
const [components, { wasmViz }] = await Promise.all([import("@r4pm/components"), import("@r4pm/components/viz/wasm")]);
|
|
@@ -1788,64 +2079,6 @@ const PlotlyViewer = ({ visualization, isPreview }) => {
|
|
|
1788
2079
|
}
|
|
1789
2080
|
});
|
|
1790
2081
|
};
|
|
1791
|
-
// src/util/dayjs.ts
|
|
1792
|
-
dayjs.extend(isBetween);
|
|
1793
|
-
var dayjs_default = dayjs;
|
|
1794
|
-
const formatDateTime = (dateString) => dayjs(dateString).format("YYYY-MM-DD HH:mm");
|
|
1795
|
-
const formatDate = (dateString) => dayjs(dateString).format("YYYY-MM-DD");
|
|
1796
|
-
// src/components/ResourceView/Viewers/Table.tsx
|
|
1797
|
-
const formatCell = (value, dataType) => {
|
|
1798
|
-
if (value == null) return "";
|
|
1799
|
-
switch (dataType) {
|
|
1800
|
-
case "boolean": return value ? "✅" : "❌";
|
|
1801
|
-
case "date": return dayjs_default(value).isValid() ? formatDate(value) : String(value);
|
|
1802
|
-
case "datetime": return dayjs_default(value).isValid() ? formatDateTime(value) : String(value);
|
|
1803
|
-
default: return String(value);
|
|
1804
|
-
}
|
|
1805
|
-
};
|
|
1806
|
-
const PAGE_SIZES = [
|
|
1807
|
-
20,
|
|
1808
|
-
50,
|
|
1809
|
-
75
|
|
1810
|
-
];
|
|
1811
|
-
const TableView = ({ visualization }) => {
|
|
1812
|
-
const [pageSize, setPageSize] = useState(PAGE_SIZES[0]);
|
|
1813
|
-
const columns = useMemo(() => visualization.columns.map((col) => ({
|
|
1814
|
-
accessor: col.id,
|
|
1815
|
-
title: col.label ?? col.id,
|
|
1816
|
-
render: (record) => formatCell(record[col.id], col.data_type)
|
|
1817
|
-
})), [visualization.columns]);
|
|
1818
|
-
const [page, setPage] = useState(1);
|
|
1819
|
-
const [records, setRecords] = useState(visualization.rows.slice(0, pageSize));
|
|
1820
|
-
useEffect(() => {
|
|
1821
|
-
const from = (page - 1) * pageSize;
|
|
1822
|
-
const to = from + pageSize;
|
|
1823
|
-
setRecords(visualization.rows.slice(from, to));
|
|
1824
|
-
}, [
|
|
1825
|
-
page,
|
|
1826
|
-
pageSize,
|
|
1827
|
-
visualization
|
|
1828
|
-
]);
|
|
1829
|
-
const paginationProps = visualization.rows.length > PAGE_SIZES[0] ? {
|
|
1830
|
-
totalRecords: visualization.rows.length,
|
|
1831
|
-
recordsPerPage: pageSize,
|
|
1832
|
-
onPageChange: (p) => setPage(p),
|
|
1833
|
-
page,
|
|
1834
|
-
recordsPerPageOptions: [...PAGE_SIZES],
|
|
1835
|
-
onRecordsPerPageChange: (newPageSize) => {
|
|
1836
|
-
setPageSize(newPageSize);
|
|
1837
|
-
setPage(1);
|
|
1838
|
-
}
|
|
1839
|
-
} : {};
|
|
1840
|
-
return /* @__PURE__ */ jsx(DataTable, {
|
|
1841
|
-
records,
|
|
1842
|
-
columns,
|
|
1843
|
-
withTableBorder: true,
|
|
1844
|
-
withColumnBorders: true,
|
|
1845
|
-
withRowBorders: true,
|
|
1846
|
-
...paginationProps
|
|
1847
|
-
});
|
|
1848
|
-
};
|
|
1849
2082
|
// src/components/ResourceView/index.tsx
|
|
1850
2083
|
const visualizationMap = {
|
|
1851
2084
|
table: TableView,
|
|
@@ -1866,33 +2099,72 @@ const Visualization = ({ visualization, isPreview = false, menuItems }) => {
|
|
|
1866
2099
|
...menuItems && { menuItems }
|
|
1867
2100
|
});
|
|
1868
2101
|
};
|
|
1869
|
-
const
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
2102
|
+
const LoadError = () => /* @__PURE__ */ jsx(Center, {
|
|
2103
|
+
h: "100%",
|
|
2104
|
+
p: "md",
|
|
2105
|
+
children: /* @__PURE__ */ jsx(Text, {
|
|
2106
|
+
c: "dimmed",
|
|
2107
|
+
children: "Could not load the visualization"
|
|
2108
|
+
})
|
|
2109
|
+
});
|
|
2110
|
+
const NoVisualization = ({ resourceTypeLabel }) => /* @__PURE__ */ jsx(Center, {
|
|
2111
|
+
h: "100%",
|
|
2112
|
+
p: "md",
|
|
2113
|
+
children: /* @__PURE__ */ jsxs(Stack, {
|
|
1874
2114
|
align: "center",
|
|
1875
|
-
|
|
1876
|
-
children:
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2115
|
+
gap: "xs",
|
|
2116
|
+
children: [
|
|
2117
|
+
/* @__PURE__ */ jsx(EyeOffIcon, {
|
|
2118
|
+
size: 64,
|
|
2119
|
+
strokeWidth: 1.5,
|
|
2120
|
+
color: "var(--mantine-color-dimmed)"
|
|
2121
|
+
}),
|
|
2122
|
+
/* @__PURE__ */ jsx(Text, {
|
|
2123
|
+
fw: 500,
|
|
2124
|
+
children: "No visualization available"
|
|
2125
|
+
}),
|
|
2126
|
+
/* @__PURE__ */ jsxs(Group, {
|
|
2127
|
+
gap: 6,
|
|
2128
|
+
justify: "center",
|
|
2129
|
+
children: [/* @__PURE__ */ jsx(Badge, {
|
|
2130
|
+
size: "sm",
|
|
2131
|
+
color: "gray",
|
|
2132
|
+
children: resourceTypeLabel
|
|
2133
|
+
}), /* @__PURE__ */ jsx(Text, {
|
|
2134
|
+
size: "sm",
|
|
2135
|
+
c: "dimmed",
|
|
2136
|
+
children: "does not provide a visualization"
|
|
2137
|
+
})]
|
|
1884
2138
|
})
|
|
1885
|
-
|
|
1886
|
-
})
|
|
2139
|
+
]
|
|
2140
|
+
})
|
|
2141
|
+
});
|
|
2142
|
+
const ResourceVisualization = ({ id, resourceTypeLabel, isPreview }) => {
|
|
2143
|
+
const { data, isError, isPending } = useResourceVisualization(id);
|
|
2144
|
+
if (isError) return /* @__PURE__ */ jsx(LoadError, {});
|
|
2145
|
+
if (isPending) return /* @__PURE__ */ jsx(LoadingOverlay, { visible: true });
|
|
2146
|
+
if (!data) return /* @__PURE__ */ jsx(NoVisualization, { resourceTypeLabel });
|
|
1887
2147
|
return /* @__PURE__ */ jsx(Visualization, {
|
|
1888
|
-
visualization: data
|
|
2148
|
+
visualization: data,
|
|
1889
2149
|
isPreview
|
|
1890
2150
|
});
|
|
1891
2151
|
};
|
|
2152
|
+
const ResourceViewer = ({ id, isPreview = false }) => {
|
|
2153
|
+
const { data: resource, isError, isPending } = useResource(id);
|
|
2154
|
+
if (isError) return /* @__PURE__ */ jsx(LoadError, {});
|
|
2155
|
+
if (isPending) return /* @__PURE__ */ jsx(LoadingOverlay, { visible: true });
|
|
2156
|
+
if (resource.has_visualization) return /* @__PURE__ */ jsx(ResourceVisualization, {
|
|
2157
|
+
id,
|
|
2158
|
+
resourceTypeLabel: resource.resource_type_label,
|
|
2159
|
+
isPreview
|
|
2160
|
+
});
|
|
2161
|
+
if (resource.kind === "tabular") return /* @__PURE__ */ jsx(TabularView, { resourceId: id });
|
|
2162
|
+
return /* @__PURE__ */ jsx(NoVisualization, { resourceTypeLabel: resource.resource_type_label });
|
|
2163
|
+
};
|
|
1892
2164
|
// src/components/ResourceModal/ResourceModal.tsx
|
|
1893
2165
|
const ResourceModal = ({ id, onClose }) => {
|
|
1894
2166
|
const { data } = useResource(id, { query: { enabled: !!id } });
|
|
1895
|
-
return data?.
|
|
2167
|
+
return data?.id && /* @__PURE__ */ jsx(Modal, {
|
|
1896
2168
|
opened: !!data,
|
|
1897
2169
|
onClose,
|
|
1898
2170
|
fullScreen: true,
|
|
@@ -1913,7 +2185,7 @@ const ResourceModal = ({ id, onClose }) => {
|
|
|
1913
2185
|
justify: "space-between",
|
|
1914
2186
|
children: [/* @__PURE__ */ jsx(Title, {
|
|
1915
2187
|
size: "h5",
|
|
1916
|
-
children: data.
|
|
2188
|
+
children: data.name
|
|
1917
2189
|
}), /* @__PURE__ */ jsx(ActionIcon, {
|
|
1918
2190
|
variant: "subtle",
|
|
1919
2191
|
color: "red",
|
|
@@ -1925,20 +2197,92 @@ const ResourceModal = ({ id, onClose }) => {
|
|
|
1925
2197
|
h: "100%",
|
|
1926
2198
|
flex: 1,
|
|
1927
2199
|
mih: 0,
|
|
1928
|
-
children: /* @__PURE__ */ jsx(ResourceViewer, { id: data.
|
|
2200
|
+
children: /* @__PURE__ */ jsx(ResourceViewer, { id: data.id })
|
|
1929
2201
|
})]
|
|
1930
2202
|
})
|
|
1931
2203
|
});
|
|
1932
2204
|
};
|
|
1933
2205
|
// src/components/ResourceSelect/ResourceSelect.tsx
|
|
1934
|
-
const
|
|
2206
|
+
const useResourceOptions = (type, onUploaded) => {
|
|
1935
2207
|
const { data: resources = [] } = useResources({ schema_hash: type ?? null });
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
|
|
2208
|
+
const [pendingTaskIds, setPendingTaskIds] = useState([]);
|
|
2209
|
+
const options = useMemo(() => resources.map(({ name, id }) => ({
|
|
2210
|
+
value: id,
|
|
2211
|
+
label: name
|
|
2212
|
+
})), [resources]);
|
|
2213
|
+
const handleUploaded = useEffectEvent(onUploaded);
|
|
2214
|
+
useEffect(() => {
|
|
2215
|
+
const uploaded = resources.filter(({ source_id }) => source_id && pendingTaskIds.includes(source_id));
|
|
2216
|
+
if (!uploaded.length) return;
|
|
2217
|
+
setPendingTaskIds((taskIds) => taskIds.filter((taskId) => !uploaded.some(({ source_id }) => source_id === taskId)));
|
|
2218
|
+
handleUploaded(uploaded.map(({ id, name }) => ({
|
|
1939
2219
|
value: id,
|
|
1940
2220
|
label: name
|
|
1941
|
-
}))
|
|
2221
|
+
})));
|
|
2222
|
+
}, [resources, pendingTaskIds]);
|
|
2223
|
+
const trackUpload = (taskIds) => setPendingTaskIds((pending) => [...pending, ...taskIds]);
|
|
2224
|
+
return {
|
|
2225
|
+
options,
|
|
2226
|
+
trackUpload
|
|
2227
|
+
};
|
|
2228
|
+
};
|
|
2229
|
+
const UploadButton = ({ disabled, onUpload }) => {
|
|
2230
|
+
const { mutate: upload, isPending } = useUpload({ mutation: { onSuccess: onUpload } });
|
|
2231
|
+
return /* @__PURE__ */ jsx(FileButton, {
|
|
2232
|
+
accept: ".ocelescope",
|
|
2233
|
+
multiple: true,
|
|
2234
|
+
onChange: (files) => {
|
|
2235
|
+
if (files.length) upload({ data: { files } });
|
|
2236
|
+
},
|
|
2237
|
+
children: (props) => /* @__PURE__ */ jsx(Tooltip, {
|
|
2238
|
+
label: "Upload resource",
|
|
2239
|
+
withArrow: true,
|
|
2240
|
+
children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
2241
|
+
...props,
|
|
2242
|
+
size: "input-sm",
|
|
2243
|
+
loading: isPending,
|
|
2244
|
+
disabled,
|
|
2245
|
+
"aria-label": "Upload resource",
|
|
2246
|
+
children: /* @__PURE__ */ jsx(UploadIcon, { size: 16 })
|
|
2247
|
+
})
|
|
2248
|
+
})
|
|
2249
|
+
});
|
|
2250
|
+
};
|
|
2251
|
+
const ViewButton = ({ resourceId, onView }) => /* @__PURE__ */ jsx(Tooltip, {
|
|
2252
|
+
label: resourceId ? "View resource" : "Select a resource to view it",
|
|
2253
|
+
withArrow: true,
|
|
2254
|
+
children: /* @__PURE__ */ jsx(ActionIcon, {
|
|
2255
|
+
variant: "default",
|
|
2256
|
+
size: "input-sm",
|
|
2257
|
+
"data-disabled": !resourceId || void 0,
|
|
2258
|
+
"aria-disabled": !resourceId,
|
|
2259
|
+
onClick: resourceId ? () => onView(resourceId) : void 0,
|
|
2260
|
+
"aria-label": "View resource",
|
|
2261
|
+
children: /* @__PURE__ */ jsx(EyeIcon, { size: 16 })
|
|
2262
|
+
})
|
|
2263
|
+
});
|
|
2264
|
+
const ResourceSelect = (props) => {
|
|
2265
|
+
const { type, withUpload = true, withView = true, ...selectProps } = props;
|
|
2266
|
+
const [viewedId, setViewedId] = useState();
|
|
2267
|
+
const { options, trackUpload } = useResourceOptions(type, (uploaded) => {
|
|
2268
|
+
if (props.multiple) props.onChange([.../* @__PURE__ */ new Set([...props.value, ...uploaded.map(({ value }) => value)])]);
|
|
2269
|
+
else if (uploaded[0]) props.onChange(uploaded[0].value, uploaded[0]);
|
|
1942
2270
|
});
|
|
2271
|
+
const viewId = props.multiple ? null : props.value;
|
|
2272
|
+
const showView = withView && !props.multiple;
|
|
2273
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ActionSelect, {
|
|
2274
|
+
...selectProps,
|
|
2275
|
+
data: options,
|
|
2276
|
+
actions: (showView || withUpload) && /* @__PURE__ */ jsxs(Fragment, { children: [showView && /* @__PURE__ */ jsx(ViewButton, {
|
|
2277
|
+
resourceId: viewId,
|
|
2278
|
+
onView: setViewedId
|
|
2279
|
+
}), withUpload && /* @__PURE__ */ jsx(UploadButton, {
|
|
2280
|
+
disabled: props.disabled,
|
|
2281
|
+
onUpload: trackUpload
|
|
2282
|
+
})] })
|
|
2283
|
+
}), viewedId && /* @__PURE__ */ jsx(ResourceModal, {
|
|
2284
|
+
id: viewedId,
|
|
2285
|
+
onClose: () => setViewedId(void 0)
|
|
2286
|
+
})] });
|
|
1943
2287
|
};
|
|
1944
|
-
export { ResourceModal, ResourceSelect, ResourceViewer, Visualization };
|
|
2288
|
+
export { ActionSelect, ResourceModal, ResourceSelect, ResourceViewer, Visualization };
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ocelescope/resources",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.12.0",
|
|
5
5
|
"description": "Ocelescope resources: helpers and UI components for rendering and exporting data.",
|
|
6
6
|
"author": "Görkem-Emre Öztürk <goerkem.oeztuerk@rwth-aachen.de>",
|
|
7
7
|
"license": "MIT",
|
|
@@ -30,7 +30,8 @@
|
|
|
30
30
|
"@floating-ui/react": "^0.27.18",
|
|
31
31
|
"@hpcc-js/wasm-graphviz": "^1.30.0",
|
|
32
32
|
"@mantine/core": "^9.7.4",
|
|
33
|
-
"@
|
|
33
|
+
"@mantine/hooks": "^9.7.4",
|
|
34
|
+
"@ocelescope/api-base": "^0.12.0",
|
|
34
35
|
"@r4pm/components": "^0.4.0",
|
|
35
36
|
"@xyflow/react": "^12.12.0",
|
|
36
37
|
"dayjs": "^1.11.23",
|