Skip to content

Commit eba7a7f

Browse files
authored
Merge pull request #79 from ulitol97/master
Store application data between pages
2 parents 8b5fb96 + 65dc9e6 commit eba7a7f

45 files changed

Lines changed: 671 additions & 262 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

src/API.js

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -364,7 +364,7 @@ class API {
364364
Input some Shape Expression (ShEx) and select its format, as well as
365365
a target validation engine and format to have your schema converted
366366
</p>
367-
<p>
367+
<span>
368368
Several target engines are supported, including:
369369
<ul>
370370
<li>ShEx and SHACL for schema to schema conversions</li>
@@ -389,7 +389,7 @@ class API {
389389
for schema to forms conversions
390390
</li>
391391
</ul>
392-
</p>
392+
</span>
393393
<p>
394394
{API.engines.shex} to {API.engines.shacl} is still unsupported
395395
</p>
@@ -418,7 +418,7 @@ class API {
418418
Input a SHACL schema and select its format/engine, as well as a
419419
target validation engine and format to have your schema converted
420420
</p>
421-
<p>
421+
<span>
422422
Several target engines are supported, including:
423423
<ul>
424424
<li>
@@ -461,12 +461,12 @@ class API {
461461
for WESO's ShEx/SHACL API
462462
</li>
463463
</ul>
464-
</p>
464+
</span>
465465
</>
466466
),
467467

468468
shapeMapInfo:
469-
"Input a ShapeMap (by text, by pointing to a URL with the contents or by file) and select its format to validate its contents. Note that whole URIs must be used as there is no data/schema from which a prefix map can be retrieved",
469+
"Input a ShapeMap (by text, by pointing to a URL with the contents or by file) and select its format to validate its contents. Whole URIs must be used since there is no data/schema from which a prefix map can be retrieved",
470470
},
471471

472472
dataTabs: {
@@ -637,6 +637,8 @@ class API {
637637

638638
// ID of the results container for any operation
639639
static resultsId = "results-container";
640+
// Key for storing session temporary data in session storage
641+
static sessionStorageDataKey = "applicationData";
640642
}
641643

642644
export default API;

src/App.js

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,19 @@
1-
import React from "react";
1+
import React, { useState } from "react";
22
import Container from "react-bootstrap/Container";
33
import "./App.css";
4+
import {
5+
initialApplicationContext
6+
} from "./context/ApplicationContext";
7+
import ApplicationProvider from "./context/ApplicationProvider";
48
import Routes from "./Routes.js";
59

610
function App() {
11+
const [appContext, setAppContext] = useState(initialApplicationContext);
712
return (
813
<Container fluid={true}>
9-
<Routes />
14+
<ApplicationProvider>
15+
<Routes />
16+
</ApplicationProvider>
1017
</Container>
1118
);
1219
}

src/components/ByText.js

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,9 @@ import Code from "./Code";
99

1010
function ByText(props) {
1111
// Pre-process the text sent down to the text container
12-
const textContent = props.textAreaValue?.trim();
12+
const textContent = props.fromParams
13+
? props.textAreaValue?.trim()
14+
: props.textAreaValue;
1315

1416
function handleChange(value, y, change) {
1517
props.handleByTextChange && props.handleByTextChange(value, y, change);
@@ -24,20 +26,20 @@ function ByText(props) {
2426
Use a generic <Code> element with text by default */}
2527
{textFormat == API.formats.turtle.toLowerCase() ? (
2628
<TurtleForm
29+
value={textContent}
2730
onChange={handleChange}
28-
engine={props.textEngine}
31+
setCodeMirror={props.setCodeMirror}
2932
fromParams={props.fromParams}
3033
resetFromParams={props.resetFromParams}
31-
value={textContent}
3234
options={{ placeholder: props.placeholder, ...props.options }}
3335
/>
3436
) : textFormat == API.formats.shexc.toLowerCase() ? (
3537
<ShexForm
38+
value={textContent}
3639
onChange={handleChange}
3740
setCodeMirror={props.setCodeMirror}
3841
fromParams={props.fromParams}
3942
resetFromParams={props.resetFromParams}
40-
value={textContent}
4143
options={{ placeholder: props.placeholder, ...props.options }}
4244
/>
4345
) : (

src/components/Code.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ function Code(props) {
3131
useEffect(() => {
3232
if (editor) {
3333
if (props.fromParams) {
34-
editor.setValue(props.value);
34+
props.value && editor.setValue(props.value);
3535
props.resetFromParams && props.resetFromParams();
3636
}
3737
}

src/components/InputTabs.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ function InputTabs(props) {
2424
return (
2525
<Form.Group>
2626
<Form.Label style={{ fontWeight: "bold" }}>{props.name}</Form.Label>
27-
<Tabs activeKey={activeSource} id="dataTabs" onSelect={handleTabChange}>
27+
<Tabs activeKey={activeSource} id="dataTabs" onSelect={handleTabChange} mountOnEnter={true}>
2828
<Tab eventKey={API.sources.byText} title="Text">
2929
<ByText
3030
name={props.byTextName}

src/components/InputTabsWithFormat.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,11 @@ InputTabsWithFormat.defaultProps = {
9999
byUrlName: "",
100100
byUrlPlaceholder: "",
101101
byFileName: "",
102+
fromParams: false,
102103
nameFormat: API.texts.dataTabs.formatHeader,
104+
105+
textAreaValue: "",
106+
urlValue: "",
103107
};
104108

105109
export default InputTabsWithFormat;

src/components/PageHeader.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ const PageHeader = ({ title, details }) => {
3232

3333
PageHeader.propTypes = {
3434
title: PropTypes.string.isRequired,
35-
details: PropTypes.string,
35+
details: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
3636
};
3737

3838
export default PageHeader;

src/components/SelectEngine.js

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import PropTypes from "prop-types";
2-
import React from "react";
2+
import React, { useContext, useEffect } from "react";
33
import API from "../API";
4+
import { ApplicationContext } from "../context/ApplicationContext";
45
import SelectFormat from "./SelectFormat";
56

67
export const allEngines = [
@@ -59,9 +60,20 @@ SelectEngine.defaultProps = {
5960

6061
// Shorthand for SelectEngine preconfigured with Shacl engines
6162
export function SelectSHACLEngine(props) {
63+
// Get schema and its setter from context
64+
const { shaclSchema: ctxShacl, setShaclSchema: setCtxShacl } = useContext(
65+
ApplicationContext
66+
);
67+
68+
// Change context when the contained schema changes
69+
useEffect(() => {
70+
setCtxShacl(props.shacl);
71+
}, [props.shacl]);
72+
6273
return (
6374
<SelectEngine
6475
{...props}
76+
selectedEngine={props.selectedEngine || ctxShacl.engine}
6577
urlEngines={API.routes.server.schemaShaclEngines}
6678
/>
6779
);

src/context/ApplicationContext.js

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { createContext } from "react";
2+
import { InitialQuery } from "../query/Query";
3+
import { InitialShacl } from "../shacl/Shacl";
4+
import { InitialShapeMap } from "../shapeMap/ShapeMap";
5+
import { InitialShex } from "../shex/Shex";
6+
import { InitialUML } from "../uml/UML";
7+
8+
// Initial values in context
9+
export const initialApplicationContext = {
10+
// Array of data (merge uses 2 units of data and more data compound could be added in the future)
11+
// See provider for the function to add new data
12+
rdfData: [],
13+
sparqlQuery: InitialQuery,
14+
sparqlEndpoint: "",
15+
shexSchema: InitialShex,
16+
shaclSchema: InitialShacl,
17+
validationEndpoint: "", // Optional endpoint shown in ShaclValidate
18+
shapeMap: InitialShapeMap,
19+
umlData: InitialUML,
20+
};
21+
22+
// Shared context for storing the data the user is operating on
23+
// and using it throughout the application (e.g.: for autifilling input forms when changing page)
24+
export const ApplicationContext = createContext(initialApplicationContext);

src/context/ApplicationProvider.js

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import React, { useEffect, useReducer } from "react";
2+
import API from "../API";
3+
import { InitialData } from "../data/Data";
4+
import {
5+
ApplicationContext,
6+
initialApplicationContext
7+
} from "./ApplicationContext";
8+
9+
// Resort to session storage to persist session data
10+
// Use Context API as middleware to parse and easily manipulate
11+
// the data in session storage
12+
// https://stackoverflow.com/a/62505656/9744696
13+
const ApplicationProvider = ({ children }) => {
14+
// Reducer types
15+
const reducerTypes = Object.freeze({
16+
rdf: "rdf",
17+
addRdf: "addRdf",
18+
sparqlQuery: "sparql",
19+
sparqlEnpoint: "sparqlEndpoint",
20+
shex: "shex",
21+
shacl: "shacl",
22+
validationEndpoint: "validationEndpoint",
23+
shapeMap: "shapeMap",
24+
uml: "uml",
25+
});
26+
27+
// Reducer function
28+
function applicationDataReducer(state, { type, value }) {
29+
// Last trap for nullish values
30+
if (!value) return state;
31+
32+
// Trim text and URLs before storing
33+
const trimBeforeStore = (item) => ({
34+
...item,
35+
textArea: item.textArea.trim(),
36+
url: item.url.trim(),
37+
});
38+
39+
const finalValue = Array.isArray(value)
40+
? value.map(trimBeforeStore)
41+
: typeof value === "object"
42+
? trimBeforeStore(value)
43+
: typeof value === "string"
44+
? value.trim()
45+
: value;
46+
47+
switch (type) {
48+
case reducerTypes.rdf:
49+
return { ...state, rdfData: finalValue };
50+
case reducerTypes.addRdf:
51+
return {
52+
...state,
53+
rdfData: [...state.rdfData, finalValue],
54+
};
55+
case reducerTypes.sparqlQuery:
56+
return { ...state, sparqlQuery: finalValue };
57+
case reducerTypes.sparqlEnpoint:
58+
return { ...state, sparqlEndpoint: finalValue };
59+
case reducerTypes.shex:
60+
return { ...state, shexSchema: finalValue };
61+
case reducerTypes.shacl:
62+
return { ...state, shaclSchema: finalValue };
63+
case reducerTypes.validationEndpoint:
64+
return { ...state, validationEndpoint: finalValue };
65+
case reducerTypes.shapeMap:
66+
return { ...state, shapeMap: finalValue };
67+
case reducerTypes.uml:
68+
return { ...state, umlData: finalValue };
69+
default:
70+
return state;
71+
}
72+
}
73+
74+
// Reducer to handle the application data
75+
const [applicationData, dispatch] = useReducer(
76+
applicationDataReducer,
77+
getLocalStorage(API.sessionStorageDataKey, initialApplicationContext)
78+
);
79+
80+
function setLocalStorage(key, value) {
81+
try {
82+
window.localStorage.setItem(key, JSON.stringify(value));
83+
} catch (e) {
84+
// catch possible errors
85+
}
86+
}
87+
88+
function getLocalStorage(key, initialValue) {
89+
try {
90+
const value = window.localStorage.getItem(key);
91+
return value ? JSON.parse(value) : initialValue;
92+
} catch (e) {
93+
// if error, return initial value
94+
return initialValue;
95+
}
96+
}
97+
98+
// Update session data when context data changed
99+
useEffect(() => {
100+
setLocalStorage(API.sessionStorageDataKey, applicationData);
101+
}, [applicationData]);
102+
103+
return (
104+
<ApplicationContext.Provider
105+
value={{
106+
...applicationData,
107+
// Granular control over the data to be updated
108+
setRdfData: (rdfData) =>
109+
dispatch({ type: reducerTypes.rdf, value: rdfData }),
110+
addRdfData: (rdfData) => {
111+
const newIndex = applicationData.rdfData.length; // new Data index based on current context data
112+
// New data object, use InitialData if no data was provided
113+
const newData = {
114+
index: newIndex,
115+
...(rdfData || InitialData),
116+
};
117+
dispatch({ type: reducerTypes.addRdf, value: newData }); // Update state and return new data
118+
return newData;
119+
},
120+
setSparqlQuery: (sparqlQuery) =>
121+
dispatch({ type: reducerTypes.sparqlQuery, value: sparqlQuery }),
122+
setSparqlEndpoint: (sparqlEndpoint) =>
123+
dispatch({ type: reducerTypes.sparqlEnpoint, value: sparqlEndpoint }),
124+
setShexSchema: (shexSchema) =>
125+
dispatch({ type: reducerTypes.shex, value: shexSchema }),
126+
setShaclSchema: (shaclSchema) =>
127+
dispatch({ type: reducerTypes.shacl, value: shaclSchema }),
128+
setValidationEndpoint: (vEndpoint) =>
129+
dispatch({ type: reducerTypes.validationEndpoint, value: vEndpoint }),
130+
setShapeMap: (shapeMap) =>
131+
dispatch({ type: reducerTypes.shapeMap, value: shapeMap }),
132+
setUmlData: (umlData) =>
133+
dispatch({ type: reducerTypes.uml, value: umlData }),
134+
}}
135+
>
136+
{children}
137+
</ApplicationContext.Provider>
138+
);
139+
};
140+
141+
export default ApplicationProvider;

0 commit comments

Comments
 (0)