@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
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>
|
package/src/lib/App.css
ADDED
|
@@ -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
|
+
}
|
package/src/lib/App.tsx
ADDED
|
@@ -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
|
+
}
|