@logilab/sparqlexplorer 0.7.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.
Files changed (88) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +73 -0
  3. package/build/src/lib/App.d.ts +6 -0
  4. package/build/src/lib/components/ClassList.d.ts +9 -0
  5. package/build/src/lib/components/EndpointForm.d.ts +1 -0
  6. package/build/src/lib/components/GraphSelector.d.ts +1 -0
  7. package/build/src/lib/components/HomePage.d.ts +1 -0
  8. package/build/src/lib/components/SearchPage.d.ts +1 -0
  9. package/build/src/lib/components/UriPage.d.ts +1 -0
  10. package/build/src/lib/components/UtilsComponents.d.ts +11 -0
  11. package/build/src/lib/components/ViewSelector.d.ts +2 -0
  12. package/build/src/lib/components/Yasgui.d.ts +6 -0
  13. package/build/src/lib/components/YasrTableResults.d.ts +55 -0
  14. package/build/src/lib/components/layout/DrawerContent.d.ts +4 -0
  15. package/build/src/lib/components/layout/Footer.d.ts +1 -0
  16. package/build/src/lib/components/layout/Layout.d.ts +2 -0
  17. package/build/src/lib/components/layout/Navbar.d.ts +4 -0
  18. package/build/src/lib/components/uri/URIDefaultView.d.ts +6 -0
  19. package/build/src/lib/components/uri/URIWithSelectedView.d.ts +7 -0
  20. package/build/src/lib/context/AuthContext.d.ts +11 -0
  21. package/build/src/lib/context/ConfigContext.d.ts +15 -0
  22. package/build/src/lib/context/ViewsContext.d.ts +14 -0
  23. package/build/src/lib/hooks/useClasses.d.ts +6 -0
  24. package/build/src/lib/hooks/useGraphs.d.ts +8 -0
  25. package/build/src/lib/hooks/useNavigateWithParams.d.ts +5 -0
  26. package/build/src/lib/hooks/useParams.d.ts +1 -0
  27. package/build/src/lib/hooks/useURIData.d.ts +21 -0
  28. package/build/src/lib/hooks/useURILink.d.ts +1 -0
  29. package/build/src/lib/index.d.ts +8 -0
  30. package/build/src/lib/public-path.d.ts +1 -0
  31. package/build/src/lib/routes/Home.d.ts +1 -0
  32. package/build/src/lib/routes/Search.d.ts +1 -0
  33. package/build/src/lib/routes/Uri.d.ts +1 -0
  34. package/build/src/lib/routes/Yasgui.d.ts +1 -0
  35. package/build/src/lib/setupTests.d.ts +1 -0
  36. package/build/src/lib/utils/getIconFromURI.d.ts +3 -0
  37. package/build/src/lib/utils/utils.d.ts +24 -0
  38. package/build/src/lib/yasgui-utils/Storage.d.ts +16 -0
  39. package/build/src/lib/yasgui-utils/index.d.ts +16 -0
  40. package/build/static/js/lib.js +285 -0
  41. package/build/static/js/lib.js.LICENSE.txt +71 -0
  42. package/build/static/js/lib.js.map +1 -0
  43. package/package.json +73 -0
  44. package/src/app/index.css +23 -0
  45. package/src/app/index.tsx +28 -0
  46. package/src/app/templates/constants.js +1 -0
  47. package/src/app/templates/index.hbs +18 -0
  48. package/src/lib/App.css +83 -0
  49. package/src/lib/App.tsx +31 -0
  50. package/src/lib/components/ClassList.tsx +173 -0
  51. package/src/lib/components/EndpointForm.tsx +126 -0
  52. package/src/lib/components/GraphSelector.tsx +114 -0
  53. package/src/lib/components/HomePage.tsx +51 -0
  54. package/src/lib/components/SearchPage.tsx +211 -0
  55. package/src/lib/components/UriPage.tsx +158 -0
  56. package/src/lib/components/UtilsComponents.tsx +54 -0
  57. package/src/lib/components/ViewSelector.css +22 -0
  58. package/src/lib/components/ViewSelector.tsx +78 -0
  59. package/src/lib/components/Yasgui.tsx +127 -0
  60. package/src/lib/components/YasrTableResults.ts +529 -0
  61. package/src/lib/components/layout/DrawerContent.tsx +55 -0
  62. package/src/lib/components/layout/Footer.tsx +32 -0
  63. package/src/lib/components/layout/Layout.tsx +103 -0
  64. package/src/lib/components/layout/Navbar.tsx +231 -0
  65. package/src/lib/components/uri/URIDefaultView.tsx +392 -0
  66. package/src/lib/components/uri/URIWithSelectedView.tsx +31 -0
  67. package/src/lib/context/AuthContext.tsx +32 -0
  68. package/src/lib/context/ConfigContext.tsx +50 -0
  69. package/src/lib/context/ViewsContext.tsx +53 -0
  70. package/src/lib/hooks/useClasses.ts +48 -0
  71. package/src/lib/hooks/useGraphs.ts +67 -0
  72. package/src/lib/hooks/useNavigateWithParams.tsx +97 -0
  73. package/src/lib/hooks/useParams.tsx +8 -0
  74. package/src/lib/hooks/useURIData.ts +180 -0
  75. package/src/lib/hooks/useURILink.ts +7 -0
  76. package/src/lib/index.tsx +9 -0
  77. package/src/lib/public-path.ts +3 -0
  78. package/src/lib/routes/Home.tsx +13 -0
  79. package/src/lib/routes/Search.tsx +13 -0
  80. package/src/lib/routes/Uri.tsx +10 -0
  81. package/src/lib/routes/Yasgui.tsx +13 -0
  82. package/src/lib/setupTests.ts +5 -0
  83. package/src/lib/types.d.ts +6 -0
  84. package/src/lib/utils/getIconFromURI.ts +32 -0
  85. package/src/lib/utils/prefixInverted.json +2445 -0
  86. package/src/lib/utils/utils.ts +131 -0
  87. package/src/lib/yasgui-utils/Storage.ts +117 -0
  88. package/src/lib/yasgui-utils/index.ts +66 -0
@@ -0,0 +1,392 @@
1
+ import BlockIcon from "@mui/icons-material/Block";
2
+ import FilterAltIcon from "@mui/icons-material/FilterAlt";
3
+ import Box from "@mui/material/Box";
4
+ import CircularProgress from "@mui/material/CircularProgress";
5
+ import Container from "@mui/material/Container";
6
+ import IconButton from "@mui/material/IconButton";
7
+ import InputBase from "@mui/material/InputBase";
8
+ import Paper from "@mui/material/Paper";
9
+ import Stack from "@mui/material/Stack";
10
+ import Table from "@mui/material/Table";
11
+ import TableBody from "@mui/material/TableBody";
12
+ import TableCell from "@mui/material/TableCell";
13
+ import TableContainer from "@mui/material/TableContainer";
14
+ import TableHead from "@mui/material/TableHead";
15
+ import TablePagination from "@mui/material/TablePagination";
16
+ import type { TablePaginationActionsProps } from "@mui/material/TablePaginationActions";
17
+ import TablePaginationActions from "@mui/material/TablePaginationActions";
18
+ import TableRow from "@mui/material/TableRow";
19
+ import TextField from "@mui/material/TextField";
20
+ import Typography from "@mui/material/Typography";
21
+ import { type PropsWithChildren, useEffect, useState } from "react";
22
+ import { useNavigateWithParams } from "../../hooks/useNavigateWithParams";
23
+ import { useParam } from "../../hooks/useParams";
24
+ import { type ITriples, useURIData } from "../../hooks/useURIData";
25
+ import { getBestLabel } from "../../utils/utils";
26
+ import { AlertError, Loading, URILink } from "../UtilsComponents";
27
+
28
+ function usePageParam() {
29
+ const pageParam = useParam("page");
30
+ const parsedPage = parseInt(pageParam ?? "0", 10);
31
+ const page =
32
+ parsedPage > 0 && !Number.isNaN(parsedPage) ? parsedPage - 1 : 0;
33
+ return page;
34
+ }
35
+
36
+ function usePerPageParam() {
37
+ const perPageParam = useParam("perpage");
38
+ const parsedPerPage = parseInt(perPageParam ?? "20", 10);
39
+ const perPage =
40
+ !Number.isNaN(parsedPerPage) &&
41
+ (parsedPerPage === 20 || parsedPerPage === 50 || parsedPerPage === 100)
42
+ ? parsedPerPage
43
+ : 20;
44
+ return perPage;
45
+ }
46
+
47
+ function useFilter() {
48
+ const filter = useParam("filter");
49
+ return filter ?? "";
50
+ }
51
+
52
+ export function URIDefaultView({
53
+ endpoint,
54
+ uri,
55
+ graphs,
56
+ showUri = true,
57
+ }: {
58
+ endpoint: string;
59
+ uri: string;
60
+ graphs: string[];
61
+ showUri?: boolean;
62
+ }) {
63
+ const pageNumber = usePageParam();
64
+ const filter = useFilter();
65
+ const perPage = usePerPageParam();
66
+ const [filterInput, setFilterInput] = useState(filter);
67
+ const { triples, total, labels, error, isLoaded } = useURIData({
68
+ endpoint,
69
+ uri,
70
+ graphs,
71
+ search: filter,
72
+ page: pageNumber,
73
+ perPage: perPage,
74
+ });
75
+ const navigate = useNavigateWithParams();
76
+
77
+ function updatePage(newPage: number) {
78
+ const params: Record<string, string> = {
79
+ page: (newPage + 1).toString(),
80
+ perpage: perPage.toString(),
81
+ };
82
+ if (filter !== "") {
83
+ params.filter = filter;
84
+ }
85
+ navigate(`/browse/${encodeURIComponent(uri)}`, params);
86
+ }
87
+
88
+ function updatePerPage(newPerPage: string) {
89
+ const params: Record<string, string> = {
90
+ perpage: newPerPage,
91
+ };
92
+ if (filter !== "") {
93
+ params.filter = filter;
94
+ }
95
+ navigate(`/browse/${encodeURIComponent(uri)}`, params);
96
+ }
97
+
98
+ function updateFilter(newFilter: string) {
99
+ navigate(`/browse/${encodeURIComponent(uri)}`, {
100
+ page: "1",
101
+ perpage: perPage.toString(),
102
+ filter: newFilter,
103
+ });
104
+ }
105
+
106
+ useEffect(() => {
107
+ if (filterInput !== filter) {
108
+ setFilterInput(filter);
109
+ }
110
+ }, [filter, filterInput]);
111
+
112
+ const hasResults = triples.length !== 0;
113
+
114
+ return (
115
+ <Stack spacing={1}>
116
+ {showUri ? (
117
+ <Box>
118
+ <Container>
119
+ {isLoaded ? (
120
+ <Typography variant="h4">
121
+ {getBestLabel({ labels, uri })}
122
+ </Typography>
123
+ ) : (
124
+ <CircularProgress />
125
+ )}
126
+ </Container>
127
+ </Box>
128
+ ) : null}
129
+ <Box padding={2}>
130
+ <Paper>
131
+ <Box padding={2}>
132
+ <Paper
133
+ component="form"
134
+ sx={{
135
+ paddingLeft: 2,
136
+ paddingRight: 2,
137
+ paddingTop: 1,
138
+ paddingBottom: 1,
139
+ display: "flex",
140
+ alignItems: "center",
141
+ }}
142
+ onSubmit={(e: React.FormEvent<HTMLFormElement>) => {
143
+ e.preventDefault();
144
+ const value = (
145
+ e.currentTarget.elements.namedItem(
146
+ "search",
147
+ ) as HTMLInputElement
148
+ ).value;
149
+ updateFilter(value);
150
+ }}
151
+ >
152
+ <InputBase
153
+ type="text"
154
+ placeholder="Filter triples"
155
+ name="search"
156
+ disabled={!hasResults && filter.length === 0}
157
+ sx={{
158
+ flex: 1,
159
+ }}
160
+ value={filterInput}
161
+ onChange={(event) =>
162
+ setFilterInput(event.target.value)
163
+ }
164
+ />
165
+ {isLoaded ? (
166
+ <IconButton type="submit" aria-label="search">
167
+ <FilterAltIcon />
168
+ </IconButton>
169
+ ) : (
170
+ <CircularProgress />
171
+ )}
172
+ </Paper>
173
+ </Box>
174
+ <TableContainer>
175
+ <Table>
176
+ <TableHead>
177
+ <TableRow>
178
+ <TableCell>
179
+ <Typography fontWeight={600}>
180
+ Subject
181
+ </Typography>
182
+ </TableCell>
183
+ <TableCell>
184
+ <Typography fontWeight={600}>
185
+ Predicate
186
+ </Typography>
187
+ </TableCell>
188
+ <TableCell>
189
+ <Typography fontWeight={600}>
190
+ Object
191
+ </Typography>
192
+ </TableCell>
193
+ <TableCell>
194
+ <Typography fontWeight={600}>
195
+ Graph
196
+ </Typography>
197
+ </TableCell>
198
+ </TableRow>
199
+ </TableHead>
200
+ <TableBody>
201
+ <TableContent
202
+ triples={triples}
203
+ uri={uri}
204
+ isLoaded={isLoaded}
205
+ error={error}
206
+ perPage={perPage}
207
+ />
208
+ </TableBody>
209
+ </Table>
210
+ </TableContainer>
211
+ <Box paddingRight={1}>
212
+ <Stack
213
+ direction="row"
214
+ alignItems={"center"}
215
+ justifyContent={"end"}
216
+ >
217
+ <TablePagination
218
+ component={"div"}
219
+ rowsPerPageOptions={[20, 50, 100]}
220
+ page={pageNumber}
221
+ onPageChange={(_, page) => updatePage(page)}
222
+ onRowsPerPageChange={(event) => {
223
+ updatePerPage(event.target.value);
224
+ }}
225
+ count={total}
226
+ rowsPerPage={perPage}
227
+ showFirstButton
228
+ showLastButton
229
+ ActionsComponent={CustomPaginationActionAction}
230
+ />
231
+ <Box width={30}>
232
+ {!isLoaded ? (
233
+ <CircularProgress size={30} />
234
+ ) : null}
235
+ </Box>
236
+ </Stack>
237
+ </Box>
238
+ </Paper>
239
+ </Box>
240
+ </Stack>
241
+ );
242
+ }
243
+
244
+ function CustomPaginationActionAction(props: TablePaginationActionsProps) {
245
+ const totalPages = Math.ceil(props.count / props.rowsPerPage);
246
+ const currentPage = props.page + 1;
247
+ const [inputValue, setInputValue] = useState(currentPage);
248
+
249
+ useEffect(() => {
250
+ setInputValue(currentPage);
251
+ }, [currentPage]);
252
+
253
+ return (
254
+ <Box flexShrink={0} paddingLeft={2} paddingTop={1} paddingBottom={1}>
255
+ <Stack direction={"row"} alignItems={"center"}>
256
+ <Stack direction={"row"} alignItems={"center"} spacing={1}>
257
+ <Box
258
+ component={"form"}
259
+ onSubmit={() => {
260
+ if (Number.isNaN(inputValue) || inputValue <= 0) {
261
+ props.onPageChange(null, 0);
262
+ // Make sure to reset the input value if we are already on the first page
263
+ // as the useEffect would not get triggered
264
+ setInputValue(1);
265
+ } else if (inputValue > totalPages) {
266
+ props.onPageChange(null, totalPages - 1);
267
+ // Make sure to reset the input value if we are already on the last page
268
+ // as the useEffect would not get triggered
269
+ setInputValue(totalPages);
270
+ } else {
271
+ props.onPageChange(null, inputValue - 1);
272
+ }
273
+ }}
274
+ >
275
+ <TextField
276
+ label={"Page"}
277
+ onChange={(event) => {
278
+ const intValue = parseInt(
279
+ event.target.value,
280
+ 10,
281
+ );
282
+ if (Number.isNaN(intValue)) {
283
+ setInputValue(0);
284
+ } else {
285
+ setInputValue(intValue);
286
+ }
287
+ }}
288
+ size="small"
289
+ sx={{
290
+ width: 60,
291
+ }}
292
+ value={inputValue}
293
+ />
294
+ </Box>
295
+ <Typography>/ {totalPages}</Typography>
296
+ </Stack>
297
+ <TablePaginationActions {...props} style={{ marginLeft: 0 }} />
298
+ </Stack>
299
+ </Box>
300
+ );
301
+ }
302
+
303
+ function TableContent({
304
+ triples,
305
+ uri,
306
+ error,
307
+ isLoaded,
308
+ perPage,
309
+ }: {
310
+ triples: ITriples[];
311
+ uri: string;
312
+ error: string | null;
313
+ isLoaded: boolean;
314
+ perPage: number;
315
+ }) {
316
+ const hasResults = triples.length !== 0;
317
+ if (!isLoaded) {
318
+ return (
319
+ <FullSizeTableCell perPage={perPage}>
320
+ <Loading />
321
+ </FullSizeTableCell>
322
+ );
323
+ }
324
+ if (error) {
325
+ return (
326
+ <FullSizeTableCell perPage={perPage}>
327
+ <AlertError error={error} />
328
+ </FullSizeTableCell>
329
+ );
330
+ }
331
+ if (hasResults) {
332
+ const emptyCells = perPage - triples.length;
333
+ return (
334
+ <>
335
+ {triples.map((triple) => (
336
+ /* biome-ignore lint/correctness/useJsxKeyInIterable: no key */
337
+ <TableRow>
338
+ <TableCell>
339
+ <URILink node={triple.subject} focusUri={uri} />
340
+ </TableCell>
341
+ <TableCell>
342
+ <URILink node={triple.predicate} focusUri={uri} />
343
+ </TableCell>
344
+ <TableCell>
345
+ <URILink node={triple.object} focusUri={uri} />
346
+ </TableCell>
347
+ <TableCell>
348
+ <URILink node={triple.graph} />
349
+ </TableCell>
350
+ </TableRow>
351
+ ))}
352
+ {emptyCells > 0 ? (
353
+ <TableRow sx={{ height: 57 * emptyCells }}>
354
+ <TableCell colSpan={4}> </TableCell>
355
+ </TableRow>
356
+ ) : null}
357
+ </>
358
+ );
359
+ }
360
+ return (
361
+ <FullSizeTableCell perPage={perPage}>
362
+ <Stack spacing={4} display={"flex"} justifyContent={"center"}>
363
+ <Box display={"flex"} justifyContent={"center"}>
364
+ <BlockIcon color="disabled" sx={{ fontSize: "15em" }} />
365
+ </Box>
366
+ <Typography
367
+ variant="subtitle1"
368
+ color={"GrayText"}
369
+ textAlign={"center"}
370
+ >
371
+ No results
372
+ </Typography>
373
+ </Stack>
374
+ </FullSizeTableCell>
375
+ );
376
+ }
377
+
378
+ function FullSizeTableCell({
379
+ children,
380
+ perPage,
381
+ }: PropsWithChildren<{ perPage: number }>) {
382
+ return (
383
+ <TableRow
384
+ sx={{
385
+ // 57 = size of one row
386
+ height: 57 * perPage,
387
+ }}
388
+ >
389
+ <TableCell colSpan={4}>{children}</TableCell>
390
+ </TableRow>
391
+ );
392
+ }
@@ -0,0 +1,31 @@
1
+ import type { SparqlViewConfig } from "@logilab/sparqlexplorer-views";
2
+ import { useAuth } from "../../context/AuthContext";
3
+ import { useSearchParamsString } from "../../hooks/useNavigateWithParams";
4
+
5
+ export interface URIWithSelectedViewProps {
6
+ endpoint: string;
7
+ uri: string;
8
+ viewWrapper: SparqlViewConfig;
9
+ }
10
+
11
+ export function URIWithSelectedView({
12
+ endpoint,
13
+ uri,
14
+ viewWrapper,
15
+ }: URIWithSelectedViewProps) {
16
+ const getSearchParamsString = useSearchParamsString();
17
+ const { auth } = useAuth();
18
+
19
+ const Component = viewWrapper.component;
20
+ return (
21
+ <Component
22
+ uri={uri}
23
+ endpoint={endpoint}
24
+ generateUrl={(originalUrl: string) => {
25
+ const encodedUri = encodeURIComponent(originalUrl);
26
+ return `#/browse/${encodedUri}?${getSearchParamsString()}`;
27
+ }}
28
+ auth={auth}
29
+ />
30
+ );
31
+ }
@@ -0,0 +1,32 @@
1
+ import type { BasicAuth } from "@logilab/sparqlutils";
2
+ import {
3
+ createContext,
4
+ type PropsWithChildren,
5
+ useContext,
6
+ useState,
7
+ } from "react";
8
+
9
+ export type AuthContextType = {
10
+ auth: BasicAuth | undefined;
11
+ setAuth: (auth: BasicAuth | undefined) => void;
12
+ };
13
+
14
+ const AuthContext = createContext<AuthContextType>({
15
+ auth: undefined,
16
+ setAuth: () => undefined,
17
+ });
18
+
19
+ export function AuthProvider({ children }: PropsWithChildren) {
20
+ const [auth, setAuth] = useState<BasicAuth | undefined>(undefined);
21
+
22
+ return (
23
+ <AuthContext.Provider value={{ auth, setAuth }}>
24
+ {children}
25
+ </AuthContext.Provider>
26
+ );
27
+ }
28
+
29
+ export function useAuth() {
30
+ const { auth, setAuth } = useContext(AuthContext);
31
+ return { auth, setAuth };
32
+ }
@@ -0,0 +1,50 @@
1
+ import React, { type PropsWithChildren, useContext } from "react";
2
+
3
+ export type SparqlExplorerConfig = {
4
+ endpoint: string | null;
5
+ canEditEndpoint: boolean;
6
+ view: string | null;
7
+ selectedGraphs: string[];
8
+ };
9
+
10
+ const ConfigContext = React.createContext<SparqlExplorerConfig>({
11
+ endpoint: null,
12
+ canEditEndpoint: true,
13
+ view: null,
14
+ selectedGraphs: [],
15
+ });
16
+
17
+ export function ConfigProvider({
18
+ children,
19
+ config,
20
+ }: PropsWithChildren<{ config: SparqlExplorerConfig }>) {
21
+ return (
22
+ <ConfigContext.Provider value={config}>
23
+ {children}
24
+ </ConfigContext.Provider>
25
+ );
26
+ }
27
+
28
+ export function useConfig() {
29
+ return useContext(ConfigContext);
30
+ }
31
+
32
+ export function useEndpoint() {
33
+ const { endpoint } = useConfig();
34
+ return endpoint;
35
+ }
36
+
37
+ export function useView() {
38
+ const { view } = useConfig();
39
+ return view;
40
+ }
41
+
42
+ export function useCanEditEndpoint() {
43
+ const { canEditEndpoint } = useConfig();
44
+ return canEditEndpoint;
45
+ }
46
+
47
+ export function useSelectedGraphs() {
48
+ const { selectedGraphs } = useConfig();
49
+ return selectedGraphs.length === 0 ? ["default"] : selectedGraphs;
50
+ }
@@ -0,0 +1,53 @@
1
+ import type { SparqlViewConfig } from "@logilab/sparqlexplorer-views";
2
+ import {
3
+ type PropsWithChildren,
4
+ createContext,
5
+ type Dispatch,
6
+ type SetStateAction,
7
+ useState,
8
+ } from "react";
9
+
10
+ type ViewsProps = {
11
+ views: SparqlViewConfig[];
12
+ };
13
+
14
+ export const ViewsContext = createContext<{
15
+ views: SparqlViewConfig[];
16
+ selectedView?: SparqlViewConfig;
17
+ setSelectedView: Dispatch<SetStateAction<SparqlViewConfig | undefined>>;
18
+ applicableViews?: SparqlViewConfig[];
19
+ setApplicableViews: Dispatch<
20
+ SetStateAction<SparqlViewConfig[] | undefined>
21
+ >;
22
+ }>({
23
+ views: [],
24
+ selectedView: undefined,
25
+ setSelectedView: () => undefined,
26
+ applicableViews: [],
27
+ setApplicableViews: () => undefined,
28
+ });
29
+
30
+ export function ViewsProvider({
31
+ views,
32
+ children,
33
+ }: PropsWithChildren<ViewsProps>) {
34
+ const [selectedView, setSelectedView] = useState<
35
+ SparqlViewConfig | undefined
36
+ >();
37
+ const [applicableViews, setApplicableViews] = useState<
38
+ SparqlViewConfig[] | undefined
39
+ >(undefined);
40
+ return (
41
+ <ViewsContext
42
+ value={{
43
+ views,
44
+ selectedView,
45
+ setSelectedView,
46
+ applicableViews,
47
+ setApplicableViews,
48
+ }}
49
+ >
50
+ {children}
51
+ </ViewsContext>
52
+ );
53
+ }
@@ -0,0 +1,48 @@
1
+ import { querySparql, sparqlResultToArray } from "@logilab/sparqlutils";
2
+ import { useEffect, useState } from "react";
3
+ import type { ClassListProps, ListQueryResult } from "../components/ClassList";
4
+ import { useAuth } from "../context/AuthContext";
5
+ import { getLabelSparqlClause } from "../utils/utils";
6
+
7
+ export function useClasses({ endpoint }: ClassListProps) {
8
+ const [classes, setClasses] = useState<ListQueryResult[]>([]);
9
+ const [error, setError] = useState<string | null>(null);
10
+ const [isLoaded, setIsLoaded] = useState(false);
11
+ const { auth } = useAuth();
12
+ useEffect(() => {
13
+ async function fetchClasses() {
14
+ try {
15
+ setIsLoaded(false);
16
+ setError(null);
17
+ // Ignore OpenLinks classes as these are internal to virtuoso
18
+ const queryResults = await querySparql(
19
+ endpoint,
20
+ `SELECT DISTINCT ?uri (COUNT(?x) as ?c) ?label WHERE {
21
+ ?x a ?uri
22
+ ${getLabelSparqlClause(undefined, true)}
23
+ } GROUP BY ?uri ?label ORDER BY DESC(?c)`,
24
+ auth,
25
+ );
26
+ const temp = sparqlResultToArray(queryResults).map((elem) => {
27
+ return {
28
+ uri: elem.uri.value,
29
+ nbInstances: elem.c.value,
30
+ label: elem.label?.value,
31
+ };
32
+ });
33
+ // Filtering within the request is too slow
34
+ const filteredTemp = temp.filter(
35
+ (e) => !e.uri.startsWith("http://www.openlinksw.com"),
36
+ );
37
+ setClasses(filteredTemp);
38
+ } catch (e) {
39
+ setError(e.message);
40
+ } finally {
41
+ setIsLoaded(true);
42
+ }
43
+ }
44
+ fetchClasses();
45
+ }, [endpoint, auth]);
46
+
47
+ return { classes, error, isLoaded };
48
+ }
@@ -0,0 +1,67 @@
1
+ import { querySparql, sparqlResultToArray } from "@logilab/sparqlutils";
2
+ import { useEffect, useState } from "react";
3
+ import { useAuth } from "../context/AuthContext";
4
+ import { useEndpoint } from "../context/ConfigContext";
5
+
6
+ export interface ListQueryResult {
7
+ graph: string;
8
+ }
9
+
10
+ const IGNORED_GRAPHS = [
11
+ "http://www.openlinksw.com/schemas/virtrdf#",
12
+ "urn:core:services:sparql",
13
+ "urn:activitystreams-owl:map",
14
+ ];
15
+
16
+ export function useGraphs(showAll = false) {
17
+ const endpoint = useEndpoint();
18
+ const [graphs, setGraphs] = useState<ListQueryResult[]>([]);
19
+ const [error, setError] = useState<string | null>(null);
20
+ const [isLoaded, setIsLoaded] = useState(false);
21
+ const { auth } = useAuth();
22
+ useEffect(() => {
23
+ async function fetchClasses() {
24
+ if (!endpoint) {
25
+ setGraphs([]);
26
+ setIsLoaded(true);
27
+ setError(null);
28
+ return;
29
+ }
30
+ try {
31
+ setIsLoaded(false);
32
+ setError(null);
33
+ const queryResults = await querySparql(
34
+ endpoint,
35
+ `SELECT DISTINCT ?g WHERE {
36
+ GRAPH ?g { ?s ?p ?o }
37
+ } `,
38
+ auth,
39
+ );
40
+ const temp = sparqlResultToArray(queryResults).map((elem) => {
41
+ return { graph: elem.g.value };
42
+ });
43
+ setGraphs([{ graph: "default" }, ...temp]);
44
+ } catch (e) {
45
+ setError(e.message);
46
+ } finally {
47
+ setIsLoaded(true);
48
+ }
49
+ }
50
+ fetchClasses();
51
+ }, [endpoint, auth]);
52
+
53
+ const ignoredGraphs = IGNORED_GRAPHS.map((g) => `<${g}>`).join(", ");
54
+ return {
55
+ graphs: showAll
56
+ ? graphs
57
+ : // Filtering within the request is too slow
58
+ graphs.filter(
59
+ (e) =>
60
+ !ignoredGraphs.includes(e.graph) &&
61
+ !e.graph.startsWith("http://www.w3.org") &&
62
+ !e.graph.includes("/DAV"),
63
+ ),
64
+ error,
65
+ isLoaded,
66
+ };
67
+ }