Skip to content

Commit

Permalink
Merge pull request #79 from ulitol97/master
Browse files Browse the repository at this point in the history
Store application data between pages
  • Loading branch information
ulitol97 authored Feb 16, 2022
2 parents 8b5fb96 + 65dc9e6 commit eba7a7f
Show file tree
Hide file tree
Showing 45 changed files with 671 additions and 262 deletions.
12 changes: 7 additions & 5 deletions src/API.js
Original file line number Diff line number Diff line change
Expand Up @@ -364,7 +364,7 @@ class API {
Input some Shape Expression (ShEx) and select its format, as well as
a target validation engine and format to have your schema converted
</p>
<p>
<span>
Several target engines are supported, including:
<ul>
<li>ShEx and SHACL for schema to schema conversions</li>
Expand All @@ -389,7 +389,7 @@ class API {
for schema to forms conversions
</li>
</ul>
</p>
</span>
<p>
{API.engines.shex} to {API.engines.shacl} is still unsupported
</p>
Expand Down Expand Up @@ -418,7 +418,7 @@ class API {
Input a SHACL schema and select its format/engine, as well as a
target validation engine and format to have your schema converted
</p>
<p>
<span>
Several target engines are supported, including:
<ul>
<li>
Expand Down Expand Up @@ -461,12 +461,12 @@ class API {
for WESO's ShEx/SHACL API
</li>
</ul>
</p>
</span>
</>
),

shapeMapInfo:
"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",
"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",
},

dataTabs: {
Expand Down Expand Up @@ -637,6 +637,8 @@ class API {

// ID of the results container for any operation
static resultsId = "results-container";
// Key for storing session temporary data in session storage
static sessionStorageDataKey = "applicationData";
}

export default API;
11 changes: 9 additions & 2 deletions src/App.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,19 @@
import React from "react";
import React, { useState } from "react";
import Container from "react-bootstrap/Container";
import "./App.css";
import {
initialApplicationContext
} from "./context/ApplicationContext";
import ApplicationProvider from "./context/ApplicationProvider";
import Routes from "./Routes.js";

function App() {
const [appContext, setAppContext] = useState(initialApplicationContext);
return (
<Container fluid={true}>
<Routes />
<ApplicationProvider>
<Routes />
</ApplicationProvider>
</Container>
);
}
Expand Down
10 changes: 6 additions & 4 deletions src/components/ByText.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ import Code from "./Code";

function ByText(props) {
// Pre-process the text sent down to the text container
const textContent = props.textAreaValue?.trim();
const textContent = props.fromParams
? props.textAreaValue?.trim()
: props.textAreaValue;

function handleChange(value, y, change) {
props.handleByTextChange && props.handleByTextChange(value, y, change);
Expand All @@ -24,20 +26,20 @@ function ByText(props) {
Use a generic <Code> element with text by default */}
{textFormat == API.formats.turtle.toLowerCase() ? (
<TurtleForm
value={textContent}
onChange={handleChange}
engine={props.textEngine}
setCodeMirror={props.setCodeMirror}
fromParams={props.fromParams}
resetFromParams={props.resetFromParams}
value={textContent}
options={{ placeholder: props.placeholder, ...props.options }}
/>
) : textFormat == API.formats.shexc.toLowerCase() ? (
<ShexForm
value={textContent}
onChange={handleChange}
setCodeMirror={props.setCodeMirror}
fromParams={props.fromParams}
resetFromParams={props.resetFromParams}
value={textContent}
options={{ placeholder: props.placeholder, ...props.options }}
/>
) : (
Expand Down
2 changes: 1 addition & 1 deletion src/components/Code.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ function Code(props) {
useEffect(() => {
if (editor) {
if (props.fromParams) {
editor.setValue(props.value);
props.value && editor.setValue(props.value);
props.resetFromParams && props.resetFromParams();
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/components/InputTabs.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ function InputTabs(props) {
return (
<Form.Group>
<Form.Label style={{ fontWeight: "bold" }}>{props.name}</Form.Label>
<Tabs activeKey={activeSource} id="dataTabs" onSelect={handleTabChange}>
<Tabs activeKey={activeSource} id="dataTabs" onSelect={handleTabChange} mountOnEnter={true}>
<Tab eventKey={API.sources.byText} title="Text">
<ByText
name={props.byTextName}
Expand Down
4 changes: 4 additions & 0 deletions src/components/InputTabsWithFormat.js
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,11 @@ InputTabsWithFormat.defaultProps = {
byUrlName: "",
byUrlPlaceholder: "",
byFileName: "",
fromParams: false,
nameFormat: API.texts.dataTabs.formatHeader,

textAreaValue: "",
urlValue: "",
};

export default InputTabsWithFormat;
2 changes: 1 addition & 1 deletion src/components/PageHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const PageHeader = ({ title, details }) => {

PageHeader.propTypes = {
title: PropTypes.string.isRequired,
details: PropTypes.string,
details: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
};

export default PageHeader;
14 changes: 13 additions & 1 deletion src/components/SelectEngine.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import PropTypes from "prop-types";
import React from "react";
import React, { useContext, useEffect } from "react";
import API from "../API";
import { ApplicationContext } from "../context/ApplicationContext";
import SelectFormat from "./SelectFormat";

export const allEngines = [
Expand Down Expand Up @@ -59,9 +60,20 @@ SelectEngine.defaultProps = {

// Shorthand for SelectEngine preconfigured with Shacl engines
export function SelectSHACLEngine(props) {
// Get schema and its setter from context
const { shaclSchema: ctxShacl, setShaclSchema: setCtxShacl } = useContext(
ApplicationContext
);

// Change context when the contained schema changes
useEffect(() => {
setCtxShacl(props.shacl);
}, [props.shacl]);

return (
<SelectEngine
{...props}
selectedEngine={props.selectedEngine || ctxShacl.engine}
urlEngines={API.routes.server.schemaShaclEngines}
/>
);
Expand Down
24 changes: 24 additions & 0 deletions src/context/ApplicationContext.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { createContext } from "react";
import { InitialQuery } from "../query/Query";
import { InitialShacl } from "../shacl/Shacl";
import { InitialShapeMap } from "../shapeMap/ShapeMap";
import { InitialShex } from "../shex/Shex";
import { InitialUML } from "../uml/UML";

// Initial values in context
export const initialApplicationContext = {
// Array of data (merge uses 2 units of data and more data compound could be added in the future)
// See provider for the function to add new data
rdfData: [],
sparqlQuery: InitialQuery,
sparqlEndpoint: "",
shexSchema: InitialShex,
shaclSchema: InitialShacl,
validationEndpoint: "", // Optional endpoint shown in ShaclValidate
shapeMap: InitialShapeMap,
umlData: InitialUML,
};

// Shared context for storing the data the user is operating on
// and using it throughout the application (e.g.: for autifilling input forms when changing page)
export const ApplicationContext = createContext(initialApplicationContext);
141 changes: 141 additions & 0 deletions src/context/ApplicationProvider.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
import React, { useEffect, useReducer } from "react";
import API from "../API";
import { InitialData } from "../data/Data";
import {
ApplicationContext,
initialApplicationContext
} from "./ApplicationContext";

// Resort to session storage to persist session data
// Use Context API as middleware to parse and easily manipulate
// the data in session storage
// https://stackoverflow.com/a/62505656/9744696
const ApplicationProvider = ({ children }) => {
// Reducer types
const reducerTypes = Object.freeze({
rdf: "rdf",
addRdf: "addRdf",
sparqlQuery: "sparql",
sparqlEnpoint: "sparqlEndpoint",
shex: "shex",
shacl: "shacl",
validationEndpoint: "validationEndpoint",
shapeMap: "shapeMap",
uml: "uml",
});

// Reducer function
function applicationDataReducer(state, { type, value }) {
// Last trap for nullish values
if (!value) return state;

// Trim text and URLs before storing
const trimBeforeStore = (item) => ({
...item,
textArea: item.textArea.trim(),
url: item.url.trim(),
});

const finalValue = Array.isArray(value)
? value.map(trimBeforeStore)
: typeof value === "object"
? trimBeforeStore(value)
: typeof value === "string"
? value.trim()
: value;

switch (type) {
case reducerTypes.rdf:
return { ...state, rdfData: finalValue };
case reducerTypes.addRdf:
return {
...state,
rdfData: [...state.rdfData, finalValue],
};
case reducerTypes.sparqlQuery:
return { ...state, sparqlQuery: finalValue };
case reducerTypes.sparqlEnpoint:
return { ...state, sparqlEndpoint: finalValue };
case reducerTypes.shex:
return { ...state, shexSchema: finalValue };
case reducerTypes.shacl:
return { ...state, shaclSchema: finalValue };
case reducerTypes.validationEndpoint:
return { ...state, validationEndpoint: finalValue };
case reducerTypes.shapeMap:
return { ...state, shapeMap: finalValue };
case reducerTypes.uml:
return { ...state, umlData: finalValue };
default:
return state;
}
}

// Reducer to handle the application data
const [applicationData, dispatch] = useReducer(
applicationDataReducer,
getLocalStorage(API.sessionStorageDataKey, initialApplicationContext)
);

function setLocalStorage(key, value) {
try {
window.localStorage.setItem(key, JSON.stringify(value));
} catch (e) {
// catch possible errors
}
}

function getLocalStorage(key, initialValue) {
try {
const value = window.localStorage.getItem(key);
return value ? JSON.parse(value) : initialValue;
} catch (e) {
// if error, return initial value
return initialValue;
}
}

// Update session data when context data changed
useEffect(() => {
setLocalStorage(API.sessionStorageDataKey, applicationData);
}, [applicationData]);

return (
<ApplicationContext.Provider
value={{
...applicationData,
// Granular control over the data to be updated
setRdfData: (rdfData) =>
dispatch({ type: reducerTypes.rdf, value: rdfData }),
addRdfData: (rdfData) => {
const newIndex = applicationData.rdfData.length; // new Data index based on current context data
// New data object, use InitialData if no data was provided
const newData = {
index: newIndex,
...(rdfData || InitialData),
};
dispatch({ type: reducerTypes.addRdf, value: newData }); // Update state and return new data
return newData;
},
setSparqlQuery: (sparqlQuery) =>
dispatch({ type: reducerTypes.sparqlQuery, value: sparqlQuery }),
setSparqlEndpoint: (sparqlEndpoint) =>
dispatch({ type: reducerTypes.sparqlEnpoint, value: sparqlEndpoint }),
setShexSchema: (shexSchema) =>
dispatch({ type: reducerTypes.shex, value: shexSchema }),
setShaclSchema: (shaclSchema) =>
dispatch({ type: reducerTypes.shacl, value: shaclSchema }),
setValidationEndpoint: (vEndpoint) =>
dispatch({ type: reducerTypes.validationEndpoint, value: vEndpoint }),
setShapeMap: (shapeMap) =>
dispatch({ type: reducerTypes.shapeMap, value: shapeMap }),
setUmlData: (umlData) =>
dispatch({ type: reducerTypes.uml, value: umlData }),
}}
>
{children}
</ApplicationContext.Provider>
);
};

export default ApplicationProvider;
10 changes: 8 additions & 2 deletions src/data/Data.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ export function mkDataTabs(
setData({ ...data, activeSource: value });
}
function handleDataByTextChange(value, y, change) {
onTextChange(value, y, change);
// onTextChange(value, y, change);
setData({ ...data, textArea: value });
}
function handleDataUrlChange(value) {
Expand All @@ -92,11 +92,16 @@ export function mkDataTabs(
function handleInferenceChange(value) {
setData({ ...data, inference: value });
}

function handleCodeMirrorChange(value) {
setData({ ...data, codeMirror: value });
}
const resetParams = () => setData({ ...data, fromParams: false });

return (
<React.Fragment>
<DataTabs
data={data}
name={name}
subname={subname}
activeSource={data.activeSource}
Expand All @@ -108,11 +113,12 @@ export function mkDataTabs(
handleFileUpload={handleDataFileUpload}
selectedFormat={data.format}
handleDataFormatChange={handleDataFormatChange}
setCodeMirror={(cm) => setData({ ...data, codeMirror: cm })}
setCodeMirror={handleCodeMirrorChange}
fromParams={data.fromParams}
resetFromParams={resetParams}
/>
<SelectInferenceEngine
data={data}
handleInferenceChange={handleInferenceChange}
selectedInference={data.inference || InitialData.inference}
fromParams={data.fromParams}
Expand Down
Loading

0 comments on commit eba7a7f

Please sign in to comment.