Skip to content

Commit

Permalink
feat: implement splash screen
Browse files Browse the repository at this point in the history
If the `splashScreen.content` prop becomes unwieldy, we can move it to a separate config file.

Closes #65
  • Loading branch information
stdavis committed Aug 24, 2022
1 parent 3a959d2 commit de576fa
Show file tree
Hide file tree
Showing 9 changed files with 147 additions and 4 deletions.
5 changes: 5 additions & 0 deletions public/config.json
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,11 @@
"searchField": "NAME",
"serviceUrl": "https://gis.wfrc.org/arcgis/rest/services/General/ZoomToPlaceNames/FeatureServer/1"
},
"splashScreen": {
"enabled": true,
"title": "Splash Screen Title",
"content": "<p>Splash <b>Screen</b> Content</p>"
},
"translations": {
"en": {
"translation": {
Expand Down
25 changes: 25 additions & 0 deletions public/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
"outFields",
"projectInformation",
"sherlock",
"splashScreen",
"translations",
"webMapId"
],
Expand Down Expand Up @@ -407,6 +408,30 @@
}
}
},
"splashScreen": {
"title": "Splash Screen Config",
"description": "Configuration options for the splash screen",
"type": "object",
"additionalProperties": false,
"required": ["enabled"],
"properties": {
"enabled": {
"title": "Enabled",
"description": "Whether the splash screen is enabled",
"type": "boolean"
},
"title": {
"title": "Title",
"description": "The title of the splash screen",
"type": "string"
},
"content": {
"title": "Content",
"description": "The content of the splash screen",
"type": "string"
}
}
},
"translations": {
"title": "Translations",
"description": "Contains the translated strings used in the app. Falls back to `en` if there is no other translation. Most strings in the other configs can be translated by using this format: `trans:<key>`. For example: `trans:visionMapTitle`.",
Expand Down
4 changes: 3 additions & 1 deletion src/App.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ import MapWidgetContainer from './components/MapWidgetContainer';
import MapWidgetResizeHandle from './components/MapWidgetResizeHandle';
import ProjectInformation from './components/ProjectInformation';
import { MapServiceProvider, Sherlock } from './components/Sherlock';
import SplashScreen from './components/SplashScreen';
import useFilterReducer from './hooks/useFilterReducer';
import config from './services/config';
import { useSpecialTranslation } from './services/i18n';
import useFilterReducer from './services/useFilterReducer';

const queryClient = new QueryClient();

Expand Down Expand Up @@ -195,6 +196,7 @@ function App() {
<Sherlock {...sherlockConfig}></Sherlock>
</div>
</div>
<SplashScreen />
</QueryClientProvider>
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/bootstrap.scss
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ $nav-link-color: $gray-700;
// @import '../node_modules/bootstrap/scss/list-group';
@import '../node_modules/bootstrap/scss/close';
// @import '../node_modules/bootstrap/scss/toasts';
// @import '../node_modules/bootstrap/scss/modal';
@import '../node_modules/bootstrap/scss/modal';
// @import '../node_modules/bootstrap/scss/tooltip';
@import '../node_modules/bootstrap/scss/popover';
// @import '../node_modules/bootstrap/scss/carousel';
Expand Down
31 changes: 31 additions & 0 deletions src/components/SplashScreen.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { useState } from 'react';
import { Button, FormGroup, Input, Label, Modal, ModalBody, ModalFooter, ModalHeader } from 'reactstrap';
import useLocalStorage from '../hooks/useLocalStorage';
import config from '../services/config';

export default function SplashScreen() {
const [showOnLoad, setShowOnLoad] = useLocalStorage('wfrc-rtp-show-splash', true, true);
const [isOpen, setIsOpen] = useState(showOnLoad);

const toggle = () => setIsOpen(!isOpen);

return (
<Modal isOpen={isOpen} toggle={toggle} centered>
<ModalHeader toggle={toggle}>{config.splashScreen.title}</ModalHeader>
<ModalBody>
<div dangerouslySetInnerHTML={{ __html: config.splashScreen.content }}></div>
</ModalBody>
<ModalFooter className="d-flex flex-direction-row justify-content-between">
<FormGroup check inline>
<Label check>
<Input type="checkbox" onChange={(event) => setShowOnLoad(!event.target.checked)} checked={!showOnLoad} />
Don&apos;t Show This Again
</Label>
</FormGroup>
<Button color="primary" onClick={toggle}>
Dismiss
</Button>
</ModalFooter>
</Modal>
);
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { useImmerReducer } from 'use-immer';
import { addOrRemove } from '../components/utils';
import config from './config';
import config from '../services/config';

export function getQuery(state, geometryType, projectConfig) {
// phase is a numeric field
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest';
import config from './config';
import config from '../services/config';
import { getQuery, initialState, reducer } from './useFilterReducer';

const strip = (str) => str.replace(/\s/g, '');
Expand Down
18 changes: 18 additions & 0 deletions src/hooks/useLocalStorage.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import { useState } from 'react';

export default function (key, initialValue, parseWithJSON = false) {
const getValueFromLocalStorage = () => {
const value = localStorage.getItem(key);

return parseWithJSON ? JSON.parse(value) : value;
};

const [value, setValue] = useState(() => getValueFromLocalStorage() ?? initialValue);

const setNewValue = (newValue) => {
setValue(newValue);
localStorage.setItem(key, parseWithJSON ? JSON.stringify(newValue) : newValue);
};

return [value, setNewValue];
}
62 changes: 62 additions & 0 deletions src/hooks/useLocalStorage.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import useLocalStorage from './useLocalStorage';

describe('useLocalStorage', () => {
beforeEach(() => {
vi.mock('react', () => {
return {
useState: (initialValue) => [initialValue(), () => {}],
};
});
});

afterEach(() => {
vi.restoreAllMocks();
});

it('can parse using JSON', () => {
const localStorageMock = {
getItem: vi.fn().mockReturnValue('{"a":1}'),
setItem: vi.fn(),
};
vi.stubGlobal('localStorage', localStorageMock);

const [value, setValue] = useLocalStorage('key', { a: 1 }, true);

expect(localStorageMock.getItem).toHaveBeenCalledWith('key');
expect(value).toEqual({ a: 1 });

setValue({ a: 2 });

expect(localStorageMock.setItem).toHaveBeenCalledWith('key', '{"a":2}');
});

it('returns the initial value if none is in storage', () => {
const localStorageMock = {
getItem: vi.fn().mockReturnValue(undefined),
setItem: vi.fn(),
};
vi.stubGlobal('localStorage', localStorageMock);

const initialValue = 'test value';

const [value] = useLocalStorage('key', initialValue);

expect(value).toEqual(initialValue);
});

it('returns the value from storage if it exists', () => {
const localStorageValue = 'changed value';
const localStorageMock = {
getItem: vi.fn().mockReturnValue(localStorageValue),
setItem: vi.fn(),
};
vi.stubGlobal('localStorage', localStorageMock);

const initialValue = 'test value';

const [value] = useLocalStorage('key', initialValue);

expect(value).toEqual(localStorageValue);
});
});

0 comments on commit de576fa

Please sign in to comment.