@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
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@logilab/sparqlexplorer",
3
+ "version": "0.7.0",
4
+ "description": "A tool to explore sparql endpoints",
5
+ "exports": "./build/src/lib/index.js",
6
+ "types": "./build/src/lib/index.d.ts",
7
+ "scripts": {
8
+ "clean": "rm -rf build",
9
+ "format": "biome format",
10
+ "format:write": "biome format --write",
11
+ "build": "webpack --config webpack.dev.js",
12
+ "build:production": "webpack --config webpack.prod.js",
13
+ "build:pages": "webpack --config webpack.pages.js",
14
+ "build:lib": "concurrently --kill-others 'tsc --project ./tsconfig.lib.json --watch' 'cpx \"src/lib/**/*.css\" build/src/lib/ --watch'",
15
+ "build:lib:production": "webpack --config webpack.lib.prod.js",
16
+ "serve": "webpack-dev-server --config webpack.dev.js",
17
+ "lint": "biome lint",
18
+ "lint:write": "biome lint --fix",
19
+ "check": "biome check",
20
+ "check:write": "biome check --fix"
21
+ },
22
+ "peerDependencies": {
23
+ "react": "^19.2.3",
24
+ "react-dom": "^19.2.3"
25
+ },
26
+ "repository": {
27
+ "type": "hg",
28
+ "url": "https://forge.extranet.logilab.fr/open-source/SemWeb/sparqlexplorer"
29
+ },
30
+ "author": "Logilab",
31
+ "license": "LGPL-3.0",
32
+ "devDependencies": {
33
+ "@biomejs/biome": "2.3.12",
34
+ "@types/react": "^19.2.9",
35
+ "@types/react-dom": "^19.2.3",
36
+ "@types/react-router-dom": "^5.3.3",
37
+ "concurrently": "^9.2.1",
38
+ "copy-webpack-plugin": "^13.0.1",
39
+ "cpx": "^1.5.0",
40
+ "css-loader": "^7.1.2",
41
+ "file-loader": "^6.2.0",
42
+ "html-webpack-plugin": "^5.6.6",
43
+ "sass": "^1.97.3",
44
+ "sass-loader": "^16.0.6",
45
+ "serve": "^14.2.5",
46
+ "source-map-loader": "^5.0.0",
47
+ "style-loader": "^4.0.0",
48
+ "ts-loader": "^9.5.4",
49
+ "typescript": "^5.9.3",
50
+ "webpack": "^5.104.1",
51
+ "webpack-cli": "^6.0.1",
52
+ "webpack-dev-server": "^5.2.3",
53
+ "webpack-merge": "^6.0.1"
54
+ },
55
+ "dependencies": {
56
+ "@emotion/react": "^11.14.0",
57
+ "@emotion/styled": "^11.14.1",
58
+ "@logilab/sparqlexplorer-views": "^1.9.1",
59
+ "@logilab/sparqlutils": "^1.1.0",
60
+ "@logilab/yasgui": "^4.2.30",
61
+ "@mui/icons-material": "^7.3.7",
62
+ "@mui/material": "^7.3.7",
63
+ "datatables.net": "^2.3.6",
64
+ "react": "^19.2.3",
65
+ "react-dom": "^19.2.3",
66
+ "react-router-dom": "^7.13.0",
67
+ "ts-essentials": "^10.1.1"
68
+ },
69
+ "files": [
70
+ "./build",
71
+ "./src/"
72
+ ]
73
+ }
@@ -0,0 +1,23 @@
1
+ html {
2
+ min-height: 100vh;
3
+ }
4
+
5
+ body {
6
+ margin: 0;
7
+ font-family:
8
+ -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen",
9
+ "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
10
+ sans-serif;
11
+ -webkit-font-smoothing: antialiased;
12
+ -moz-osx-font-smoothing: grayscale;
13
+ min-height: 100vh;
14
+ }
15
+
16
+ #root {
17
+ min-height: 100vh;
18
+ }
19
+
20
+ code {
21
+ font-family:
22
+ source-code-pro, Menlo, Monaco, Consolas, "Courier New", monospace;
23
+ }
@@ -0,0 +1,28 @@
1
+ import {
2
+ SPARQL_VIEWS,
3
+ type SparqlViewConfig,
4
+ } from "@logilab/sparqlexplorer-views";
5
+ import React from "react";
6
+ import { createRoot } from "react-dom/client";
7
+ import { DEFAUT_VIEW_NAME } from "../lib/utils/utils";
8
+ import "./index.css";
9
+ import App from "../lib/App";
10
+
11
+ const container = document.getElementById("root");
12
+ /* biome-ignore lint/style/noNonNullAssertion: needed to compile */
13
+ const root = createRoot(container!);
14
+
15
+ const allViews: Array<SparqlViewConfig> = [
16
+ {
17
+ name: DEFAUT_VIEW_NAME,
18
+ component: () => undefined,
19
+ isApplicable: () => Promise.resolve(true),
20
+ },
21
+ ...SPARQL_VIEWS,
22
+ ];
23
+
24
+ root.render(
25
+ <React.StrictMode>
26
+ <App views={allViews} />
27
+ </React.StrictMode>,
28
+ );
@@ -0,0 +1 @@
1
+ window.SPARQL_ENDPOINT = null;
@@ -0,0 +1,18 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="description" content="">
7
+ <meta name="viewport" content="width=device-width, initial-scale=1">
8
+ <script src="static/js/constants.js"></script>
9
+ <title>
10
+ <%= htmlWebpackPlugin.options.title %>
11
+ </title>
12
+ </head>
13
+
14
+ <body>
15
+ <div id="root"></div>
16
+ </body>
17
+
18
+ </html>
@@ -0,0 +1,83 @@
1
+ .App {
2
+ text-align: center;
3
+ }
4
+
5
+ .App-logo {
6
+ height: 40vmin;
7
+ pointer-events: none;
8
+ }
9
+
10
+ @media (prefers-reduced-motion: no-preference) {
11
+ .App-logo {
12
+ animation: App-logo-spin infinite 20s linear;
13
+ }
14
+ }
15
+
16
+ .App-header {
17
+ background-color: #282c34;
18
+ min-height: 100vh;
19
+ display: flex;
20
+ flex-direction: column;
21
+ align-items: center;
22
+ justify-content: center;
23
+ font-size: calc(10px + 2vmin);
24
+ color: white;
25
+ }
26
+
27
+ .App-link {
28
+ color: #61dafb;
29
+ }
30
+
31
+ @keyframes App-logo-spin {
32
+ from {
33
+ transform: rotate(0deg);
34
+ }
35
+ to {
36
+ transform: rotate(360deg);
37
+ }
38
+ }
39
+
40
+ .bm-burger-button:hover {
41
+ /* biome-ignore lint/complexity/noImportantStyles: important */
42
+ opacity: 1 !important;
43
+ }
44
+
45
+ .jumbotron {
46
+ width: 700px;
47
+ margin: auto;
48
+ }
49
+
50
+ .jumbotron button {
51
+ float: right;
52
+ }
53
+
54
+ .out-link {
55
+ position: absolute;
56
+ top: 5px;
57
+ right: 150px;
58
+ }
59
+
60
+ .search-link {
61
+ position: absolute;
62
+ top: 5px;
63
+ right: 100px;
64
+ }
65
+
66
+ .whole-height-container {
67
+ height: 100vh;
68
+ display: flex;
69
+ }
70
+
71
+ .rbt-input-main {
72
+ /* biome-ignore lint/complexity/noImportantStyles: important */
73
+ width: 100% !important;
74
+ }
75
+
76
+ .yasgui-container {
77
+ height: 100%;
78
+ overflow-y: auto;
79
+ }
80
+
81
+ #yasgui {
82
+ width: 100%;
83
+ }
@@ -0,0 +1,31 @@
1
+ import type { SparqlViewConfig } from "@logilab/sparqlexplorer-views";
2
+ import { Route, HashRouter as Router, Routes } from "react-router-dom";
3
+
4
+ import "./App.css";
5
+
6
+ import { ViewsProvider } from "./context/ViewsContext";
7
+ import { AuthProvider } from "./context/AuthContext";
8
+ import { Home } from "./routes/Home";
9
+ import { Search } from "./routes/Search";
10
+ import { Uri } from "./routes/Uri";
11
+ import { Yasgui } from "./routes/Yasgui";
12
+
13
+ function App({ views }: { views: Array<SparqlViewConfig> }) {
14
+ return (
15
+ <AuthProvider>
16
+ <ViewsProvider views={views}>
17
+ <Router>
18
+ <Routes>
19
+ <Route path="/" element={<Home />} />
20
+ <Route path="/yasgui" element={<Yasgui />} />
21
+ <Route path="/search" element={<Search />} />
22
+ <Route path="/browse/*" element={<Uri />} />
23
+ <Route path="*" element={<Home />} />
24
+ </Routes>
25
+ </Router>
26
+ </ViewsProvider>
27
+ </AuthProvider>
28
+ );
29
+ }
30
+
31
+ export default App;
@@ -0,0 +1,173 @@
1
+ import FilterAltIcon from "@mui/icons-material/FilterAlt";
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 Card from "@mui/material/Card";
8
+ import CardActionArea from "@mui/material/CardActionArea";
9
+ import CardContent from "@mui/material/CardContent";
10
+ import Chip from "@mui/material/Chip";
11
+ import Grid from "@mui/material/Grid";
12
+ import InputAdornment from "@mui/material/InputAdornment";
13
+ import Stack from "@mui/material/Stack";
14
+ import TextField from "@mui/material/TextField";
15
+ import Typography from "@mui/material/Typography";
16
+ import React from "react";
17
+ import { Link } from "react-router-dom";
18
+ import { useClasses } from "../hooks/useClasses";
19
+ import { useURILink } from "../hooks/useURILink";
20
+ import { getIconFromURI } from "../utils/getIconFromURI";
21
+ import { prefixedNameFromUri } from "../utils/utils";
22
+ import { AlertError, Loading } from "./UtilsComponents";
23
+
24
+ export interface ListQueryResult {
25
+ uri: string;
26
+ nbInstances?: string;
27
+ label?: string;
28
+ }
29
+
30
+ export interface ClassListProps {
31
+ endpoint: string;
32
+ }
33
+
34
+ function isMetaClass(uri: string) {
35
+ return (
36
+ uri.startsWith("http://www.w3.org/1999/02/22-rdf-syntax-ns") ||
37
+ uri.startsWith("http://www.w3.org/2000/01/rdf-schema") ||
38
+ uri.startsWith("http://www.w3.org/ns/sparql-service-description") ||
39
+ uri.startsWith("http://www.w3.org/2002/07/owl")
40
+ );
41
+ }
42
+
43
+ export function ClassList({ endpoint }: ClassListProps) {
44
+ const { classes, isLoaded, error } = useClasses({ endpoint });
45
+ console.log("render");
46
+ const [fitlerClassLabel, setFilterClassLabel] = React.useState<string>("");
47
+ if (!isLoaded) {
48
+ return <Loading />;
49
+ } else if (error !== null) {
50
+ return <AlertError error={error} />;
51
+ }
52
+
53
+ const fitleredClasses = classes.filter((item) =>
54
+ item.uri.toLowerCase().includes(fitlerClassLabel.toLowerCase()),
55
+ );
56
+
57
+ const metaClasses: ListQueryResult[] = [];
58
+ const mainClasses: ListQueryResult[] = [];
59
+ fitleredClasses.forEach((c) => {
60
+ if (isMetaClass(c.uri)) {
61
+ metaClasses.push(c);
62
+ } else {
63
+ mainClasses.push(c);
64
+ }
65
+ });
66
+
67
+ return (
68
+ <Stack spacing={1}>
69
+ <TextField
70
+ value={fitlerClassLabel}
71
+ onChange={(e) => setFilterClassLabel(e.target.value)}
72
+ type="text"
73
+ placeholder="Filter classes"
74
+ InputProps={{
75
+ endAdornment: (
76
+ <InputAdornment position="start">
77
+ <FilterAltIcon />
78
+ </InputAdornment>
79
+ ),
80
+ }}
81
+ />
82
+ <Box>
83
+ <Grid container spacing={2} columns={12}>
84
+ {mainClasses.map((item) => (
85
+ <ClassListItem
86
+ key={item.uri}
87
+ uri={item.uri}
88
+ instanceCount={item.nbInstances}
89
+ label={item.label}
90
+ />
91
+ ))}
92
+ </Grid>
93
+ </Box>
94
+ {metaClasses.length > 0 ? (
95
+ <Accordion>
96
+ <AccordionSummary expandIcon={<ArrowDownIcon />}>
97
+ <Typography>Meta Classes</Typography>
98
+ </AccordionSummary>
99
+ <AccordionDetails>
100
+ <Grid container spacing={2} columns={12}>
101
+ {metaClasses.map((item) => (
102
+ <ClassListItem
103
+ key={item.uri}
104
+ uri={item.uri}
105
+ instanceCount={item.nbInstances}
106
+ label={item.label}
107
+ />
108
+ ))}
109
+ </Grid>
110
+ </AccordionDetails>
111
+ </Accordion>
112
+ ) : null}
113
+ </Stack>
114
+ );
115
+ }
116
+
117
+ function ClassListItem({
118
+ uri,
119
+ instanceCount,
120
+ label,
121
+ }: {
122
+ uri: string;
123
+ instanceCount?: string;
124
+ label?: string;
125
+ }) {
126
+ const uriLink = useURILink(uri);
127
+
128
+ const Icon = getIconFromURI(uri);
129
+
130
+ return (
131
+ <Grid size={{ xs: 4 }} sx={{ flexFlow: 1 }}>
132
+ <Link
133
+ key={uri}
134
+ to={uriLink}
135
+ style={{
136
+ textDecoration: "none",
137
+ color: "inherit",
138
+ }}
139
+ >
140
+ <Card sx={{ minHeight: 80, display: "flex" }}>
141
+ <CardActionArea sx={{ display: "flex", flex: 1 }}>
142
+ <CardContent sx={{ display: "flex", flex: 1 }}>
143
+ <Stack
144
+ flex={1}
145
+ spacing={2}
146
+ direction={"row"}
147
+ alignItems={"center"}
148
+ >
149
+ <Icon sx={{ color: "GrayText" }} />
150
+ <Stack flex={1}>
151
+ <Typography variant="body1">
152
+ {label ?? prefixedNameFromUri(uri)}
153
+ </Typography>
154
+ {label ? (
155
+ <Typography
156
+ variant="caption"
157
+ color={"GrayText"}
158
+ >
159
+ {prefixedNameFromUri(uri)}
160
+ </Typography>
161
+ ) : null}
162
+ </Stack>
163
+ {instanceCount !== undefined ? (
164
+ <Chip size="small" label={instanceCount} />
165
+ ) : null}
166
+ </Stack>
167
+ </CardContent>
168
+ </CardActionArea>
169
+ </Card>
170
+ </Link>
171
+ </Grid>
172
+ );
173
+ }
@@ -0,0 +1,126 @@
1
+ import { BasicAuth, querySparql, type SparqlAuth } from "@logilab/sparqlutils";
2
+ import ExploreIcon from "@mui/icons-material/Explore";
3
+ import Autocomplete from "@mui/material/Autocomplete";
4
+ import Box from "@mui/material/Box";
5
+ import Button from "@mui/material/Button";
6
+ import Stack from "@mui/material/Stack";
7
+ import TextField from "@mui/material/TextField";
8
+ import * as React from "react";
9
+ import { useAuth } from "../context/AuthContext";
10
+ import { useEndpoint } from "../context/ConfigContext";
11
+ import { useNavigateWithParams } from "../hooks/useNavigateWithParams";
12
+ import { addToSavedEndpoints, getSavedEndpoints } from "../utils/utils";
13
+
14
+ async function checkServerValid(endpoint: string, auth?: SparqlAuth) {
15
+ try {
16
+ await querySparql(endpoint, `ASK { ?s ?p ?o }`, auth);
17
+ return true;
18
+ } catch {
19
+ return false;
20
+ }
21
+ }
22
+
23
+ export const EndpointForm = () => {
24
+ const [loading, setLoading] = React.useState(false);
25
+ const [error, setError] = React.useState<string | undefined>();
26
+ const savedEndpoints = getSavedEndpoints();
27
+ const { auth, setAuth } = useAuth();
28
+
29
+ const endpoint = useEndpoint();
30
+
31
+ const [endpointInput, setEndpointInput] = React.useState<string>(
32
+ endpoint ?? "",
33
+ );
34
+ const [username, setUsername] = React.useState(auth?.username ?? "");
35
+ const [password, setPassword] = React.useState(auth?.password ?? "");
36
+
37
+ const navigate = useNavigateWithParams();
38
+
39
+ React.useEffect(() => {
40
+ if (error) {
41
+ setError(undefined);
42
+ }
43
+ }, [error]);
44
+
45
+ return (
46
+ <form
47
+ onSubmit={async (e) => {
48
+ e.preventDefault();
49
+ setLoading(true);
50
+ const newAuth =
51
+ username && password
52
+ ? new BasicAuth(username, password)
53
+ : undefined;
54
+ const valid = await checkServerValid(endpointInput, newAuth);
55
+ setLoading(false);
56
+ if (!valid) {
57
+ setError("Invalid endpoint");
58
+ return;
59
+ }
60
+ addToSavedEndpoints(endpointInput);
61
+ setAuth(newAuth);
62
+ navigate("/", {
63
+ endpoint: endpointInput,
64
+ graphs: null,
65
+ });
66
+ }}
67
+ >
68
+ <Stack spacing={1} direction={"row"}>
69
+ <Stack spacing={1} style={{ flex: 1 }}>
70
+ <Autocomplete
71
+ disablePortal
72
+ id="combo-box-demo"
73
+ options={savedEndpoints}
74
+ freeSolo
75
+ value={endpoint}
76
+ onChange={(_, value) => setEndpointInput(value ?? "")}
77
+ renderInput={(params) => (
78
+ <TextField
79
+ {...params}
80
+ label="Sparql Endpoint"
81
+ onChange={(event) =>
82
+ setEndpointInput(event.target.value)
83
+ }
84
+ error={error !== undefined}
85
+ helperText={error}
86
+ required
87
+ />
88
+ )}
89
+ fullWidth
90
+ />
91
+ <Stack direction={"row"} spacing={1}>
92
+ <TextField
93
+ label="Username"
94
+ defaultValue={auth}
95
+ value={username}
96
+ onChange={(event) =>
97
+ setUsername(event.target.value)
98
+ }
99
+ fullWidth
100
+ />
101
+ <TextField
102
+ label="Password"
103
+ defaultValue={auth}
104
+ value={password}
105
+ type="password"
106
+ onChange={(event) =>
107
+ setPassword(event.target.value)
108
+ }
109
+ fullWidth
110
+ />
111
+ </Stack>
112
+ </Stack>
113
+ <Box>
114
+ <Button
115
+ variant="contained"
116
+ type="submit"
117
+ disabled={endpointInput === "" || loading}
118
+ startIcon={<ExploreIcon />}
119
+ >
120
+ Explore
121
+ </Button>
122
+ </Box>
123
+ </Stack>
124
+ </form>
125
+ );
126
+ };
@@ -0,0 +1,114 @@
1
+ import Alert from "@mui/material/Alert";
2
+ import Box from "@mui/material/Box";
3
+ import Checkbox from "@mui/material/Checkbox";
4
+ import CircularProgress from "@mui/material/CircularProgress";
5
+ import FormControlLabel from "@mui/material/FormControlLabel";
6
+ import List from "@mui/material/List";
7
+ import ListItemButton from "@mui/material/ListItemButton";
8
+ import ListItemText from "@mui/material/ListItemText";
9
+ import Switch from "@mui/material/Switch";
10
+ import Typography from "@mui/material/Typography";
11
+ import { useState } from "react";
12
+ import { useEndpoint, useSelectedGraphs } from "../context/ConfigContext";
13
+ import { useGraphs } from "../hooks/useGraphs";
14
+ import { useAddGraph, useRemoveGraph } from "../hooks/useNavigateWithParams";
15
+
16
+ const SHOW_ALL_KEY = "sparqlexplorer.graphs.showall";
17
+
18
+ function shouldShowAll() {
19
+ return window.localStorage.getItem(SHOW_ALL_KEY) === "1";
20
+ }
21
+
22
+ function saveShowAll(open: boolean) {
23
+ return window.localStorage.setItem(SHOW_ALL_KEY, open ? "1" : "0");
24
+ }
25
+
26
+ export function GraphSelector() {
27
+ const endpoint = useEndpoint();
28
+ const [showAll, setShowAll] = useState(shouldShowAll());
29
+ const { error, graphs, isLoaded } = useGraphs(showAll);
30
+
31
+ if (!endpoint) {
32
+ return null;
33
+ }
34
+
35
+ function updateShowAll(newValue: boolean) {
36
+ setShowAll(newValue);
37
+ saveShowAll(newValue);
38
+ }
39
+
40
+ return (
41
+ <Box>
42
+ <Box paddingLeft={1} paddingRight={1}>
43
+ <Typography variant="h6">Available Graphs</Typography>
44
+ </Box>
45
+ {isLoaded ? (
46
+ <>
47
+ <Box paddingLeft={1} paddingRight={1}>
48
+ <FormControlLabel
49
+ control={
50
+ <Switch
51
+ checked={showAll}
52
+ onChange={() => updateShowAll(!showAll)}
53
+ />
54
+ }
55
+ label="Show all"
56
+ />
57
+ </Box>
58
+ <GraphList graphs={graphs} error={error} />
59
+ </>
60
+ ) : (
61
+ <Box paddingTop={5} display={"flex"} justifyContent={"center"}>
62
+ <CircularProgress />
63
+ </Box>
64
+ )}
65
+ </Box>
66
+ );
67
+ }
68
+
69
+ function GraphList({
70
+ graphs,
71
+ error,
72
+ }: {
73
+ graphs: Array<{ graph: string }>;
74
+ error: string | null;
75
+ }) {
76
+ const selectedGraphs = useSelectedGraphs();
77
+ const addGraph = useAddGraph();
78
+ const removeGraph = useRemoveGraph();
79
+
80
+ if (error) {
81
+ return <Alert severity="error">{error}</Alert>;
82
+ }
83
+ if (graphs.length === 0) {
84
+ return <Typography color={"GrayText"}>No graphs found</Typography>;
85
+ }
86
+
87
+ return (
88
+ <List>
89
+ {graphs.map((g) => {
90
+ const selected = selectedGraphs.includes(g.graph);
91
+ return (
92
+ <ListItemButton
93
+ key={g.graph}
94
+ onClick={() => {
95
+ if (selected) {
96
+ removeGraph(g.graph);
97
+ } else {
98
+ addGraph(g.graph);
99
+ }
100
+ }}
101
+ disabled={
102
+ selected &&
103
+ g.graph === "default" &&
104
+ selectedGraphs.length === 1
105
+ }
106
+ >
107
+ <Checkbox checked={selected} />
108
+ <ListItemText primary={g.graph} />
109
+ </ListItemButton>
110
+ );
111
+ })}
112
+ </List>
113
+ );
114
+ }