Skip to content

Commit

Permalink
Add follow mode for latest games
Browse files Browse the repository at this point in the history
  • Loading branch information
uncaught committed Apr 11, 2020
1 parent 3800416 commit 31ed8f6
Show file tree
Hide file tree
Showing 7 changed files with 109 additions and 22 deletions.
28 changes: 14 additions & 14 deletions client/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import React, {ReactElement} from 'react';
import {BrowserRouter as Router, Switch} from 'react-router-dom';
import {Switch} from 'react-router-dom';
import './App.css';
import './DarkMode.css';
import Groups from './pages/groups';
Expand All @@ -8,21 +8,21 @@ import useSubscription from '@logux/redux/use-subscription';
import {GroupsLoad} from '@doko/common';
import HandleInvitation from './pages/HandleInvitation';
import Simulation from './pages/Simulation';
import {useFollowLatestGame} from './store/Ui';

export default function App(): ReactElement | null {
useSubscription<GroupsLoad>(['groups/load']);
useFollowLatestGame();

return <Router>
<Switch>
<Page path="/invitation" displayName={'Einladung'}>
<HandleInvitation/>
</Page>
<Page path="/simulation" displayName={'Simulation'}>
<Simulation/>
</Page>
<Page path="/" displayName={'Doppelkopf'}>
<Groups/>
</Page>
</Switch>
</Router>;
return <Switch>
<Page path="/invitation" displayName={'Einladung'}>
<HandleInvitation/>
</Page>
<Page path="/simulation" displayName={'Simulation'}>
<Simulation/>
</Page>
<Page path="/" displayName={'Doppelkopf'}>
<Groups/>
</Page>
</Switch>;
}
4 changes: 4 additions & 0 deletions client/src/DarkMode.css
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ body,
color: var(--std-color);
}

.ui.inverted.icon.menu .item {
--std-color: white;
}

.ui.button,
.ui.dropdown .menu > .item,
.ui.dropdown .menu .selected.item,
Expand Down
17 changes: 16 additions & 1 deletion client/src/PageMenu.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
import React, {ReactElement, useCallback} from 'react';
import {Icon, Menu} from 'semantic-ui-react';
import {Checkbox, Icon, Menu} from 'semantic-ui-react';
import {asLink} from './AsLink';
import {SemanticICONS} from 'semantic-ui-react/dist/commonjs/generic';
import {usePageContext} from './Page';
import {useLatestGroupGame} from './store/Games';
import {useLatestGroupRound} from './store/Rounds';
import {useSelector} from 'react-redux';
import {followLastGameSelector, useSetUi} from './store/Ui';

export interface PageMenuItemConfig {
route: string;
Expand All @@ -21,6 +23,17 @@ function isConfig(item: PageMenuItemConfig | PageMenuItemComp): item is PageMenu
return item.hasOwnProperty('route');
}

function FollowLastGameMenuItem(): ReactElement {
const checked = useSelector(followLastGameSelector);
const setUi = useSetUi();
return <Menu.Item>
<Icon name='forward'/>
<Checkbox label={'Spielen folgen'}
checked={checked}
onChange={(_e, {checked}) => setUi({followLastGame: checked})}/>
</Menu.Item>;
}

export default function PageMenu({closeMenu}: { closeMenu: () => void }): ReactElement | null {
const {groupId, menuItems} = usePageContext<{ groupId?: string }>();
const lastRound = useLatestGroupRound();
Expand Down Expand Up @@ -59,6 +72,8 @@ export default function PageMenu({closeMenu}: { closeMenu: () => void }): ReactE
</>}
</>}

<FollowLastGameMenuItem/>

{menuItems.map((MenuItem, idx) => {
if (isConfig(MenuItem)) {
return <Item key={idx} route={MenuItem.route}>
Expand Down
7 changes: 6 additions & 1 deletion client/src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import badgeMessages from '@logux/client/badge/en';
import log from '@logux/client/log';
import {storeReducer} from './store/Store';
import {getAuth} from './Auth';
import {BrowserRouter as Router} from 'react-router-dom';

const isDev = process.env.NODE_ENV === 'development';
const wsProto = window.location.protocol === 'https:' ? 'wss' : 'ws';
Expand All @@ -35,7 +36,11 @@ if (isDev) {
store.client.start();

ReactDOM.render(
<Provider store={store}><App/></Provider>,
<Provider store={store}>
<Router>
<App/>
</Router>
</Provider>,
document.getElementById('root'),
);

Expand Down
14 changes: 14 additions & 0 deletions client/src/store/Rounds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import useSubscription from '@logux/redux/use-subscription';
import {usePageContext} from '../Page';
import {LoguxDispatch} from './Logux';
import {groupsSelector} from './Groups';
import {createSelector} from 'reselect';
import {memoize} from 'lodash';

const {addReducer, combinedReducer} = createReducer<Rounds>({}, 'rounds');

Expand Down Expand Up @@ -63,6 +65,18 @@ export const roundsReducer = combinedReducer;

export const roundsSelector = (state: State) => state.rounds;

export const getRoundByIdSelector = createSelector(
roundsSelector,
(rounds) => memoize((id: string): Round | null => {
for (const round of Object.values(rounds)) {
if (round[id]) {
return round[id];
}
}
return null;
}),
);

export function useLoadRounds() {
const {groupId} = usePageContext<{ groupId: string }>();
const group = useSelector(groupsSelector)[groupId];
Expand Down
59 changes: 54 additions & 5 deletions client/src/store/Ui.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import {
DeepPartial,
GamesAdd,
GroupMembersInvitationAccepted,
GroupMembersInvitationRejected,
GroupMembersInvitationUsed,
mergeStates,
SubType,
} from '@doko/common';
import {createReducer} from './Reducer';
import {createReducer, isAction} from './Reducer';
import {State} from './Store';
import {useDispatch} from 'react-redux';
import {useDispatch, useSelector, useStore} from 'react-redux';
import {LoguxDispatch} from './Logux';
import {useCallback} from 'react';
import {useCallback, useEffect} from 'react';
import * as LocalStorage from '../LocalStorage';
import {useHistory} from 'react-router-dom';
import {getRoundByIdSelector} from './Rounds';
import Log from '@logux/core/log';

export interface Ui {
acceptedInvitations: { [token: string]: string }, //token => groupId for the invitee
followLastGame: boolean;
rejectedInvitations: string[]; //for the invitee
usedInvitationTokens: string[]; //for the inviter
statistics: {
Expand All @@ -30,6 +36,7 @@ export interface UiSet {

const initial: Ui = {
acceptedInvitations: {},
followLastGame: false,
rejectedInvitations: [],
usedInvitationTokens: [],
statistics: {
Expand All @@ -52,9 +59,30 @@ function addUniqueToken(key: keyof SubType<Ui, string[]>) {
};
}

const {addReducer, combinedReducer} = createReducer<Ui>(initial);
const localStorageSync: Array<keyof Ui> = ['followLastGame'];

addReducer<UiSet>('ui/set', (state, action) => mergeStates(state, action.ui));
function getInitialState(): Ui {
const state = {...initial};
localStorageSync.forEach((key) => {
const parsed = LocalStorage.get<any>(`ui.${key}`);
if (parsed !== null) {
state[key] = parsed;
}
});
return state;
}

const {addReducer, combinedReducer} = createReducer<Ui>(getInitialState());

addReducer<UiSet>('ui/set', (state, action) => {
const newState = mergeStates(state, action.ui);
localStorageSync.forEach((key) => {
if (state[key] !== newState[key]) {
LocalStorage.set(`ui.${key}`, newState[key]);
}
});
return newState;
});

addReducer<GroupMembersInvitationAccepted>('groupMembers/invitationAccepted',
(state, {groupId, token}) => mergeStates(state, {acceptedInvitations: {[token]: groupId}}));
Expand All @@ -66,6 +94,7 @@ addReducer<GroupMembersInvitationUsed>('groupMembers/invitationUsed', addUniqueT
export const uiReducer = combinedReducer;

export const acceptedInvitationsSelector = (state: State) => state.ui.acceptedInvitations;
export const followLastGameSelector = (state: State) => state.ui.followLastGame;
export const rejectedInvitationsSelector = (state: State) => state.ui.rejectedInvitations;
export const usedInvitationTokensSelector = (state: State) => state.ui.usedInvitationTokens;
export const statisticsSelector = (state: State) => state.ui.statistics;
Expand All @@ -76,3 +105,23 @@ export function useSetUi() {
dispatch({type: 'ui/set', ui} as UiSet);
}, [dispatch]);
}

export function useFollowLatestGame() {
const getRoundById = useSelector(getRoundByIdSelector);
const followLastGame = useSelector(followLastGameSelector);
const history = useHistory();
const store = useStore() as unknown as { log: Log };
useEffect(() => {
if (followLastGame) {
return store.log.on('add', (action) => {
if (isAction<GamesAdd>(action, 'games/add')) {
const {game} = action;
const round = getRoundById(game.roundId);
if (round) {
history.push(`/group/${round.groupId}/rounds/round/${game.roundId}/games/game/${game.id}`);
}
}
});
}
}, [followLastGame, history, store, getRoundById]);
}
2 changes: 1 addition & 1 deletion common/@types/logux.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ declare module '@logux/core/log' {
lastTime: number;
sequence: number;

on(event: 'preadd' | 'add' | 'clean', listener: () => void): () => void;
on(event: 'preadd' | 'add' | 'clean', listener: <A extends Action>(action: A) => void): () => void;

add<A extends Action>(action: A, meta: AddMeta): () => void;

Expand Down

0 comments on commit 31ed8f6

Please sign in to comment.