@logilab/sparqlexplorer 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/LICENSE +661 -0
  2. package/README.md +73 -0
  3. package/build/src/lib/App.d.ts +6 -0
  4. package/build/src/lib/components/ClassList.d.ts +9 -0
  5. package/build/src/lib/components/EndpointForm.d.ts +1 -0
  6. package/build/src/lib/components/GraphSelector.d.ts +1 -0
  7. package/build/src/lib/components/HomePage.d.ts +1 -0
  8. package/build/src/lib/components/SearchPage.d.ts +1 -0
  9. package/build/src/lib/components/UriPage.d.ts +1 -0
  10. package/build/src/lib/components/UtilsComponents.d.ts +11 -0
  11. package/build/src/lib/components/ViewSelector.d.ts +2 -0
  12. package/build/src/lib/components/Yasgui.d.ts +6 -0
  13. package/build/src/lib/components/YasrTableResults.d.ts +55 -0
  14. package/build/src/lib/components/layout/DrawerContent.d.ts +4 -0
  15. package/build/src/lib/components/layout/Footer.d.ts +1 -0
  16. package/build/src/lib/components/layout/Layout.d.ts +2 -0
  17. package/build/src/lib/components/layout/Navbar.d.ts +4 -0
  18. package/build/src/lib/components/uri/URIDefaultView.d.ts +6 -0
  19. package/build/src/lib/components/uri/URIWithSelectedView.d.ts +7 -0
  20. package/build/src/lib/context/AuthContext.d.ts +11 -0
  21. package/build/src/lib/context/ConfigContext.d.ts +15 -0
  22. package/build/src/lib/context/ViewsContext.d.ts +14 -0
  23. package/build/src/lib/hooks/useClasses.d.ts +6 -0
  24. package/build/src/lib/hooks/useGraphs.d.ts +8 -0
  25. package/build/src/lib/hooks/useNavigateWithParams.d.ts +5 -0
  26. package/build/src/lib/hooks/useParams.d.ts +1 -0
  27. package/build/src/lib/hooks/useURIData.d.ts +21 -0
  28. package/build/src/lib/hooks/useURILink.d.ts +1 -0
  29. package/build/src/lib/index.d.ts +8 -0
  30. package/build/src/lib/public-path.d.ts +1 -0
  31. package/build/src/lib/routes/Home.d.ts +1 -0
  32. package/build/src/lib/routes/Search.d.ts +1 -0
  33. package/build/src/lib/routes/Uri.d.ts +1 -0
  34. package/build/src/lib/routes/Yasgui.d.ts +1 -0
  35. package/build/src/lib/setupTests.d.ts +1 -0
  36. package/build/src/lib/utils/getIconFromURI.d.ts +3 -0
  37. package/build/src/lib/utils/utils.d.ts +24 -0
  38. package/build/src/lib/yasgui-utils/Storage.d.ts +16 -0
  39. package/build/src/lib/yasgui-utils/index.d.ts +16 -0
  40. package/build/static/js/lib.js +285 -0
  41. package/build/static/js/lib.js.LICENSE.txt +71 -0
  42. package/build/static/js/lib.js.map +1 -0
  43. package/package.json +73 -0
  44. package/src/app/index.css +23 -0
  45. package/src/app/index.tsx +28 -0
  46. package/src/app/templates/constants.js +1 -0
  47. package/src/app/templates/index.hbs +18 -0
  48. package/src/lib/App.css +83 -0
  49. package/src/lib/App.tsx +31 -0
  50. package/src/lib/components/ClassList.tsx +173 -0
  51. package/src/lib/components/EndpointForm.tsx +126 -0
  52. package/src/lib/components/GraphSelector.tsx +114 -0
  53. package/src/lib/components/HomePage.tsx +51 -0
  54. package/src/lib/components/SearchPage.tsx +211 -0
  55. package/src/lib/components/UriPage.tsx +158 -0
  56. package/src/lib/components/UtilsComponents.tsx +54 -0
  57. package/src/lib/components/ViewSelector.css +22 -0
  58. package/src/lib/components/ViewSelector.tsx +78 -0
  59. package/src/lib/components/Yasgui.tsx +127 -0
  60. package/src/lib/components/YasrTableResults.ts +529 -0
  61. package/src/lib/components/layout/DrawerContent.tsx +55 -0
  62. package/src/lib/components/layout/Footer.tsx +32 -0
  63. package/src/lib/components/layout/Layout.tsx +103 -0
  64. package/src/lib/components/layout/Navbar.tsx +231 -0
  65. package/src/lib/components/uri/URIDefaultView.tsx +392 -0
  66. package/src/lib/components/uri/URIWithSelectedView.tsx +31 -0
  67. package/src/lib/context/AuthContext.tsx +32 -0
  68. package/src/lib/context/ConfigContext.tsx +50 -0
  69. package/src/lib/context/ViewsContext.tsx +53 -0
  70. package/src/lib/hooks/useClasses.ts +48 -0
  71. package/src/lib/hooks/useGraphs.ts +67 -0
  72. package/src/lib/hooks/useNavigateWithParams.tsx +97 -0
  73. package/src/lib/hooks/useParams.tsx +8 -0
  74. package/src/lib/hooks/useURIData.ts +180 -0
  75. package/src/lib/hooks/useURILink.ts +7 -0
  76. package/src/lib/index.tsx +9 -0
  77. package/src/lib/public-path.ts +3 -0
  78. package/src/lib/routes/Home.tsx +13 -0
  79. package/src/lib/routes/Search.tsx +13 -0
  80. package/src/lib/routes/Uri.tsx +10 -0
  81. package/src/lib/routes/Yasgui.tsx +13 -0
  82. package/src/lib/setupTests.ts +5 -0
  83. package/src/lib/types.d.ts +6 -0
  84. package/src/lib/utils/getIconFromURI.ts +32 -0
  85. package/src/lib/utils/prefixInverted.json +2445 -0
  86. package/src/lib/utils/utils.ts +131 -0
  87. package/src/lib/yasgui-utils/Storage.ts +117 -0
  88. package/src/lib/yasgui-utils/index.ts +66 -0
@@ -0,0 +1,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
+ }