Added a saved settings system that uses localstorage and versioning
This commit is contained in:
parent
e702936c44
commit
602631f9b1
@ -16,6 +16,8 @@
|
|||||||
"fake-indexeddb": "^3.0.0",
|
"fake-indexeddb": "^3.0.0",
|
||||||
"interactjs": "^1.9.7",
|
"interactjs": "^1.9.7",
|
||||||
"konva": "^6.0.0",
|
"konva": "^6.0.0",
|
||||||
|
"lodash.get": "^4.4.2",
|
||||||
|
"lodash.set": "^4.3.2",
|
||||||
"polygon-clipping": "^0.14.3",
|
"polygon-clipping": "^0.14.3",
|
||||||
"raw.macro": "^0.3.0",
|
"raw.macro": "^0.3.0",
|
||||||
"react": "^16.13.0",
|
"react": "^16.13.0",
|
||||||
|
@ -15,11 +15,13 @@ import { DatabaseProvider } from "./contexts/DatabaseContext";
|
|||||||
import { MapDataProvider } from "./contexts/MapDataContext";
|
import { MapDataProvider } from "./contexts/MapDataContext";
|
||||||
import { TokenDataProvider } from "./contexts/TokenDataContext";
|
import { TokenDataProvider } from "./contexts/TokenDataContext";
|
||||||
import { MapLoadingProvider } from "./contexts/MapLoadingContext";
|
import { MapLoadingProvider } from "./contexts/MapLoadingContext";
|
||||||
|
import { SettingsProvider } from "./contexts/SettingsContext.js";
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<DatabaseProvider>
|
<DatabaseProvider>
|
||||||
|
<SettingsProvider>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<Router>
|
<Router>
|
||||||
<Switch>
|
<Switch>
|
||||||
@ -50,6 +52,7 @@ function App() {
|
|||||||
</Switch>
|
</Switch>
|
||||||
</Router>
|
</Router>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
</SettingsProvider>
|
||||||
</DatabaseProvider>
|
</DatabaseProvider>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
|
@ -19,6 +19,7 @@ import SelectDiceButton from "./SelectDiceButton";
|
|||||||
import Divider from "../Divider";
|
import Divider from "../Divider";
|
||||||
|
|
||||||
import { dice } from "../../dice";
|
import { dice } from "../../dice";
|
||||||
|
import useSetting from "../../helpers/useSetting";
|
||||||
|
|
||||||
function DiceButtons({
|
function DiceButtons({
|
||||||
diceRolls,
|
diceRolls,
|
||||||
@ -29,10 +30,13 @@ function DiceButtons({
|
|||||||
shareDice,
|
shareDice,
|
||||||
onShareDiceChange,
|
onShareDiceChange,
|
||||||
}) {
|
}) {
|
||||||
const [currentDice, setCurrentDice] = useState(dice[0]);
|
const [currentDiceStyle, setCurrentDiceStyle] = useSetting("dice.style");
|
||||||
|
const [currentDice, setCurrentDice] = useState(
|
||||||
|
dice.find((d) => d.key === currentDiceStyle)
|
||||||
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const initialDice = dice[0];
|
const initialDice = dice.find((d) => d.key === currentDiceStyle);
|
||||||
onDiceLoad(initialDice);
|
onDiceLoad(initialDice);
|
||||||
setCurrentDice(initialDice);
|
setCurrentDice(initialDice);
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
@ -50,6 +54,7 @@ function DiceButtons({
|
|||||||
async function handleDiceChange(dice) {
|
async function handleDiceChange(dice) {
|
||||||
await onDiceLoad(dice);
|
await onDiceLoad(dice);
|
||||||
setCurrentDice(dice);
|
setCurrentDice(dice);
|
||||||
|
setCurrentDiceStyle(dice.key);
|
||||||
}
|
}
|
||||||
|
|
||||||
let buttons = [
|
let buttons = [
|
||||||
|
@ -12,6 +12,7 @@ import MapLoadingOverlay from "./MapLoadingOverlay";
|
|||||||
import NetworkedMapPointer from "../../network/NetworkedMapPointer";
|
import NetworkedMapPointer from "../../network/NetworkedMapPointer";
|
||||||
|
|
||||||
import TokenDataContext from "../../contexts/TokenDataContext";
|
import TokenDataContext from "../../contexts/TokenDataContext";
|
||||||
|
import SettingsContext from "../../contexts/SettingsContext";
|
||||||
|
|
||||||
import TokenMenu from "../token/TokenMenu";
|
import TokenMenu from "../token/TokenMenu";
|
||||||
import TokenDragOverlay from "../token/TokenDragOverlay";
|
import TokenDragOverlay from "../token/TokenDragOverlay";
|
||||||
@ -48,31 +49,10 @@ function Map({
|
|||||||
const tokenSizePercent = gridSizeNormalized.x;
|
const tokenSizePercent = gridSizeNormalized.x;
|
||||||
|
|
||||||
const [selectedToolId, setSelectedToolId] = useState("pan");
|
const [selectedToolId, setSelectedToolId] = useState("pan");
|
||||||
const [toolSettings, setToolSettings] = useState({
|
const { settings, setSettings } = useContext(SettingsContext);
|
||||||
fog: {
|
|
||||||
type: "polygon",
|
|
||||||
useEdgeSnapping: false,
|
|
||||||
useFogCut: false,
|
|
||||||
preview: false,
|
|
||||||
},
|
|
||||||
drawing: {
|
|
||||||
color: "red",
|
|
||||||
type: "brush",
|
|
||||||
useBlending: true,
|
|
||||||
},
|
|
||||||
measure: {
|
|
||||||
type: "chebyshev",
|
|
||||||
scale: "5ft",
|
|
||||||
},
|
|
||||||
timer: {
|
|
||||||
hour: 0,
|
|
||||||
minute: 0,
|
|
||||||
second: 0,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
function handleToolSettingChange(tool, change) {
|
function handleToolSettingChange(tool, change) {
|
||||||
setToolSettings((prevSettings) => ({
|
setSettings((prevSettings) => ({
|
||||||
...prevSettings,
|
...prevSettings,
|
||||||
[tool]: {
|
[tool]: {
|
||||||
...prevSettings[tool],
|
...prevSettings[tool],
|
||||||
@ -191,7 +171,7 @@ function Map({
|
|||||||
currentMapState={mapState}
|
currentMapState={mapState}
|
||||||
onSelectedToolChange={setSelectedToolId}
|
onSelectedToolChange={setSelectedToolId}
|
||||||
selectedToolId={selectedToolId}
|
selectedToolId={selectedToolId}
|
||||||
toolSettings={toolSettings}
|
toolSettings={settings}
|
||||||
onToolSettingChange={handleToolSettingChange}
|
onToolSettingChange={handleToolSettingChange}
|
||||||
onToolAction={handleToolAction}
|
onToolAction={handleToolAction}
|
||||||
disabledControls={disabledControls}
|
disabledControls={disabledControls}
|
||||||
@ -291,7 +271,7 @@ function Map({
|
|||||||
onShapesRemove={handleMapShapesRemove}
|
onShapesRemove={handleMapShapesRemove}
|
||||||
active={selectedToolId === "drawing"}
|
active={selectedToolId === "drawing"}
|
||||||
toolId="drawing"
|
toolId="drawing"
|
||||||
toolSettings={toolSettings.drawing}
|
toolSettings={settings.drawing}
|
||||||
gridSize={gridSizeNormalized}
|
gridSize={gridSizeNormalized}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
@ -306,9 +286,9 @@ function Map({
|
|||||||
onShapesEdit={handleFogShapesEdit}
|
onShapesEdit={handleFogShapesEdit}
|
||||||
active={selectedToolId === "fog"}
|
active={selectedToolId === "fog"}
|
||||||
toolId="fog"
|
toolId="fog"
|
||||||
toolSettings={toolSettings.fog}
|
toolSettings={settings.fog}
|
||||||
gridSize={gridSizeNormalized}
|
gridSize={gridSizeNormalized}
|
||||||
transparent={allowFogDrawing && !toolSettings.fog.preview}
|
transparent={allowFogDrawing && !settings.fog.preview}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -321,7 +301,7 @@ function Map({
|
|||||||
map={map}
|
map={map}
|
||||||
active={selectedToolId === "measure"}
|
active={selectedToolId === "measure"}
|
||||||
gridSize={gridSizeNormalized}
|
gridSize={gridSizeNormalized}
|
||||||
selectedToolSettings={toolSettings[selectedToolId]}
|
selectedToolSettings={settings[selectedToolId]}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
31
src/contexts/SettingsContext.js
Normal file
31
src/contexts/SettingsContext.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
|
import { getSettings } from "../settings";
|
||||||
|
|
||||||
|
const SettingsContext = React.createContext({
|
||||||
|
settings: {},
|
||||||
|
setSettings: () => {},
|
||||||
|
});
|
||||||
|
|
||||||
|
const settingsProvider = getSettings();
|
||||||
|
|
||||||
|
export function SettingsProvider({ children }) {
|
||||||
|
const [settings, setSettings] = useState(settingsProvider.getAll());
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
settingsProvider.setAll(settings);
|
||||||
|
}, [settings]);
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
settings,
|
||||||
|
setSettings,
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SettingsContext.Provider value={value}>
|
||||||
|
{children}
|
||||||
|
</SettingsContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SettingsContext;
|
43
src/helpers/Settings.js
Normal file
43
src/helpers/Settings.js
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
/**
|
||||||
|
* An interface to a local storage back settings store with a versioning mechanism
|
||||||
|
*/
|
||||||
|
class Settings {
|
||||||
|
name;
|
||||||
|
currentVersion;
|
||||||
|
|
||||||
|
constructor(name) {
|
||||||
|
this.name = name;
|
||||||
|
this.currentVersion = this.get("__version");
|
||||||
|
}
|
||||||
|
|
||||||
|
version(versionNumber, upgradeFunction) {
|
||||||
|
if (versionNumber > this.currentVersion) {
|
||||||
|
this.currentVersion = versionNumber;
|
||||||
|
this.setAll(upgradeFunction(this.getAll()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getAll() {
|
||||||
|
return JSON.parse(localStorage.getItem(this.name));
|
||||||
|
}
|
||||||
|
|
||||||
|
get(key) {
|
||||||
|
const settings = this.getAll();
|
||||||
|
return settings && settings[key];
|
||||||
|
}
|
||||||
|
|
||||||
|
setAll(newSettings) {
|
||||||
|
localStorage.setItem(
|
||||||
|
this.name,
|
||||||
|
JSON.stringify({ ...newSettings, __version: this.currentVersion })
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
set(key, value) {
|
||||||
|
let settings = this.getAll();
|
||||||
|
settings[key] = value;
|
||||||
|
this.setAll(settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Settings;
|
26
src/helpers/useSetting.js
Normal file
26
src/helpers/useSetting.js
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { useContext } from "react";
|
||||||
|
|
||||||
|
import get from "lodash.get";
|
||||||
|
import set from "lodash.set";
|
||||||
|
|
||||||
|
import SettingsContext from "../contexts/SettingsContext";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper to get and set nested settings that are saved in local storage
|
||||||
|
* @param {String} path The path to the setting within the Settings object provided by the SettingsContext
|
||||||
|
*/
|
||||||
|
function useSetting(path) {
|
||||||
|
const { settings, setSettings } = useContext(SettingsContext);
|
||||||
|
|
||||||
|
const setting = get(settings, path);
|
||||||
|
|
||||||
|
const setSetting = (value) =>
|
||||||
|
setSettings((prev) => {
|
||||||
|
const updated = set({ ...prev }, path, value);
|
||||||
|
return updated;
|
||||||
|
});
|
||||||
|
|
||||||
|
return [setting, setSetting];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default useSetting;
|
@ -1,10 +1,12 @@
|
|||||||
import React, { useState, useRef } from "react";
|
import React, { useRef } from "react";
|
||||||
import { Box, Label, Input, Button, Flex, Text } from "theme-ui";
|
import { Box, Label, Input, Button, Flex, Text } from "theme-ui";
|
||||||
|
|
||||||
import Modal from "../components/Modal";
|
import Modal from "../components/Modal";
|
||||||
|
|
||||||
import { getHMSDuration, getDurationHMS } from "../helpers/timer";
|
import { getHMSDuration, getDurationHMS } from "../helpers/timer";
|
||||||
|
|
||||||
|
import useSetting from "../helpers/useSetting";
|
||||||
|
|
||||||
function StartTimerModal({
|
function StartTimerModal({
|
||||||
isOpen,
|
isOpen,
|
||||||
onRequestClose,
|
onRequestClose,
|
||||||
@ -17,9 +19,9 @@ function StartTimerModal({
|
|||||||
inputRef.current && inputRef.current.focus();
|
inputRef.current && inputRef.current.focus();
|
||||||
}
|
}
|
||||||
|
|
||||||
const [hour, setHour] = useState(0);
|
const [hour, setHour] = useSetting("timer.hour");
|
||||||
const [minute, setMinute] = useState(0);
|
const [minute, setMinute] = useSetting("timer.minute");
|
||||||
const [second, setSecond] = useState(0);
|
const [second, setSecond] = useSetting("timer.second");
|
||||||
|
|
||||||
function handleSubmit(event) {
|
function handleSubmit(event) {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
|
@ -6,6 +6,7 @@ import Session from "../helpers/Session";
|
|||||||
import { isStreamStopped, omit, fromEntries } from "../helpers/shared";
|
import { isStreamStopped, omit, fromEntries } from "../helpers/shared";
|
||||||
|
|
||||||
import AuthContext from "../contexts/AuthContext";
|
import AuthContext from "../contexts/AuthContext";
|
||||||
|
import useSetting from "../helpers/useSetting";
|
||||||
|
|
||||||
import Party from "../components/party/Party";
|
import Party from "../components/party/Party";
|
||||||
|
|
||||||
@ -26,9 +27,10 @@ function NetworkedParty({ gameId, session }) {
|
|||||||
const [timer, setTimer] = useState(null);
|
const [timer, setTimer] = useState(null);
|
||||||
const [partyTimers, setPartyTimers] = useState({});
|
const [partyTimers, setPartyTimers] = useState({});
|
||||||
const [diceRolls, setDiceRolls] = useState([]);
|
const [diceRolls, setDiceRolls] = useState([]);
|
||||||
const [shareDice, setShareDice] = useState(false);
|
|
||||||
const [partyDiceRolls, setPartyDiceRolls] = useState({});
|
const [partyDiceRolls, setPartyDiceRolls] = useState({});
|
||||||
|
|
||||||
|
const [shareDice, setShareDice] = useSetting("dice.shareDice");
|
||||||
|
|
||||||
function handleNicknameChange(newNickname) {
|
function handleNicknameChange(newNickname) {
|
||||||
setNickname(newNickname);
|
setNickname(newNickname);
|
||||||
session.send("nickname", { [session.id]: newNickname });
|
session.send("nickname", { [session.id]: newNickname });
|
||||||
|
36
src/settings.js
Normal file
36
src/settings.js
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import Settings from "./helpers/Settings";
|
||||||
|
|
||||||
|
function loadVersions(settings) {
|
||||||
|
settings.version(1, () => ({
|
||||||
|
fog: {
|
||||||
|
type: "polygon",
|
||||||
|
useEdgeSnapping: false,
|
||||||
|
useFogCut: false,
|
||||||
|
preview: false,
|
||||||
|
},
|
||||||
|
drawing: {
|
||||||
|
color: "red",
|
||||||
|
type: "brush",
|
||||||
|
useBlending: true,
|
||||||
|
},
|
||||||
|
measure: {
|
||||||
|
type: "chebyshev",
|
||||||
|
scale: "5ft",
|
||||||
|
},
|
||||||
|
timer: {
|
||||||
|
hour: 0,
|
||||||
|
minute: 0,
|
||||||
|
second: 0,
|
||||||
|
},
|
||||||
|
dice: {
|
||||||
|
shareDice: false,
|
||||||
|
style: "galaxy",
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getSettings() {
|
||||||
|
let settings = new Settings("OwlbearRodeoSettings");
|
||||||
|
loadVersions(settings);
|
||||||
|
return settings;
|
||||||
|
}
|
10
yarn.lock
10
yarn.lock
@ -7061,11 +7061,21 @@ lodash.debounce@^4.0.8:
|
|||||||
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af"
|
||||||
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
integrity sha1-gteb/zCmfEAF/9XiUVMArZyk168=
|
||||||
|
|
||||||
|
lodash.get@^4.4.2:
|
||||||
|
version "4.4.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
|
||||||
|
integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk=
|
||||||
|
|
||||||
lodash.memoize@^4.1.2:
|
lodash.memoize@^4.1.2:
|
||||||
version "4.1.2"
|
version "4.1.2"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
|
||||||
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
|
||||||
|
|
||||||
|
lodash.set@^4.3.2:
|
||||||
|
version "4.3.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/lodash.set/-/lodash.set-4.3.2.tgz#d8757b1da807dde24816b0d6a84bea1a76230b23"
|
||||||
|
integrity sha1-2HV7HagH3eJIFrDWqEvqGnYjCyM=
|
||||||
|
|
||||||
lodash.sortby@^4.7.0:
|
lodash.sortby@^4.7.0:
|
||||||
version "4.7.0"
|
version "4.7.0"
|
||||||
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
resolved "https://registry.yarnpkg.com/lodash.sortby/-/lodash.sortby-4.7.0.tgz#edd14c824e2cc9c1e0b0a1b42bb5210516a42438"
|
||||||
|
Loading…
x
Reference in New Issue
Block a user