From 99e178fa82fdee033a5d60196d6bbe99ac0d3c13 Mon Sep 17 00:00:00 2001 From: HerveH44 Date: Wed, 9 Dec 2020 09:48:09 +0100 Subject: [PATCH 01/13] connect StartPanel simple buttons to redux store --- frontend/src/components/Checkbox.jsx | 4 +- frontend/src/game/StartPanel.jsx | 25 +- frontend/src/router.js | 6 +- frontend/src/state/ingame-host-settings.js | 32 + frontend/src/state/store.js | 8 + package-lock.json | 15116 ++++++++++++++++++- package.json | 2 + 7 files changed, 15170 insertions(+), 23 deletions(-) create mode 100644 frontend/src/state/ingame-host-settings.js create mode 100644 frontend/src/state/store.js diff --git a/frontend/src/components/Checkbox.jsx b/frontend/src/components/Checkbox.jsx index 25dfa104..c7fa4441 100644 --- a/frontend/src/components/Checkbox.jsx +++ b/frontend/src/components/Checkbox.jsx @@ -3,7 +3,7 @@ import PropTypes from "prop-types"; import App from "../app"; -const Checkbox = ({link, text, side, onChange, ...rest}) => ( +const Checkbox = ({link, text, side, onChange, value, ...rest}) => (
{side === "right" ? text : ""} ( onChange={onChange || function (e) { App.save(link, e.currentTarget.checked); }} - checked={App.state[link]}/> + checked={value || App.state[link]}/> {side === "left" ? text : ""}
); diff --git a/frontend/src/game/StartPanel.jsx b/frontend/src/game/StartPanel.jsx index 4aedaa86..db302119 100644 --- a/frontend/src/game/StartPanel.jsx +++ b/frontend/src/game/StartPanel.jsx @@ -1,8 +1,10 @@ import React from "react"; +import { useDispatch, useSelector } from "react-redux"; import App from "../app"; import Checkbox from "../components/Checkbox"; import Select from "../components/Select"; +import { selectAddBots, selectShufflePlayers, selectUseTimer, toggleBots, toggleUseTimer, toggleShufflePlayers } from "../state/ingame-host-settings"; import { toTitleCase } from "../utils"; const StartPanel = () => { @@ -40,20 +42,35 @@ const StartControls = () => { }; const Options = () => { - const {useTimer} = App.state; + const dispatch = useDispatch(); const timers = ["Fast", "Moderate", "Slow", "Leisurely"]; + const addBots = useSelector(selectAddBots); + const shufflePlayers = useSelector(selectShufflePlayers); + const useTimer = useSelector(selectUseTimer); return ( {showAddBotsCheckbox() - ? + ? dispatch(toggleBots())} /> : null } {showShufflePlayersCheckbox() - ? + ? dispatch(toggleShufflePlayers())} /> : null }
- + dispatch(toggleUseTimer())} /> + dispatch(setTimerLength(e.target.value))} - opts={timers} - disabled={!useTimer} /> + value={timerLength} + onChange={(e) => dispatch(setTimerLength(e.target.value))} + opts={timers} + disabled={!useTimer} />
); @@ -85,11 +85,11 @@ const Options = () => { const showAddBotsCheckbox = () => { // No need for bots in decadent draft since there's no passing. return !App.state.isDecadentDraft; -} +}; const showShufflePlayersCheckbox = () => { // No need to shuffle players in decadent draft because there's no passing. return !App.state.isDecadentDraft; -} +}; export default StartPanel; diff --git a/frontend/src/state/store.js b/frontend/src/state/store.js index bbd96596..27b738eb 100644 --- a/frontend/src/state/store.js +++ b/frontend/src/state/store.js @@ -1,5 +1,5 @@ -import { combineReducers, configureStore, getDefaultMiddleware } from '@reduxjs/toolkit'; -import storage from 'redux-persist/lib/storage' +import { combineReducers, configureStore, getDefaultMiddleware } from "@reduxjs/toolkit"; +import storage from "redux-persist/lib/storage"; import { persistReducer, FLUSH, REHYDRATE, @@ -7,10 +7,10 @@ import { PERSIST, PURGE, REGISTER -} from 'redux-persist'; +} from "redux-persist"; import { initialState as startControlInitState } from "./start-controls"; import startControls from "./start-controls"; -import migrateState from './migration'; +import migrateState from "./migration"; const reducers = combineReducers({ startControls: startControls.reducer @@ -41,4 +41,4 @@ export default configureStore({ ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER] } }), -}) \ No newline at end of file +}); \ No newline at end of file From c7a34b029e6a06db6cddd9b5edf9b72e1e1ff9d7 Mon Sep 17 00:00:00 2001 From: HerveH44 Date: Thu, 10 Dec 2020 16:51:22 +0100 Subject: [PATCH 11/13] more eslint pb --- frontend/src/state/start-controls.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frontend/src/state/start-controls.js b/frontend/src/state/start-controls.js index 002d1244..04aa00d6 100644 --- a/frontend/src/state/start-controls.js +++ b/frontend/src/state/start-controls.js @@ -1,4 +1,4 @@ -import { createSlice } from '@reduxjs/toolkit'; +import { createSlice } from "@reduxjs/toolkit"; export const timers = ["Fast", "Moderate", "Slow", "Leisurely"]; From 8345a80870f53ebddfd06ba948d8806965b07ccd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Huneau?= Date: Sun, 13 Dec 2020 12:35:08 +0100 Subject: [PATCH 12/13] use gameSettings redux store --- frontend/src/app.js | 10 -- frontend/src/game/GameSettings.jsx | 258 ++++++++++++++++------------ frontend/src/state/game-settings.js | 97 +++++++++++ frontend/src/state/store.js | 7 +- 4 files changed, 251 insertions(+), 121 deletions(-) create mode 100644 frontend/src/state/game-settings.js diff --git a/frontend/src/app.js b/frontend/src/app.js index 71254d15..cf2b8f3d 100644 --- a/frontend/src/app.js +++ b/frontend/src/app.js @@ -45,20 +45,10 @@ let App = { packs: 3, cubePoolSize: 90, - beep: true, - notify: false, - notificationGranted: false, - chat: false, - cols: false, - hidepicks: false, deckSize: 40, filename: "filename", filetype: "txt", - side: false, - sort: "rarity", log: {}, - cardSize: "normal", - cardLang: "en", game: {}, mtgJsonVersion: { version: "0.0.0", diff --git a/frontend/src/game/GameSettings.jsx b/frontend/src/game/GameSettings.jsx index 4b766e33..53883c99 100644 --- a/frontend/src/game/GameSettings.jsx +++ b/frontend/src/game/GameSettings.jsx @@ -1,132 +1,172 @@ +import { capitalize } from "lodash"; import React from "react"; +import { useDispatch, useSelector } from "react-redux"; import App from "../app"; import Checkbox from "../components/Checkbox"; +import { imgLanguageDisplay, selectBeep, selectCardLang, setNotificationResult, changeSort, toggleCols, selectCardSize, selectChat, selectHidepicks, notificationBlocked, selectNotify, selectSort, toggleChat, toggleNotify, toggleBeep, selectSide, toggleSide, toggleHidepicks, selectCols, sortChoices, sizeDisplay, changeCardSize, changeCardLang } from "../state/game-settings"; import "./GameSettings.scss"; -const GameSettings = () => ( -
-
- Settings - - - {!App.state.isSealed && - +const GameSettings = () => { + const dispatch = useDispatch(); + const beep = useSelector(selectBeep); + const notify = useSelector(selectNotify); + const isNotificationBlocked = useSelector(notificationBlocked); + const chat = useSelector(selectChat); + const side = useSelector(selectSide); + const hidePicks = useSelector(selectHidepicks); + const cols = useSelector(selectCols); + + //TODO: that could go inside the reducer? But it has an async behavior ... + const onNotificationChange = () => { + if (!notify) { + dispatch(toggleNotify()); + } else if ("Notification" in window) { + Notification.requestPermission().then((result) => { + dispatch(setNotificationResult(result)); + if (result === "granted") { + dispatch(toggleNotify()); } - {!App.state.isSealed && + }); + } else { + dispatch(setNotificationResult("notsupported")); + if (notify) { + dispatch(toggleNotify()); + } + } + }; + + return ( +
+
+ Settings + + dispatch(toggleChat())} /> + {!App.state.isSealed && + dispatch(toggleBeep())} /> + } + {!App.state.isSealed &&
+ text={isNotificationBlocked ? "Web notifications blocked in browser" : "Use desktop notifications over beep"} + value={notify} + disabled={!beep || isNotificationBlocked} + onChange={onNotificationChange} />
- } - {!App.state.isSealed && - } - {!App.state.isSealed && - - } - - - - {App.state.cardSize != "text" && } -
-
-
-); + } + {!App.state.isSealed && + dispatch(toggleSide())} />} + {!App.state.isSealed && + dispatch(toggleHidepicks())} />} + dispatch(toggleCols())} /> + + + {App.state.cardSize != "text" && } +
+
+
+ ); +}; -const SortCards = () => ( -
- Sort cards by: -
- {["CMC", "Color", "Type", "Rarity"].map((sort, index) => { - const isActive = sort.toLowerCase() === App.state.sort; +const SortCards = () => { + const dispatch = useDispatch(); + const sortValue = useSelector(selectSort); + return ( +
+ Sort cards by: +
+ {sortChoices.map((sort, index) => { + const isActive = sort === sortValue; - return ( - - ); - })} + return ( + + ); + })} +
-
-); - -const sizeDisplay = { - "text": "Text-Only", - "small": "Low", - "normal": "Medium", - "large": "High", -}; + );}; -const CardsImageQuality = () => ( -
+const CardsImageQuality = () => { + const dispatch = useDispatch(); + const cardSize = useSelector(selectCardSize); + const cardLang = useSelector(selectCardLang); + return ( +
Card image quality: -
- {Object.keys(sizeDisplay).map((size, index) => { - const isActive = size.toLowerCase() === App.state.cardSize; +
+ {Object.keys(sizeDisplay).map((size, index) => { + const isActive = size === cardSize; - return ( - - ); - })} + return ( + + ); + })} +
-
-); - -const imgLanguageDisplay = { - "en": "English", - "fr": "French (Français)", - "es": "Spanish (Español)", - "de": "German (Deutsch)", - "it": "Italian (Italiano)", - "pt": "Portuguese (Português)", - "ja": "Japanese (日本語)", - "ko": "Korean (한국어)", - "ru": "Russian (Русский)", - "zhs": "Simplified Chinese (简体中文)", - "zht": "Traditional Chinese (繁體中文)" + ); }; -const CardsImageLanguage = () => ( -
+const CardsImageLanguage = () => { + const dispatch = useDispatch(); + const cardLang = useSelector(selectCardLang); + return ( +
Card image language: -
- +
+ +
-
-); + ); +}; export default GameSettings; diff --git a/frontend/src/state/game-settings.js b/frontend/src/state/game-settings.js new file mode 100644 index 00000000..d7b66c4b --- /dev/null +++ b/frontend/src/state/game-settings.js @@ -0,0 +1,97 @@ +import { createSlice } from "@reduxjs/toolkit"; + +export const sortChoices = ["cmc", "color", "type", "rarity"]; + +export const sizeDisplay = { + "text": "Text-Only", + "small": "Low", + "normal": "Medium", + "large": "High", +}; + +export const imgLanguageDisplay = { + "en": "English", + "fr": "French (Français)", + "es": "Spanish (Español)", + "de": "German (Deutsch)", + "it": "Italian (Italiano)", + "pt": "Portuguese (Português)", + "ja": "Japanese (日本語)", + "ko": "Korean (한국어)", + "ru": "Russian (Русский)", + "zhs": "Simplified Chinese (简体中文)", + "zht": "Traditional Chinese (繁體中文)" +}; + +export const initialState = { + beep: true, + notify: false, + notificationGranted: false, + chat: false, + cols: false, + hidepicks: false, + side: false, + sort: "rarity", + cardSize: "normal", + cardLang: "en", + notificationResult: "", +}; + +const gameSettings = createSlice({ + name: "gameSettings", + initialState, + reducers: { + toggleBeep: state => { + state.beep = !state.beep; + }, + toggleNotify: state => { + state.notify = !state.notify; + }, + setNotificationResult: (state, action) => { + state.notificationResult = action.payload; + }, + toggleChat: state => { + state.chat = !state.chat; + }, + toggleHidepicks: state => { + state.hidepicks = !state.hidepicks; + }, + toggleSide: state => { + state.side = !state.side; + }, + changeSort: (state, action) => { + if (sortChoices.includes(action.payload)) { + state.sort = action.payload; + } + }, + changeCardSize: (state, action) => { + if (Object.keys(sizeDisplay).includes(action.payload)) { + state.cardSize = action.payload; + } + }, + changeCardLang: (state, action) => { + if (Object.keys(imgLanguageDisplay).includes(action.payload)) { + state.cardLang = action.payload; + } + }, + toggleCols: state => { + state.cols = !state.cols; + } + } +}); + +export const selectBeep = state => state.gameSettings.beep; +export const selectNotify = state => state.gameSettings.notify; +export const selectNotificationGranted = state => state.gameSettings.notificationGranted; +export const selectChat = state => state.gameSettings.chat; +export const selectHidepicks = state => state.gameSettings.hidepicks; +export const selectSide = state => state.gameSettings.side; +export const selectSort = state => state.gameSettings.sort; +export const selectCols = state => state.gameSettings.cols; +export const selectCardSize = state => state.gameSettings.cardSize; +export const selectCardLang = state => state.gameSettings.cardLang; +export const notificationBlocked = state => ["denied", "notsupported"].includes(state.gameSettings.notificationResult); + +export const {changeCardLang, changeCardSize, changeSort, toggleBeep, toggleChat, toggleHidepicks, toggleNotify, setNotificationResult, toggleSide, toggleCols} = gameSettings.actions; + +export default gameSettings; \ No newline at end of file diff --git a/frontend/src/state/store.js b/frontend/src/state/store.js index 27b738eb..5188071b 100644 --- a/frontend/src/state/store.js +++ b/frontend/src/state/store.js @@ -9,11 +9,13 @@ import { REGISTER } from "redux-persist"; import { initialState as startControlInitState } from "./start-controls"; +import gameSettings, { initialState as gameSettingslInitState } from "./game-settings"; import startControls from "./start-controls"; import migrateState from "./migration"; const reducers = combineReducers({ - startControls: startControls.reducer + startControls: startControls.reducer, + gameSettings: gameSettings.reducer }); const persistConfig = { @@ -25,7 +27,8 @@ const persistConfig = { } const newState = { ...state, - startControls: migrateState(startControlInitState, state.startControls) + startControls: migrateState(startControlInitState, state.startControls), + gameSettings: migrateState(gameSettingslInitState, state.gameSettings), }; return Promise.resolve(newState); } From 2234a97d952483ea7985864cb4cf796ac49fd250 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Herv=C3=A9=20Huneau?= Date: Tue, 19 Jan 2021 16:43:32 +0100 Subject: [PATCH 13/13] follow mixmix recommandations --- frontend/src/components/Checkbox.jsx | 1 + frontend/src/state/migration/index.js | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/Checkbox.jsx b/frontend/src/components/Checkbox.jsx index c7fa4441..5700f176 100644 --- a/frontend/src/components/Checkbox.jsx +++ b/frontend/src/components/Checkbox.jsx @@ -21,6 +21,7 @@ Checkbox.propTypes = { link: PropTypes.string, text: PropTypes.string, side: PropTypes.string, + value: PropTypes.any, onChange: PropTypes.func }; diff --git a/frontend/src/state/migration/index.js b/frontend/src/state/migration/index.js index 51cc0341..78bcfc46 100644 --- a/frontend/src/state/migration/index.js +++ b/frontend/src/state/migration/index.js @@ -1,5 +1,5 @@ const readAndDeleteFromLocalStorage = (key, defaultValue) => { - const val = localStorage[key]; + const val = localStorage.getItem(key); if (!val) { return defaultValue; } @@ -9,7 +9,7 @@ const readAndDeleteFromLocalStorage = (key, defaultValue) => { } catch (e) { return defaultValue; } finally { - delete localStorage[key]; + delete localStorage.removeItem(key); } };