@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,51 @@
|
|
|
1
|
+
import Alert from "@mui/material/Alert";
|
|
2
|
+
import Stack from "@mui/material/Stack";
|
|
3
|
+
import Typography from "@mui/material/Typography";
|
|
4
|
+
import { useCanEditEndpoint, useEndpoint } from "../context/ConfigContext";
|
|
5
|
+
import { ClassList } from "./ClassList";
|
|
6
|
+
import { EndpointForm } from "./EndpointForm";
|
|
7
|
+
|
|
8
|
+
export function HomePage() {
|
|
9
|
+
const endpoint = useEndpoint();
|
|
10
|
+
const canEditEndpoint = useCanEditEndpoint();
|
|
11
|
+
console.log("home");
|
|
12
|
+
return (
|
|
13
|
+
<Stack spacing={4}>
|
|
14
|
+
<Stack spacing={2}>
|
|
15
|
+
<Typography variant="h4" component={"h1"}>
|
|
16
|
+
Welcome to SparqlExplorer
|
|
17
|
+
</Typography>
|
|
18
|
+
|
|
19
|
+
{canEditEndpoint ? (
|
|
20
|
+
<Stack spacing={1}>
|
|
21
|
+
<Typography>
|
|
22
|
+
Enter the URL of your SPARQL endpoint below to
|
|
23
|
+
browse its data conveniently using an hypertext user
|
|
24
|
+
interface:
|
|
25
|
+
</Typography>
|
|
26
|
+
<EndpointForm />
|
|
27
|
+
</Stack>
|
|
28
|
+
) : (
|
|
29
|
+
<Alert severity="info">
|
|
30
|
+
<Stack
|
|
31
|
+
direction={"row"}
|
|
32
|
+
spacing={1}
|
|
33
|
+
alignItems={"baseline"}
|
|
34
|
+
>
|
|
35
|
+
<Typography>Currently exploring</Typography>
|
|
36
|
+
<Typography fontFamily={"monospace"}>
|
|
37
|
+
{endpoint}
|
|
38
|
+
</Typography>
|
|
39
|
+
</Stack>
|
|
40
|
+
</Alert>
|
|
41
|
+
)}
|
|
42
|
+
</Stack>
|
|
43
|
+
{endpoint ? (
|
|
44
|
+
<Stack spacing={1}>
|
|
45
|
+
<Typography variant="h4">Available Classes</Typography>
|
|
46
|
+
<ClassList endpoint={endpoint} />
|
|
47
|
+
</Stack>
|
|
48
|
+
) : null}
|
|
49
|
+
</Stack>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import { querySparql, sparqlResultToArray } from "@logilab/sparqlutils";
|
|
2
|
+
import CheckIcon from "@mui/icons-material/Check";
|
|
3
|
+
import Box from "@mui/material/Box";
|
|
4
|
+
import Card from "@mui/material/Card";
|
|
5
|
+
import Chip from "@mui/material/Chip";
|
|
6
|
+
import List from "@mui/material/List";
|
|
7
|
+
import ListItemButton from "@mui/material/ListItemButton";
|
|
8
|
+
import ListItemIcon from "@mui/material/ListItemIcon";
|
|
9
|
+
import ListItemText from "@mui/material/ListItemText";
|
|
10
|
+
import Stack from "@mui/material/Stack";
|
|
11
|
+
import Typography from "@mui/material/Typography";
|
|
12
|
+
import React, { useEffect, useState } from "react";
|
|
13
|
+
import { Link } from "react-router-dom";
|
|
14
|
+
import { useAuth } from "../context/AuthContext";
|
|
15
|
+
import { useEndpoint } from "../context/ConfigContext";
|
|
16
|
+
import { useNavigateWithParams } from "../hooks/useNavigateWithParams";
|
|
17
|
+
import { useParam } from "../hooks/useParams";
|
|
18
|
+
import { useURILink } from "../hooks/useURILink";
|
|
19
|
+
import { getIconFromURI } from "../utils/getIconFromURI";
|
|
20
|
+
import { prefixedNameFromUri, type RDFNode } from "../utils/utils";
|
|
21
|
+
import { Loading, NoResults } from "./UtilsComponents";
|
|
22
|
+
|
|
23
|
+
interface Result {
|
|
24
|
+
node: RDFNode;
|
|
25
|
+
label: string;
|
|
26
|
+
types: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export function SearchPage() {
|
|
30
|
+
const searchInput = useParam("search");
|
|
31
|
+
const [results, setResults] = useState<Result[]>([]);
|
|
32
|
+
const [loading, setLoading] = useState<boolean>(false);
|
|
33
|
+
const endpoint = useEndpoint();
|
|
34
|
+
const { auth } = useAuth();
|
|
35
|
+
|
|
36
|
+
const navigate = useNavigateWithParams();
|
|
37
|
+
|
|
38
|
+
useEffect(() => {
|
|
39
|
+
if (!endpoint || !searchInput) {
|
|
40
|
+
navigate("/", undefined, true);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
setLoading(true);
|
|
46
|
+
querySparql(
|
|
47
|
+
endpoint,
|
|
48
|
+
`select distinct ?x ?z (GROUP_CONCAT(DISTINCT ?type, ", ") as ?types) where {
|
|
49
|
+
{?x ?y ?z. OPTIONAL { ?x a ?type.} }
|
|
50
|
+
UNION{
|
|
51
|
+
GRAPH ?g
|
|
52
|
+
{?x ?y ?z. OPTIONAL { ?x a ?type.} }
|
|
53
|
+
}
|
|
54
|
+
FILTER(isLiteral(?z) && regex(str(?z), "${searchInput}","i"))
|
|
55
|
+
} GROUP BY ?x ?z
|
|
56
|
+
LIMIT 100`,
|
|
57
|
+
auth,
|
|
58
|
+
).then((queryResults) => {
|
|
59
|
+
const temp = sparqlResultToArray(queryResults).map((elem) => {
|
|
60
|
+
return {
|
|
61
|
+
node: elem.x,
|
|
62
|
+
label: elem.z.value,
|
|
63
|
+
types: elem.types.value.split(", "),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
setResults(temp);
|
|
67
|
+
setLoading(false);
|
|
68
|
+
});
|
|
69
|
+
} catch (e) {
|
|
70
|
+
console.error(e.message);
|
|
71
|
+
setLoading(false);
|
|
72
|
+
}
|
|
73
|
+
}, [endpoint, searchInput, auth, navigate]);
|
|
74
|
+
|
|
75
|
+
if (!endpoint || !searchInput) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
if (loading) {
|
|
80
|
+
return <Loading />;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return <ResultsDisplay results={results} searchValue={searchInput} />;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function ResultsDisplay({
|
|
87
|
+
results,
|
|
88
|
+
searchValue,
|
|
89
|
+
}: {
|
|
90
|
+
results: Result[];
|
|
91
|
+
searchValue: string;
|
|
92
|
+
}) {
|
|
93
|
+
const [allDifferentTypes, setAllDifferentTypes] = useState<string[]>([]);
|
|
94
|
+
const [renderResults, setRenderResults] = useState<
|
|
95
|
+
{ result: Result; score: number }[]
|
|
96
|
+
>([]);
|
|
97
|
+
const [selectedType, setSelectedType] = useState<string | null>(null);
|
|
98
|
+
React.useEffect(() => {
|
|
99
|
+
const comparisonValue = searchValue.toLowerCase();
|
|
100
|
+
const scoredResults: { result: Result; score: number }[] = results.map(
|
|
101
|
+
(result) => {
|
|
102
|
+
const resultValue = result.label.toLowerCase();
|
|
103
|
+
if (resultValue === comparisonValue) {
|
|
104
|
+
return { result, score: 1 };
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
result,
|
|
108
|
+
score: 1 / (resultValue.length - comparisonValue.length),
|
|
109
|
+
};
|
|
110
|
+
},
|
|
111
|
+
);
|
|
112
|
+
setAllDifferentTypes(
|
|
113
|
+
Array.from(new Set(results.flatMap((r) => r.types))),
|
|
114
|
+
);
|
|
115
|
+
scoredResults.sort((result1, result2) => result2.score - result1.score);
|
|
116
|
+
setRenderResults(scoredResults);
|
|
117
|
+
}, [results, searchValue]);
|
|
118
|
+
|
|
119
|
+
if (results.length === 0) {
|
|
120
|
+
return <NoResults />;
|
|
121
|
+
}
|
|
122
|
+
return (
|
|
123
|
+
<Stack spacing={2} width={"100%"}>
|
|
124
|
+
<Stack
|
|
125
|
+
direction={"row"}
|
|
126
|
+
spacing={1}
|
|
127
|
+
display={"flex"}
|
|
128
|
+
alignItems={"center"}
|
|
129
|
+
>
|
|
130
|
+
<Box>
|
|
131
|
+
<Typography>Filter:</Typography>
|
|
132
|
+
</Box>
|
|
133
|
+
{allDifferentTypes.map((type) => (
|
|
134
|
+
<Chip
|
|
135
|
+
key={type}
|
|
136
|
+
icon={
|
|
137
|
+
selectedType !== null && selectedType === type ? (
|
|
138
|
+
<CheckIcon />
|
|
139
|
+
) : undefined
|
|
140
|
+
}
|
|
141
|
+
onClick={() => {
|
|
142
|
+
selectedType === type
|
|
143
|
+
? setSelectedType(null)
|
|
144
|
+
: setSelectedType(type);
|
|
145
|
+
}}
|
|
146
|
+
variant={
|
|
147
|
+
selectedType !== null && selectedType === type
|
|
148
|
+
? "filled"
|
|
149
|
+
: "outlined"
|
|
150
|
+
}
|
|
151
|
+
label={prefixedNameFromUri(type)}
|
|
152
|
+
/>
|
|
153
|
+
))}
|
|
154
|
+
</Stack>
|
|
155
|
+
<Card>
|
|
156
|
+
<List>
|
|
157
|
+
{renderResults.map(({ result: elem }) => (
|
|
158
|
+
<ResultListItem
|
|
159
|
+
selectedType={selectedType}
|
|
160
|
+
result={elem}
|
|
161
|
+
// there is a distinct clause, hence this tuple should be unique for the results
|
|
162
|
+
key={elem.node.value + elem.types + elem.label}
|
|
163
|
+
/>
|
|
164
|
+
))}
|
|
165
|
+
</List>
|
|
166
|
+
</Card>
|
|
167
|
+
</Stack>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function ResultListItem({
|
|
172
|
+
selectedType,
|
|
173
|
+
result,
|
|
174
|
+
}: {
|
|
175
|
+
selectedType: string | null;
|
|
176
|
+
result: Result;
|
|
177
|
+
}) {
|
|
178
|
+
const uriLink = useURILink(result.node.value);
|
|
179
|
+
if (selectedType !== null && result.types.indexOf(selectedType) < 0) {
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const Icon = getIconFromURI(result.types[0]);
|
|
184
|
+
|
|
185
|
+
return (
|
|
186
|
+
<Link
|
|
187
|
+
to={uriLink}
|
|
188
|
+
key={result.node.value + result.label}
|
|
189
|
+
style={{
|
|
190
|
+
textDecoration: "none",
|
|
191
|
+
color: "inherit",
|
|
192
|
+
}}
|
|
193
|
+
>
|
|
194
|
+
<ListItemButton divider>
|
|
195
|
+
<ListItemIcon>
|
|
196
|
+
<Icon />
|
|
197
|
+
</ListItemIcon>
|
|
198
|
+
<ListItemText
|
|
199
|
+
primary={
|
|
200
|
+
result.label.length > 180
|
|
201
|
+
? `${result.label.slice(0, 180)}...`
|
|
202
|
+
: result.label
|
|
203
|
+
}
|
|
204
|
+
secondary={result.types
|
|
205
|
+
.map((t) => prefixedNameFromUri(t))
|
|
206
|
+
.join(", ")}
|
|
207
|
+
/>
|
|
208
|
+
</ListItemButton>
|
|
209
|
+
</Link>
|
|
210
|
+
);
|
|
211
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import type { SparqlViewConfig } from "@logilab/sparqlexplorer-views";
|
|
2
|
+
import ArrowDownIcon from "@mui/icons-material/KeyboardArrowDown";
|
|
3
|
+
import Accordion from "@mui/material/Accordion";
|
|
4
|
+
import AccordionDetails from "@mui/material/AccordionDetails";
|
|
5
|
+
import AccordionSummary from "@mui/material/AccordionSummary";
|
|
6
|
+
import Box from "@mui/material/Box";
|
|
7
|
+
import Snackbar from "@mui/material/Snackbar";
|
|
8
|
+
import Stack from "@mui/material/Stack";
|
|
9
|
+
import Typography from "@mui/material/Typography";
|
|
10
|
+
import React, { useState, useContext } from "react";
|
|
11
|
+
import { useParams } from "react-router-dom";
|
|
12
|
+
import { useAuth } from "../context/AuthContext";
|
|
13
|
+
import {
|
|
14
|
+
useEndpoint,
|
|
15
|
+
useSelectedGraphs,
|
|
16
|
+
useView,
|
|
17
|
+
} from "../context/ConfigContext";
|
|
18
|
+
import { ViewsContext } from "../context/ViewsContext";
|
|
19
|
+
import { useNavigateWithParams } from "../hooks/useNavigateWithParams";
|
|
20
|
+
import { getApplicableViews, isDefaultView } from "../utils/utils";
|
|
21
|
+
import { Loading } from "./UtilsComponents";
|
|
22
|
+
import { URIDefaultView } from "./uri/URIDefaultView";
|
|
23
|
+
import { URIWithSelectedView } from "./uri/URIWithSelectedView";
|
|
24
|
+
|
|
25
|
+
export function UriPage() {
|
|
26
|
+
const params = useParams();
|
|
27
|
+
const endpoint = useEndpoint();
|
|
28
|
+
const viewName = useView();
|
|
29
|
+
const selectedGraphs = useSelectedGraphs();
|
|
30
|
+
const uri = params["*"];
|
|
31
|
+
|
|
32
|
+
const [loadingViews, setLoadingViews] = useState(viewName !== null);
|
|
33
|
+
const { setApplicableViews, selectedView, setSelectedView } =
|
|
34
|
+
useContext(ViewsContext);
|
|
35
|
+
const [snackbarMessage, setSnackbarMessage] = useState<string | null>(null);
|
|
36
|
+
const { auth } = useAuth();
|
|
37
|
+
|
|
38
|
+
const navigate = useNavigateWithParams();
|
|
39
|
+
|
|
40
|
+
React.useEffect(() => {
|
|
41
|
+
setSnackbarMessage(null);
|
|
42
|
+
if (uri === undefined || endpoint === null) {
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
setLoadingViews(isDefaultView(viewName));
|
|
46
|
+
getApplicableViews(uri, endpoint, auth).then((applicableViews) => {
|
|
47
|
+
setApplicableViews(applicableViews);
|
|
48
|
+
if (viewName === null) {
|
|
49
|
+
if (applicableViews.length > 0) {
|
|
50
|
+
setSelectedView(applicableViews[0]);
|
|
51
|
+
} else {
|
|
52
|
+
setSnackbarMessage(
|
|
53
|
+
"No view suitable for these data, falling back to default",
|
|
54
|
+
);
|
|
55
|
+
setSelectedView(undefined);
|
|
56
|
+
}
|
|
57
|
+
} else if (!isDefaultView(viewName)) {
|
|
58
|
+
const validView = applicableViews.find(
|
|
59
|
+
(v) => v.name === viewName,
|
|
60
|
+
);
|
|
61
|
+
if (validView) {
|
|
62
|
+
setSelectedView(validView);
|
|
63
|
+
} else {
|
|
64
|
+
setSnackbarMessage(
|
|
65
|
+
"No view suitable for these data, falling back to default",
|
|
66
|
+
);
|
|
67
|
+
setSelectedView(undefined);
|
|
68
|
+
}
|
|
69
|
+
} else {
|
|
70
|
+
setSelectedView(undefined);
|
|
71
|
+
}
|
|
72
|
+
setLoadingViews(false);
|
|
73
|
+
});
|
|
74
|
+
}, [viewName, endpoint, uri, auth, setSelectedView, setApplicableViews]);
|
|
75
|
+
|
|
76
|
+
if (endpoint === null || uri === undefined) {
|
|
77
|
+
navigate("/", undefined, true);
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (loadingViews) {
|
|
82
|
+
return <Loading />;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let content = null;
|
|
86
|
+
if (selectedView === undefined) {
|
|
87
|
+
content = (
|
|
88
|
+
<URIDefaultView
|
|
89
|
+
uri={uri}
|
|
90
|
+
endpoint={endpoint ?? ""}
|
|
91
|
+
graphs={selectedGraphs}
|
|
92
|
+
/>
|
|
93
|
+
);
|
|
94
|
+
} else {
|
|
95
|
+
content = (
|
|
96
|
+
<CustomView
|
|
97
|
+
uri={uri}
|
|
98
|
+
endpoint={endpoint ?? ""}
|
|
99
|
+
graphs={selectedGraphs}
|
|
100
|
+
selectedView={selectedView}
|
|
101
|
+
/>
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
return (
|
|
106
|
+
<>
|
|
107
|
+
<Snackbar
|
|
108
|
+
open={snackbarMessage !== null}
|
|
109
|
+
autoHideDuration={6000}
|
|
110
|
+
onClose={() => setSnackbarMessage(null)}
|
|
111
|
+
message={snackbarMessage}
|
|
112
|
+
anchorOrigin={{ vertical: "top", horizontal: "center" }}
|
|
113
|
+
/>
|
|
114
|
+
{content}
|
|
115
|
+
</>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function CustomView({
|
|
120
|
+
endpoint,
|
|
121
|
+
uri,
|
|
122
|
+
graphs,
|
|
123
|
+
selectedView,
|
|
124
|
+
}: {
|
|
125
|
+
endpoint: string;
|
|
126
|
+
uri: string;
|
|
127
|
+
graphs: string[];
|
|
128
|
+
selectedView: SparqlViewConfig;
|
|
129
|
+
}) {
|
|
130
|
+
return (
|
|
131
|
+
<Stack spacing={5}>
|
|
132
|
+
<Box>
|
|
133
|
+
<URIWithSelectedView
|
|
134
|
+
uri={uri}
|
|
135
|
+
endpoint={endpoint}
|
|
136
|
+
viewWrapper={selectedView}
|
|
137
|
+
/>
|
|
138
|
+
</Box>
|
|
139
|
+
<Accordion>
|
|
140
|
+
<AccordionSummary
|
|
141
|
+
expandIcon={<ArrowDownIcon />}
|
|
142
|
+
aria-controls="panel1-content"
|
|
143
|
+
id="panel1-header"
|
|
144
|
+
>
|
|
145
|
+
<Typography>See all triples</Typography>
|
|
146
|
+
</AccordionSummary>
|
|
147
|
+
<AccordionDetails>
|
|
148
|
+
<URIDefaultView
|
|
149
|
+
uri={uri}
|
|
150
|
+
endpoint={endpoint}
|
|
151
|
+
graphs={graphs}
|
|
152
|
+
showUri={false}
|
|
153
|
+
/>
|
|
154
|
+
</AccordionDetails>
|
|
155
|
+
</Accordion>
|
|
156
|
+
</Stack>
|
|
157
|
+
);
|
|
158
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import Alert from "@mui/material/Alert";
|
|
2
|
+
import Box from "@mui/material/Box";
|
|
3
|
+
import CircularProgress from "@mui/material/CircularProgress";
|
|
4
|
+
import Typography from "@mui/material/Typography";
|
|
5
|
+
import { Link } from "react-router-dom";
|
|
6
|
+
import { useURILink } from "../hooks/useURILink";
|
|
7
|
+
import { prefixedNameFromUri, type RDFNode } from "../utils/utils";
|
|
8
|
+
|
|
9
|
+
export function Loading() {
|
|
10
|
+
return (
|
|
11
|
+
<Box
|
|
12
|
+
height={"100%"}
|
|
13
|
+
minHeight={200}
|
|
14
|
+
display={"flex"}
|
|
15
|
+
alignItems={"center"}
|
|
16
|
+
justifyContent={"center"}
|
|
17
|
+
>
|
|
18
|
+
<CircularProgress />
|
|
19
|
+
</Box>
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function AlertError({ error }: { error: string }) {
|
|
24
|
+
return <Alert severity="error">{error}</Alert>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function NoResults() {
|
|
28
|
+
return <div>No data to display</div>;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function URILink({
|
|
32
|
+
node,
|
|
33
|
+
focusUri,
|
|
34
|
+
label,
|
|
35
|
+
}: {
|
|
36
|
+
node: RDFNode;
|
|
37
|
+
focusUri?: string;
|
|
38
|
+
label?: string;
|
|
39
|
+
}) {
|
|
40
|
+
const uriLink = useURILink(node.value);
|
|
41
|
+
if (node.type === "uri") {
|
|
42
|
+
const text = label ?? prefixedNameFromUri(node.value);
|
|
43
|
+
if (node.value === focusUri) {
|
|
44
|
+
return <Typography color={"GrayText"}>{text}</Typography>;
|
|
45
|
+
} else {
|
|
46
|
+
return (
|
|
47
|
+
<Link to={uriLink}>
|
|
48
|
+
<Typography>{text}</Typography>
|
|
49
|
+
</Link>
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return <Typography color={"GrayText"}>{node.value}</Typography>;
|
|
54
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
.list-group-item.view-item {
|
|
2
|
+
border-color: white;
|
|
3
|
+
width: 80%;
|
|
4
|
+
font-size: 1rem;
|
|
5
|
+
}
|
|
6
|
+
.list-group-item.view-item:not(.list-group-item-success) {
|
|
7
|
+
background: none;
|
|
8
|
+
color: black;
|
|
9
|
+
}
|
|
10
|
+
.list-group-item.view-item.disabled:not(.list-group-item-success) {
|
|
11
|
+
background: none;
|
|
12
|
+
color: grey;
|
|
13
|
+
}
|
|
14
|
+
.list-group-item.view-item:hover {
|
|
15
|
+
background-color: lightblue;
|
|
16
|
+
color: black;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.view-list {
|
|
20
|
+
max-height: 400px;
|
|
21
|
+
overflow-y: auto;
|
|
22
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import "./ViewSelector.css";
|
|
2
|
+
import Box from "@mui/material/Box";
|
|
3
|
+
import FormControlLabel from "@mui/material/FormControlLabel";
|
|
4
|
+
import List from "@mui/material/List";
|
|
5
|
+
import ListItemButton from "@mui/material/ListItemButton";
|
|
6
|
+
import Radio from "@mui/material/Radio";
|
|
7
|
+
import Switch from "@mui/material/Switch";
|
|
8
|
+
import Typography from "@mui/material/Typography";
|
|
9
|
+
import { useEffect, useContext } from "react";
|
|
10
|
+
import { useParams } from "react-router-dom";
|
|
11
|
+
import { useView } from "../context/ConfigContext";
|
|
12
|
+
import { ViewsContext } from "../context/ViewsContext";
|
|
13
|
+
import { useChangeView } from "../hooks/useNavigateWithParams";
|
|
14
|
+
import { DEFAUT_VIEW_NAME, isDefaultView } from "../utils/utils";
|
|
15
|
+
|
|
16
|
+
export function ViewSelector() {
|
|
17
|
+
const view = useView();
|
|
18
|
+
const params = useParams();
|
|
19
|
+
const currentUri = params["*"];
|
|
20
|
+
|
|
21
|
+
const { views, selectedView, applicableViews, setApplicableViews } =
|
|
22
|
+
useContext(ViewsContext);
|
|
23
|
+
|
|
24
|
+
const isAutomatic = () => view === null;
|
|
25
|
+
const changeView = useChangeView();
|
|
26
|
+
|
|
27
|
+
useEffect(() => {
|
|
28
|
+
if (!currentUri && applicableViews) {
|
|
29
|
+
setApplicableViews(undefined);
|
|
30
|
+
}
|
|
31
|
+
}, [currentUri, applicableViews, setApplicableViews]);
|
|
32
|
+
|
|
33
|
+
return (
|
|
34
|
+
<div>
|
|
35
|
+
<Box paddingLeft={1} paddingRight={1}>
|
|
36
|
+
<Typography variant="h6">Available views</Typography>
|
|
37
|
+
<FormControlLabel
|
|
38
|
+
control={
|
|
39
|
+
<Switch
|
|
40
|
+
checked={isAutomatic()}
|
|
41
|
+
onChange={() => {
|
|
42
|
+
changeView(
|
|
43
|
+
isAutomatic() ? DEFAUT_VIEW_NAME : null,
|
|
44
|
+
);
|
|
45
|
+
}}
|
|
46
|
+
/>
|
|
47
|
+
}
|
|
48
|
+
label="Automatic"
|
|
49
|
+
/>
|
|
50
|
+
</Box>
|
|
51
|
+
<List>
|
|
52
|
+
{views?.map((item) => {
|
|
53
|
+
const selected =
|
|
54
|
+
(selectedView === undefined &&
|
|
55
|
+
isDefaultView(item.name)) ||
|
|
56
|
+
(selectedView !== undefined &&
|
|
57
|
+
selectedView.name === item.name);
|
|
58
|
+
return (
|
|
59
|
+
<ListItemButton
|
|
60
|
+
disabled={
|
|
61
|
+
!isDefaultView(item.name) &&
|
|
62
|
+
!applicableViews?.find(
|
|
63
|
+
(v) => v.name === item.name,
|
|
64
|
+
)
|
|
65
|
+
}
|
|
66
|
+
key={item.name}
|
|
67
|
+
onClick={() => changeView(item.name)}
|
|
68
|
+
selected={selected}
|
|
69
|
+
>
|
|
70
|
+
<Radio checked={selected} size="small" />
|
|
71
|
+
{item.name}
|
|
72
|
+
</ListItemButton>
|
|
73
|
+
);
|
|
74
|
+
})}
|
|
75
|
+
</List>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|