@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.
- package/LICENSE +661 -0
- package/README.md +73 -0
- package/build/src/lib/App.d.ts +6 -0
- package/build/src/lib/components/ClassList.d.ts +9 -0
- package/build/src/lib/components/EndpointForm.d.ts +1 -0
- package/build/src/lib/components/GraphSelector.d.ts +1 -0
- package/build/src/lib/components/HomePage.d.ts +1 -0
- package/build/src/lib/components/SearchPage.d.ts +1 -0
- package/build/src/lib/components/UriPage.d.ts +1 -0
- package/build/src/lib/components/UtilsComponents.d.ts +11 -0
- package/build/src/lib/components/ViewSelector.d.ts +2 -0
- package/build/src/lib/components/Yasgui.d.ts +6 -0
- package/build/src/lib/components/YasrTableResults.d.ts +55 -0
- package/build/src/lib/components/layout/DrawerContent.d.ts +4 -0
- package/build/src/lib/components/layout/Footer.d.ts +1 -0
- package/build/src/lib/components/layout/Layout.d.ts +2 -0
- package/build/src/lib/components/layout/Navbar.d.ts +4 -0
- package/build/src/lib/components/uri/URIDefaultView.d.ts +6 -0
- package/build/src/lib/components/uri/URIWithSelectedView.d.ts +7 -0
- package/build/src/lib/context/AuthContext.d.ts +11 -0
- package/build/src/lib/context/ConfigContext.d.ts +15 -0
- package/build/src/lib/context/ViewsContext.d.ts +14 -0
- package/build/src/lib/hooks/useClasses.d.ts +6 -0
- package/build/src/lib/hooks/useGraphs.d.ts +8 -0
- package/build/src/lib/hooks/useNavigateWithParams.d.ts +5 -0
- package/build/src/lib/hooks/useParams.d.ts +1 -0
- package/build/src/lib/hooks/useURIData.d.ts +21 -0
- package/build/src/lib/hooks/useURILink.d.ts +1 -0
- package/build/src/lib/index.d.ts +8 -0
- package/build/src/lib/public-path.d.ts +1 -0
- package/build/src/lib/routes/Home.d.ts +1 -0
- package/build/src/lib/routes/Search.d.ts +1 -0
- package/build/src/lib/routes/Uri.d.ts +1 -0
- package/build/src/lib/routes/Yasgui.d.ts +1 -0
- package/build/src/lib/setupTests.d.ts +1 -0
- package/build/src/lib/utils/getIconFromURI.d.ts +3 -0
- package/build/src/lib/utils/utils.d.ts +24 -0
- package/build/src/lib/yasgui-utils/Storage.d.ts +16 -0
- package/build/src/lib/yasgui-utils/index.d.ts +16 -0
- package/build/static/js/lib.js +285 -0
- package/build/static/js/lib.js.LICENSE.txt +71 -0
- package/build/static/js/lib.js.map +1 -0
- package/package.json +73 -0
- package/src/app/index.css +23 -0
- package/src/app/index.tsx +28 -0
- package/src/app/templates/constants.js +1 -0
- package/src/app/templates/index.hbs +18 -0
- package/src/lib/App.css +83 -0
- package/src/lib/App.tsx +31 -0
- package/src/lib/components/ClassList.tsx +173 -0
- package/src/lib/components/EndpointForm.tsx +126 -0
- package/src/lib/components/GraphSelector.tsx +114 -0
- package/src/lib/components/HomePage.tsx +51 -0
- package/src/lib/components/SearchPage.tsx +211 -0
- package/src/lib/components/UriPage.tsx +158 -0
- package/src/lib/components/UtilsComponents.tsx +54 -0
- package/src/lib/components/ViewSelector.css +22 -0
- package/src/lib/components/ViewSelector.tsx +78 -0
- package/src/lib/components/Yasgui.tsx +127 -0
- package/src/lib/components/YasrTableResults.ts +529 -0
- package/src/lib/components/layout/DrawerContent.tsx +55 -0
- package/src/lib/components/layout/Footer.tsx +32 -0
- package/src/lib/components/layout/Layout.tsx +103 -0
- package/src/lib/components/layout/Navbar.tsx +231 -0
- package/src/lib/components/uri/URIDefaultView.tsx +392 -0
- package/src/lib/components/uri/URIWithSelectedView.tsx +31 -0
- package/src/lib/context/AuthContext.tsx +32 -0
- package/src/lib/context/ConfigContext.tsx +50 -0
- package/src/lib/context/ViewsContext.tsx +53 -0
- package/src/lib/hooks/useClasses.ts +48 -0
- package/src/lib/hooks/useGraphs.ts +67 -0
- package/src/lib/hooks/useNavigateWithParams.tsx +97 -0
- package/src/lib/hooks/useParams.tsx +8 -0
- package/src/lib/hooks/useURIData.ts +180 -0
- package/src/lib/hooks/useURILink.ts +7 -0
- package/src/lib/index.tsx +9 -0
- package/src/lib/public-path.ts +3 -0
- package/src/lib/routes/Home.tsx +13 -0
- package/src/lib/routes/Search.tsx +13 -0
- package/src/lib/routes/Uri.tsx +10 -0
- package/src/lib/routes/Yasgui.tsx +13 -0
- package/src/lib/setupTests.ts +5 -0
- package/src/lib/types.d.ts +6 -0
- package/src/lib/utils/getIconFromURI.ts +32 -0
- package/src/lib/utils/prefixInverted.json +2445 -0
- package/src/lib/utils/utils.ts +131 -0
- package/src/lib/yasgui-utils/Storage.ts +117 -0
- 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
|
+
}
|