diff --git a/.yarn/cache/@babel-runtime-npm-7.18.6-6a59ef0d54-8b707b64ae.zip b/.yarn/cache/@babel-runtime-npm-7.18.6-6a59ef0d54-8b707b64ae.zip new file mode 100644 index 00000000..afffbf6d Binary files /dev/null and b/.yarn/cache/@babel-runtime-npm-7.18.6-6a59ef0d54-8b707b64ae.zip differ diff --git a/.yarn/cache/@reduxjs-toolkit-npm-1.8.3-7a05ac0aba-2c932ac6fa.zip b/.yarn/cache/@reduxjs-toolkit-npm-1.8.3-7a05ac0aba-2c932ac6fa.zip new file mode 100644 index 00000000..c0523a35 Binary files /dev/null and b/.yarn/cache/@reduxjs-toolkit-npm-1.8.3-7a05ac0aba-2c932ac6fa.zip differ diff --git a/.yarn/cache/@types-hoist-non-react-statics-npm-3.3.1-c0081332b2-2c0778570d.zip b/.yarn/cache/@types-hoist-non-react-statics-npm-3.3.1-c0081332b2-2c0778570d.zip new file mode 100644 index 00000000..6803d574 Binary files /dev/null and b/.yarn/cache/@types-hoist-non-react-statics-npm-3.3.1-c0081332b2-2c0778570d.zip differ diff --git a/.yarn/cache/@types-react-redux-npm-7.1.24-31c5f19b33-6582246581.zip b/.yarn/cache/@types-react-redux-npm-7.1.24-31c5f19b33-6582246581.zip new file mode 100644 index 00000000..9c4b90cb Binary files /dev/null and b/.yarn/cache/@types-react-redux-npm-7.1.24-31c5f19b33-6582246581.zip differ diff --git a/.yarn/cache/@types-use-sync-external-store-npm-0.0.3-875a91a914-161ddb8eec.zip b/.yarn/cache/@types-use-sync-external-store-npm-0.0.3-875a91a914-161ddb8eec.zip new file mode 100644 index 00000000..65f8d4f4 Binary files /dev/null and b/.yarn/cache/@types-use-sync-external-store-npm-0.0.3-875a91a914-161ddb8eec.zip differ diff --git a/.yarn/cache/react-redux-npm-8.0.2-0855b9ffc2-44c1739c45.zip b/.yarn/cache/react-redux-npm-8.0.2-0855b9ffc2-44c1739c45.zip new file mode 100644 index 00000000..1eacd0a0 Binary files /dev/null and b/.yarn/cache/react-redux-npm-8.0.2-0855b9ffc2-44c1739c45.zip differ diff --git a/.yarn/cache/redux-npm-4.2.0-4688cc8d65-75f3955c89.zip b/.yarn/cache/redux-npm-4.2.0-4688cc8d65-75f3955c89.zip new file mode 100644 index 00000000..6ecf8c2c Binary files /dev/null and b/.yarn/cache/redux-npm-4.2.0-4688cc8d65-75f3955c89.zip differ diff --git a/.yarn/cache/redux-thunk-npm-2.4.1-2ba08bf615-af5abb425f.zip b/.yarn/cache/redux-thunk-npm-2.4.1-2ba08bf615-af5abb425f.zip new file mode 100644 index 00000000..03a3656c Binary files /dev/null and b/.yarn/cache/redux-thunk-npm-2.4.1-2ba08bf615-af5abb425f.zip differ diff --git a/.yarn/cache/reselect-npm-4.1.6-869f318cc3-3ea1058422.zip b/.yarn/cache/reselect-npm-4.1.6-869f318cc3-3ea1058422.zip new file mode 100644 index 00000000..e0fa67b4 Binary files /dev/null and b/.yarn/cache/reselect-npm-4.1.6-869f318cc3-3ea1058422.zip differ diff --git a/.yarn/cache/use-sync-external-store-npm-1.2.0-44f75d2564-5c639e0f8d.zip b/.yarn/cache/use-sync-external-store-npm-1.2.0-44f75d2564-5c639e0f8d.zip new file mode 100644 index 00000000..d737a8fc Binary files /dev/null and b/.yarn/cache/use-sync-external-store-npm-1.2.0-44f75d2564-5c639e0f8d.zip differ diff --git a/client/src/BoardGame.tsx b/client/src/BoardGame.tsx index e6ad585f..bfad11c8 100644 --- a/client/src/BoardGame.tsx +++ b/client/src/BoardGame.tsx @@ -3,12 +3,14 @@ import { Client, BoardProps } from 'boardgame.io/react'; import { Local } from 'boardgame.io/multiplayer'; import { OpenStarTerVillageType } from 'packages/game/src/types'; import Table from './features/Table/Table'; +import ActionBoard from './features/ActionBoard/ActionBoard'; import Players from './Players/Players'; import DevActions from './DevActions/DevActions'; const Board: React.FC> = (props) => { return (
+ diff --git a/client/src/app/hooks.ts b/client/src/app/hooks.ts new file mode 100644 index 00000000..6fefa5b1 --- /dev/null +++ b/client/src/app/hooks.ts @@ -0,0 +1,7 @@ +import { useDispatch, useSelector } from 'react-redux' +import type { TypedUseSelectorHook } from 'react-redux' +import type { RootState, AppDispatch } from './store' + +export const useAppDispatch: () => AppDispatch = useDispatch + +export const useAppSelector: TypedUseSelectorHook = useSelector diff --git a/client/src/app/reducers/wizardReducer.ts b/client/src/app/reducers/wizardReducer.ts new file mode 100644 index 00000000..17c49de5 --- /dev/null +++ b/client/src/app/reducers/wizardReducer.ts @@ -0,0 +1,247 @@ +import { createReducer, createAction } from '@reduxjs/toolkit' + +namespace WizardStep { + namespace Table { + type ActiveEventType = 'table--event'; + type ActiveProjectType = 'table--active-project'; + type ActiveJobType = 'table--active-job'; + type ActiveMoveType = 'table--active-move'; + + export type TableType = ActiveEventType | ActiveProjectType | ActiveJobType | ActiveMoveType; + } + + namespace Player { + namespace Hand { + type ProjectType = 'player--hand--project'; + type ForceType = 'player--hand--force'; + + export type HandType = ProjectType | ForceType; + } + + export type PlayerType = Hand.HandType + } + + export type StepType = Table.TableType | Player.PlayerType +} + +type Step = { + type: WizardStep.StepType; + value: any; + prevValue?: any; +} + +type RequiredStep = { + type: WizardStep.StepType; + limit?: number; // exactly limit, default is 1 + min?: number; // minimum limit, default is 0 + max?: number; // maximum limit, default is Inf +} + +type Page = { + isCancellable: boolean; + requiredSteps: Partial>; + toggledSteps: Step[]; +} + +type WizardState = { + pages: Page[]; + currentPage: number; +} + +const initialState: WizardState = { + pages: [], + currentPage: -1, +} + +const enum WizardActionTypes { + INIT_WIZARD = 'INIT_WIZARD', + CLEAR_WIZARD = 'CLEAR_WIZARD', + TOGGLE_ON_STEP = 'TOGGLE_ON_STEP', + TOGGLE_OFF_STEP = 'TOGGLE_OFF_STEP', + NEXT_PAGE = 'NEXT_PAGE', + PREV_PAGE = 'PREV_PAGE', +} + +const WizardActions = { + init: createAction(WizardActionTypes.INIT_WIZARD), + clear: createAction(WizardActionTypes.CLEAR_WIZARD), + toggleOnStep: createAction(WizardActionTypes.TOGGLE_ON_STEP), + toggleOffStep: createAction(WizardActionTypes.TOGGLE_OFF_STEP), + nextPage: createAction(WizardActionTypes.NEXT_PAGE), + prevPage: createAction(WizardActionTypes.PREV_PAGE), +} + +const getRequiredLimit = (required: RequiredStep) => { + if (required.limit) { + return { + min: required.limit, + max: required.limit, + } + } + if (required.min || required.max) { + return { + min: required.min || 0, + max: required.max || Infinity, + } + } + return { + min: 0, + max: Infinity, + } +} + +export const wizardReducer = createReducer(initialState, (builder) => { + builder + .addCase(WizardActions.init, (state, action) => { + state.pages = action.payload; + state.currentPage = 0; + }) + .addCase(WizardActions.clear, () => initialState) + .addCase(WizardActions.toggleOnStep, (state, action) => { + state.pages[state.currentPage].toggledSteps.push(action.payload); + }) + .addCase(WizardActions.toggleOffStep, (state, action) => { + const idx = state.pages[state.currentPage].toggledSteps.findIndex((step) => + action.payload.type === step.type + && action.payload.value === step.value + && action.payload.prevValue === step.prevValue); + + if (idx >= 0) { + state.pages[state.currentPage].toggledSteps.splice(idx); + } + }) + .addCase(WizardActions.nextPage, (state) => { + state.currentPage ++; + state.currentPage = Math.min(state.currentPage, state.pages.length); + }) + .addCase(WizardActions.prevPage, (state) => { + state.currentPage --; + state.currentPage = Math.max(0, state.currentPage); + }) +}) + +// Wizard Selector + +export const isLegitPage = (state: WizardState): boolean => state.currentPage >= 0; + +export const isPageCancellable = (state: WizardState): boolean => isLegitPage(state) && state.pages[state.currentPage].isCancellable; + +export const hasNextPage = (state: WizardState): boolean => isLegitPage(state) && state.currentPage < state.pages.length; + +export const hasPrevPage = (state: WizardState): boolean => state.currentPage > 1; + +export const getCurrentPage = (state: WizardState): Page => state.pages[state.currentPage]; + +// Page Selector + +export const isRequiredStep = (state: Page, stepType: WizardStep.StepType): boolean => !!state.requiredSteps[stepType]; + +export const isToggledStep = (state: Page, step: Step): boolean => { + const idx = state.toggledSteps.findIndex(toggled => step.type === toggled.type && step.value === toggled.value && step.prevValue === toggled.prevValue); + return idx >= 0; +} + +export const isRequiredStepsFulfilled = (state: Page): boolean => { + return Object.values(state.requiredSteps) + .every((requiredStep) => { + const { min, max } = getRequiredLimit(requiredStep); + const toggled = state.toggledSteps.filter(step => step.type === requiredStep.type).length; + + return min <= toggled && toggled <= max; + }); +} + +/** + * // ActionBoard.tsx + * onCreateProjectActionClick = () => { + * dispatch(WizardActions.init(getPagesByActionName('create-project'))); + * } + * + * onConfirmBtnClick = () => { + * dispatch(WizardActions.nextPage()); + * } + * + * onCancelBtnClick = () => { + * dispatch(WizardActions.prevPage()); + * } + * + * onSubmitBtnClick = () => { + * dispatch(WizardActions.clear()); + * } + * + * const showCancelBtn = isPageCancellable(state.wizard); + * const showConfirmBtn = hasNextPage(state.wizard); + * const showSubmitBtn = !hasNextPage(state.wizard); + * + * render (<> + * + * {showCancelBtn && + + + + )} + + + ); +}; + +export default ActionBoard; diff --git a/client/src/features/Job/ActiveJob.tsx b/client/src/features/Job/ActiveJob.tsx new file mode 100644 index 00000000..9686aa1d --- /dev/null +++ b/client/src/features/Job/ActiveJob.tsx @@ -0,0 +1,18 @@ +import { Box, Text } from '@chakra-ui/react'; +import { OpenStarTerVillageType as Type } from 'packages/game/src/types'; + +export interface IActiveJobProps { + job: Type.Card.Job; +} + +const ActiveJob: React.FC = (props) => { + const { job } = props; + + return ( + + {job.name} + + ); +} + +export default ActiveJob; diff --git a/client/src/features/Table/Table.tsx b/client/src/features/Table/Table.tsx index c7b64f33..2ed56cdd 100644 --- a/client/src/features/Table/Table.tsx +++ b/client/src/features/Table/Table.tsx @@ -2,22 +2,37 @@ import { BoardProps } from 'boardgame.io/react'; import { OpenStarTerVillageType as Type } from 'packages/game/src/types'; import { Box, Stack } from '@chakra-ui/react'; import ActiveProject from '../Project/ActiveProject'; +import ActiveJob from '../Job/ActiveJob'; type Props = BoardProps; const Table: React.FC = (props) => { const activeProjects = [...props.G.table.activeProjects, ...Array(6)].slice(0, 6); + const activeJobs = props.G.table.activeJobs; return ( - {activeProjects.map((p, pIndex) => ( - - - - ))} - - + + + {activeProjects.map((p, pIndex) => ( + + + + ))} + + + + + {activeJobs.map((job, jobIndex) => ( + + + + ))} + + + + ) } diff --git a/client/src/index.tsx b/client/src/index.tsx index 77cfed30..dbc33e3d 100644 --- a/client/src/index.tsx +++ b/client/src/index.tsx @@ -4,12 +4,16 @@ import ReactDOM from 'react-dom'; import './index.css'; import App from './App'; import reportWebVitals from './reportWebVitals'; +import { store } from './app/store' +import { Provider as ReduxProvider } from 'react-redux' ReactDOM.render( - - - + + + + + , document.getElementById('root') ); diff --git a/package.json b/package.json index 1c066fc2..0073f02b 100644 --- a/package.json +++ b/package.json @@ -21,9 +21,12 @@ "test": "yarn workspaces foreach -p run test" }, "devDependencies": { - "@types/koa-static": "^4.0.2" + "@types/koa-static": "^4.0.2", + "@types/react-redux": "^7.1.24" }, "dependencies": { - "koa-static": "^5.0.0" + "@reduxjs/toolkit": "^1.8.3", + "koa-static": "^5.0.0", + "react-redux": "^8.0.2" } } diff --git a/yarn.lock b/yarn.lock index 192ed4ec..481efd6f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1746,6 +1746,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.12.1": + version: 7.18.6 + resolution: "@babel/runtime@npm:7.18.6" + dependencies: + regenerator-runtime: ^0.13.4 + checksum: 8b707b64ae0524db617d0c49933b258b96376a38307dc0be8fb42db5697608bcc1eba459acce541e376cff5ed5c5287d24db5780bd776b7c75ba2c2e26ff8a2c + languageName: node + linkType: hard + "@babel/template@npm:^7.16.7, @babel/template@npm:^7.3.3": version: 7.16.7 resolution: "@babel/template@npm:7.16.7" @@ -3781,6 +3790,26 @@ __metadata: languageName: node linkType: hard +"@reduxjs/toolkit@npm:^1.8.3": + version: 1.8.3 + resolution: "@reduxjs/toolkit@npm:1.8.3" + dependencies: + immer: ^9.0.7 + redux: ^4.1.2 + redux-thunk: ^2.4.1 + reselect: ^4.1.5 + peerDependencies: + react: ^16.9.0 || ^17.0.0 || ^18 + react-redux: ^7.2.1 || ^8.0.2 + peerDependenciesMeta: + react: + optional: true + react-redux: + optional: true + checksum: 2c932ac6fa430b982108829a6150ca0c15ff617eb121b8055b91a89ab2124e8d93a1277ccdc96ff8af680fbde9d4c7021b1f924f11e28004ee61c6a7d39915bc + languageName: node + linkType: hard + "@rollup/plugin-babel@npm:^5.2.0": version: 5.3.1 resolution: "@rollup/plugin-babel@npm:5.3.1" @@ -4408,6 +4437,16 @@ __metadata: languageName: node linkType: hard +"@types/hoist-non-react-statics@npm:^3.3.0, @types/hoist-non-react-statics@npm:^3.3.1": + version: 3.3.1 + resolution: "@types/hoist-non-react-statics@npm:3.3.1" + dependencies: + "@types/react": "*" + hoist-non-react-statics: ^3.3.0 + checksum: 2c0778570d9a01d05afabc781b32163f28409bb98f7245c38d5eaf082416fdb73034003f5825eb5e21313044e8d2d9e1f3fe2831e345d3d1b1d20bcd12270719 + languageName: node + linkType: hard + "@types/html-minifier-terser@npm:^6.0.0": version: 6.1.0 resolution: "@types/html-minifier-terser@npm:6.1.0" @@ -4682,6 +4721,18 @@ __metadata: languageName: node linkType: hard +"@types/react-redux@npm:^7.1.24": + version: 7.1.24 + resolution: "@types/react-redux@npm:7.1.24" + dependencies: + "@types/hoist-non-react-statics": ^3.3.0 + "@types/react": "*" + hoist-non-react-statics: ^3.3.0 + redux: ^4.0.0 + checksum: 6582246581331ac7fbbd44aa1f1c136c8a9c8febbcf462432ac81302263308c21e1a2e7868beb7f73bbcb52a8e67935d133cb37f5bdcb6564eaff3a811805101 + languageName: node + linkType: hard + "@types/react@npm:*, @types/react@npm:^17.0.0": version: 17.0.39 resolution: "@types/react@npm:17.0.39" @@ -4787,6 +4838,13 @@ __metadata: languageName: node linkType: hard +"@types/use-sync-external-store@npm:^0.0.3": + version: 0.0.3 + resolution: "@types/use-sync-external-store@npm:0.0.3" + checksum: 161ddb8eec5dbe7279ac971531217e9af6b99f7783213566d2b502e2e2378ea19cf5e5ea4595039d730aa79d3d35c6567d48599f69773a02ffcff1776ec2a44e + languageName: node + linkType: hard + "@types/warning@npm:^3.0.0": version: 3.0.0 resolution: "@types/warning@npm:3.0.0" @@ -9445,7 +9503,7 @@ __metadata: languageName: node linkType: hard -"hoist-non-react-statics@npm:^3.3.1": +"hoist-non-react-statics@npm:^3.3.0, hoist-non-react-statics@npm:^3.3.1, hoist-non-react-statics@npm:^3.3.2": version: 3.3.2 resolution: "hoist-non-react-statics@npm:3.3.2" dependencies: @@ -12887,8 +12945,11 @@ __metadata: version: 0.0.0-use.local resolution: "open-star-ter-village@workspace:." dependencies: + "@reduxjs/toolkit": ^1.8.3 "@types/koa-static": ^4.0.2 + "@types/react-redux": ^7.1.24 koa-static: ^5.0.0 + react-redux: ^8.0.2 languageName: unknown linkType: soft @@ -14514,6 +14575,38 @@ __metadata: languageName: node linkType: hard +"react-redux@npm:^8.0.2": + version: 8.0.2 + resolution: "react-redux@npm:8.0.2" + dependencies: + "@babel/runtime": ^7.12.1 + "@types/hoist-non-react-statics": ^3.3.1 + "@types/use-sync-external-store": ^0.0.3 + hoist-non-react-statics: ^3.3.2 + react-is: ^18.0.0 + use-sync-external-store: ^1.0.0 + peerDependencies: + "@types/react": ^16.8 || ^17.0 || ^18.0 + "@types/react-dom": ^16.8 || ^17.0 || ^18.0 + react: ^16.8 || ^17.0 || ^18.0 + react-dom: ^16.8 || ^17.0 || ^18.0 + react-native: ">=0.59" + redux: ^4 + peerDependenciesMeta: + "@types/react": + optional: true + "@types/react-dom": + optional: true + react-dom: + optional: true + react-native: + optional: true + redux: + optional: true + checksum: 44c1739c45dad04ecc65a290897c73828ff0bf43f2b7618ed5ef6d4ceecedae38e76cecd189a5ecedf579c28ead05427bc000fb45ad30b9fcd5c2be27cd3ac73 + languageName: node + linkType: hard + "react-refresh@npm:^0.11.0": version: 0.11.0 resolution: "react-refresh@npm:0.11.0" @@ -14727,6 +14820,24 @@ __metadata: languageName: node linkType: hard +"redux-thunk@npm:^2.4.1": + version: 2.4.1 + resolution: "redux-thunk@npm:2.4.1" + peerDependencies: + redux: ^4 + checksum: af5abb425fb9dccda02e5f387d6f3003997f62d906542a3d35fc9420088f550dc1a018bdc246c7d23ee852b4d4ab8b5c64c5be426e45a328d791c4586a3c6b6e + languageName: node + linkType: hard + +"redux@npm:^4.0.0, redux@npm:^4.1.2": + version: 4.2.0 + resolution: "redux@npm:4.2.0" + dependencies: + "@babel/runtime": ^7.9.2 + checksum: 75f3955c89b3f18edf5411e5fb482aa2e4f41a416183e8802a6bf6472c4fc3d47675b8b321d147f8af8e0f616436ac507bf5a25f1c4d6180e797b549c7db2c1d + languageName: node + linkType: hard + "redux@npm:^4.1.0": version: 4.1.2 resolution: "redux@npm:4.1.2" @@ -14921,6 +15032,13 @@ __metadata: languageName: node linkType: hard +"reselect@npm:^4.1.5": + version: 4.1.6 + resolution: "reselect@npm:4.1.6" + checksum: 3ea1058422904063ec93c8f4693fe33dcb2178bbf417ace8db5b2c797a5875cf357d9308d11ed3942ee22507dd34ecfbf1f3a21340a4f31c206cab1d36ceef31 + languageName: node + linkType: hard + "resolve-cwd@npm:^3.0.0": version: 3.0.0 resolution: "resolve-cwd@npm:3.0.0" @@ -16985,6 +17103,15 @@ resolve@^2.0.0-next.3: languageName: node linkType: hard +"use-sync-external-store@npm:^1.0.0": + version: 1.2.0 + resolution: "use-sync-external-store@npm:1.2.0" + peerDependencies: + react: ^16.8.0 || ^17.0.0 || ^18.0.0 + checksum: 5c639e0f8da3521d605f59ce5be9e094ca772bd44a4ce7322b055a6f58eeed8dda3c94cabd90c7a41fb6fa852210092008afe48f7038792fd47501f33299116a + languageName: node + linkType: hard + "use@npm:^3.1.0": version: 3.1.1 resolution: "use@npm:3.1.1"