Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/multistep-form/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import HomePage from "./pages/Home";
import { Alert, Button, Link, GridContainer } from "@trussworks/react-uswds";
import { Application } from "@nmfs-radfish/react-radfish";

function App() {
function App({ application }) {
return (
<Application>
<Application application={application}>
<GridContainer>
<h1>Multi-Step</h1>
<FormInfoAnnotation />
Expand Down
26 changes: 13 additions & 13 deletions examples/multistep-form/src/index.jsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
import React from "react";
import ReactDOM from "react-dom/client";
import "./styles/theme.css";
import App from "./App";
import { ErrorBoundary, OfflineStorageWrapper } from "@nmfs-radfish/react-radfish";
import MultiStepFormApplication from "./App";
import { Application, IndexedDBMethod } from "@nmfs-radfish/radfish";
import { ErrorBoundary } from "@nmfs-radfish/react-radfish";

const offlineStorageConfig = {
type: "indexedDB",
name: import.meta.env.VITE_INDEXED_DB_NAME,
version: import.meta.env.VITE_INDEXED_DB_VERSION,
stores: {
formData: "uuid, fullName, email, city, state, zipcode",
},
};
const app = new Application({
storage: new IndexedDBMethod(
import.meta.env.VITE_INDEXED_DB_NAME,
import.meta.env.VITE_INDEXED_DB_VERSION,
{
formData: "uuid, fullName, email, city, state, zipcode",
},
),
});

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
<ErrorBoundary>
<React.StrictMode>
<OfflineStorageWrapper config={offlineStorageConfig}>
<App />
</OfflineStorageWrapper>
<MultiStepFormApplication application={app} />
</React.StrictMode>
</ErrorBoundary>,
);
5 changes: 3 additions & 2 deletions examples/on-device-storage/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,10 @@ import { Routes, Route, BrowserRouter as Router } from "react-router-dom";
import { Application } from "@nmfs-radfish/react-radfish";
import HomePage from "./pages/Home";

const App = () => {
const App = ({application}) => {

return (
<Application>
<Application application={application}>
<div className="App grid-container">
<Router>
<Routes>
Expand Down
29 changes: 18 additions & 11 deletions examples/on-device-storage/src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,10 @@ import React from "react";
import ReactDOM from "react-dom/client";
import "./styles/theme.css";
import App from "./App";
import { OfflineStorageWrapper } from "@nmfs-radfish/react-radfish";
import { ErrorBoundary } from "@nmfs-radfish/react-radfish";
import { Application, IndexedDBMethod } from "@nmfs-radfish/radfish";

const offlineStorageConfig = {
type: "indexedDB",
name: import.meta.env.VITE_INDEXED_DB_NAME,
version: import.meta.env.VITE_INDEXED_DB_VERSION,
stores: {
Expand All @@ -17,14 +16,22 @@ const offlineStorageConfig = {
},
};

const app = new Application({
storage: new IndexedDBMethod(
offlineStorageConfig.name,
offlineStorageConfig.version,
offlineStorageConfig.stores,
),
});

const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
<ErrorBoundary>
<React.StrictMode>
<OfflineStorageWrapper config={offlineStorageConfig}>
<App />
</OfflineStorageWrapper>
</React.StrictMode>
</ErrorBoundary>
);
app.on("ready", () => {
root.render(
<ErrorBoundary>
<React.StrictMode>
<App application={app} />
</React.StrictMode>
</ErrorBoundary>,
);
});
16 changes: 2 additions & 14 deletions examples/simple-table/src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,25 +2,13 @@ import React from "react";
import ReactDOM from "react-dom/client";
import "./styles/theme.css";
import App from "./App";
import { ErrorBoundary, OfflineStorageWrapper } from "@nmfs-radfish/react-radfish";

const offlineStorageConfig = {
type: "indexedDB",
name: import.meta.env.VITE_INDEXED_DB_NAME,
version: import.meta.env.VITE_INDEXED_DB_VERSION,
stores: {
formData: "uuid, image, species, computedPrice, isDraft",
},
};

import { ErrorBoundary } from "@nmfs-radfish/react-radfish";
const root = ReactDOM.createRoot(document.getElementById("root"));

root.render(
<ErrorBoundary>
<React.StrictMode>
<OfflineStorageWrapper config={offlineStorageConfig}>
<App />
</OfflineStorageWrapper>
<App />
</React.StrictMode>
</ErrorBoundary>,
);
69 changes: 69 additions & 0 deletions packages/radfish/Application.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { Application, IndexedDBMethod, LocalStorageMethod } from './index';

describe ('Application', () => {
describe('storage', () => {
it('should return the storage method', () => {
// IndexedDB Storage application
const indexedDBMethod = new IndexedDBMethod(
"test",
1,
{
formData: "uuid, fullName, email, phoneNumber, numberOfFish, species, computedPrice, isDraft",
species: "name, price",
homebaseData: "KEY, REPORT_TYPE, SORT_KEY, TRIP_TYPE, VALUE",
},
);
const indexedDBApplication = new Application({
storage: indexedDBMethod,
});
expect (indexedDBApplication.storage).toEqual(indexedDBMethod);

// Local Storage application
const localStorageMethod = new LocalStorageMethod(
"test",
{
formData: "uuid, fullName, email, phoneNumber, numberOfFish, species, computedPrice, isDraft",
species: "name, price",
homebaseData: "KEY, REPORT_TYPE, SORT_KEY, TRIP_TYPE, VALUE",
},
);
const localStorageApplication = new Application({
storage: localStorageMethod,
});
expect(localStorageApplication.storage).toEqual(localStorageMethod);
});

it('should return the storage method using a configuration object', function () {
const indexedDBApplication = new Application(
{
storage: {
type: "indexedDB",
name: "test",
version: 1,
stores: {
formData: "uuid, fullName, email, phoneNumber, numberOfFish, species, computedPrice, isDraft",
species: "name, price",
homebaseData: "KEY, REPORT_TYPE, SORT_KEY, TRIP_TYPE, VALUE",
}
}
}
)
expect(indexedDBApplication.storage).toBeInstanceOf(IndexedDBMethod);

const localStorageApplication = new Application(
{
storage: {
type: "localStorage",
name: "test",
stores: {
formData: "uuid, fullName, email, phoneNumber, numberOfFish, species, computedPrice, isDraft",
species: "name, price",
homebaseData: "KEY, REPORT_TYPE, SORT_KEY, TRIP_TYPE, VALUE",
}
}
}
)
expect(localStorageApplication.storage).toBeInstanceOf(LocalStorageMethod);
});
});
});
29 changes: 29 additions & 0 deletions packages/radfish/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { setupWorker } from "msw/browser";
import { StorageMethod, IndexedDBMethod, LocalStorageMethod } from "./on-device-storage/storage";

class EventEmitter extends EventTarget {}

Expand All @@ -14,6 +15,34 @@ export class Application {
this._dispatch("init");
}

get storage() {
if (!this._options.storage) {
return null;
}

if (!(this._options.storage instanceof StorageMethod)) {
console.warn('Please update the storage method to be an instance of StorageMethod');

switch (this._options.storage?.type) {
case "indexedDB": {
return new IndexedDBMethod(
this._options.storage.name,
this._options.storage.version,
this._options.storage.stores
);
}
case "localStorage": {
return new LocalStorageMethod(this._options.storage.name);
}
default: {
throw new Error(`Invalid storage method type: ${this._options.storage.type}`);
}
}
}

return this._options.storage;
}

on(event, callback) {
return this.emitter.addEventListener(event, callback);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ export class LocalStorageMethod extends StorageMethod {
constructor(key) {
super();
this.key = key;
this.store =
localStorage.getItem(this.key) ||
if (!localStorage.hasOwnProperty(this.key)) {
console.warn(`Initializing local storage for key: ${this.key}`);
localStorage.setItem(this.key, JSON.stringify([]));
};
this.store = localStorage.getItem(this.key);
}

/**
Expand Down
8 changes: 8 additions & 0 deletions packages/radfish/vitest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { defineConfig } from "vite";

export default defineConfig({
test: {
globals: true,
environment: "jsdom",
},
});
23 changes: 17 additions & 6 deletions packages/react-radfish/Application/index.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Toast } from "../alerts";
import { createContext, useEffect, useContext, useRef } from "react";
import { useOfflineStatus, useToasts, dispatchToast } from "../hooks";
import { OfflineStorageWrapper } from "../OfflineStorage";

const ApplicationContext = createContext();

Expand Down Expand Up @@ -31,12 +32,22 @@ function ApplicationComponent(props) {
);
}

export function Application({ application, children }) {
return (
<ApplicationContext.Provider value={application}>
<ApplicationComponent>{children}</ApplicationComponent>
</ApplicationContext.Provider>
);
export function Application({ application = null, children }) {
if (application?.storage) {
return (
<ApplicationContext.Provider value={application}>
<OfflineStorageWrapper>
<ApplicationComponent>{children}</ApplicationComponent>
</OfflineStorageWrapper>
</ApplicationContext.Provider>
);
} else {
return (
<ApplicationContext.Provider value={application}>
<ApplicationComponent>{children}</ApplicationComponent>
</ApplicationContext.Provider>
);
}
}

export const useApplication = () => {
Expand Down
21 changes: 14 additions & 7 deletions packages/react-radfish/OfflineStorage/index.jsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import { createContext, useContext } from "react";
import { IndexedDBMethod, LocalStorageMethod, StorageModelFactory } from "@nmfs-radfish/radfish";
import { StorageModelFactory } from "@nmfs-radfish/radfish";
import { useApplication } from "../Application";

export const OfflineStorageContext = createContext();

export const OfflineStorageWrapper = ({ children, config }) => {
const storageMethod =
config.type === "indexedDB"
? new IndexedDBMethod(config.name, config.version, config.stores)
: new LocalStorageMethod(config.name);
export const OfflineStorageWrapper = ({ children }) => {
const application = useApplication();

if (!application?.storage) {
throw new Error(
"OfflineStorageWrapper must be used within an Application component with a storage method configured.",
);
}

const storageMethod = application.storage;
const storageModel = StorageModelFactory.createModel(storageMethod);

function createOfflineData(tableName, data) {
Expand Down Expand Up @@ -43,7 +48,9 @@ export const OfflineStorageWrapper = ({ children, config }) => {
export const useOfflineStorage = () => {
const context = useContext(OfflineStorageContext);
if (!context) {
throw new Error("useOfflineStorage must be used within an OfflineStorageWrapper");
throw new Error(
"useOfflineStorage must be used within an OfflineStorageWrapper. Please make sure a storage method has been configured.",
);
}
return context;
};
2 changes: 1 addition & 1 deletion templates/react-javascript/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
"start": "vite",
"build": "vite build",
"prebuild": "rm -rf dist",
"test": "vitest --exclude './src/__tests__/e2e/**'",
"test": "echo \"Error: no test specified\" && exit 1",
"test:e2e": "concurrently --kill-others --success first \"npm start\" \"vitest './src/__tests__/e2e/integration.e2e.test.jsx'\"",
"lint": "eslint src",
"lint:fix": "eslint --fix src",
Expand Down
6 changes: 3 additions & 3 deletions templates/react-javascript/src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,17 @@ import {

import HomePage from "./pages/Home";

function App() {
function App({ application }) {
const [isExpanded, setExpanded] = useState(false);
return (
<Application>
<Application application={application}>
<a className="usa-skipnav" href="#main-content">
Skip to main content
</a>
<main id="main-content">
<BrowserRouter>
<Header
basic={true}
basic
showMobileOverlay={isExpanded}
className="header-container"
>
Expand Down
4 changes: 2 additions & 2 deletions templates/react-javascript/src/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,10 @@ const app = new Application({
},
});

app.on("ready", () => {
app.on("ready", async () => {
root.render(
<React.StrictMode>
<App />
<App application={app} />
</React.StrictMode>,
);
});
Loading