Skip to content

Commit

Permalink
Implement drag/drop of players into their party (closes #11).
Browse files Browse the repository at this point in the history
  • Loading branch information
uncaught committed Apr 10, 2020
1 parent 6367cdb commit 270ef63
Show file tree
Hide file tree
Showing 6 changed files with 207 additions and 76 deletions.
5 changes: 5 additions & 0 deletions client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"@types/node": "^13.1.0",
"@types/qrcode.react": "^1.0.0",
"@types/react": "^16.9.0",
"@types/react-dnd-multi-backend": "^5.0.0",
"@types/react-dom": "^16.9.0",
"@types/react-qr-reader": "^2.1.2",
"@types/react-redux": "^7.1.5",
Expand All @@ -22,6 +23,10 @@
"qrcode.react": "^1.0.0",
"react": "^16.12.0",
"react-app-rewired": "^2.1.5",
"react-dnd": "^10.0.2",
"react-dnd-html5-backend": "^10.0.2",
"react-dnd-multi-backend": "^5.0.0",
"react-dnd-touch-backend": "^10.0.2",
"react-dom": "^16.12.0",
"react-qr-reader": "^2.2.1",
"react-redux": "^7.1.3",
Expand Down
65 changes: 7 additions & 58 deletions client/src/pages/game/GamePlayer.tsx
Original file line number Diff line number Diff line change
@@ -1,70 +1,19 @@
import {gameTypeTexts, GroupMember, PatchableGame, soloLikeGameTypes} from '@doko/common';
import {GroupMember} from '@doko/common';
import React, {ReactElement, useState} from 'react';
import {useGame, usePatchGame} from '../../store/Games';
import {Button, Header, Icon, Label, Modal} from 'semantic-ui-react';
import {Header, Icon, Label, Modal} from 'semantic-ui-react';
import GamePlayerSideModal from './GamePlayerSideModal';
import {useDrag} from 'react-dnd';

export default function GamePlayer({member}: { member: GroupMember }): ReactElement {
const game = useGame()!;
const [open, setOpen] = useState(false);
const patchGame = usePatchGame();
const isSoloLike = soloLikeGameTypes.includes(game.data.gameType);
const isGameTypePlayer = game.data.gameTypeMemberId === member.id;

const chooseSide = (shallBeRe: boolean) => {
const sideKey = shallBeRe ? 're' : 'contra';
const otherSideKey = shallBeRe ? 'contra' : 're';
const inParty = game.data[sideKey].members.includes(member.id);
if (inParty || isGameTypePlayer) {
return; //nothing to do, already in party or gameType relevant player, who cannot be changed here
}

const gamePatch: PatchableGame = {
data: {
[sideKey]: {
members: [
...game.data[sideKey].members,
member.id,
],
},
},
};

if (game.data[otherSideKey].members.includes(member.id)) {
gamePatch.data![otherSideKey] = {members: game.data[otherSideKey].members.filter((id) => id !== member.id)};
}

//Move undecided players to the other party once two members are known:
if (gamePatch.data![sideKey]!.members!.length === 2 || (isSoloLike && shallBeRe)) {
const otherPartyMembers = new Set(game.data.players);
gamePatch.data![sideKey]!.members!.forEach((id) => otherPartyMembers.delete(id));
gamePatch.data![otherSideKey] = {members: [...otherPartyMembers]};
}

patchGame(gamePatch);
setOpen(false);
};

return <div className="memberDetail">
const [, dragRef] = useDrag({item: {type: 'gamePlayer', memberId: member.id}});
return <div ref={dragRef} className="memberDetail">
<Label onClick={() => setOpen(true)}>
{member.name} <Icon name={'user'}/>
</Label>

<Modal open={open} onClose={() => setOpen(false)} basic size='small' closeIcon>
<Header>Partei von {member.name}</Header>
<Modal.Content className="u-flex-row-around u-flex-wrap">
{isGameTypePlayer && <>
<Button inverted color={'green'}>Re</Button>
<div className={'u-flex-center'}>{member.name} spielt {gameTypeTexts.get(game.data.gameType)}</div>
</>}
{!isGameTypePlayer && <>
<Button inverted
color={game.data.re.members.includes(member.id) ? 'green' : undefined}
onClick={() => chooseSide(true)}>Re</Button>
<Button inverted
color={game.data.contra.members.includes(member.id) ? 'green' : undefined}
onClick={() => chooseSide(false)}>Contra</Button>
</>}
</Modal.Content>
<GamePlayerSideModal member={member} close={() => setOpen(false)}/>
</Modal>
</div>;
}
32 changes: 32 additions & 0 deletions client/src/pages/game/GamePlayerSideModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import {gameTypeTexts, GroupMember} from '@doko/common';
import React, {ReactElement} from 'react';
import {useGame} from '../../store/Games';
import {Button, Modal} from 'semantic-ui-react';
import {useSelectGamePlayerSide} from './SelectGamePlayerSide';

export default function GamePlayerSideModal({member, close}: { member: GroupMember; close: () => void }): ReactElement {
const game = useGame()!;
const isGameTypePlayer = game.data.gameTypeMemberId === member.id;
const selectGamePlayerSide = useSelectGamePlayerSide();

const chooseSide = (shallBeRe: boolean) => {
if (selectGamePlayerSide(member.id, shallBeRe)) {
close();
}
};

return <Modal.Content className="u-flex-row-around u-flex-wrap">
{isGameTypePlayer && <>
<Button inverted color={'green'}>Re</Button>
<div className={'u-flex-center'}>{member.name} spielt {gameTypeTexts.get(game.data.gameType)}</div>
</>}
{!isGameTypePlayer && <>
<Button inverted
color={game.data.re.members.includes(member.id) ? 'green' : undefined}
onClick={() => chooseSide(true)}>Re</Button>
<Button inverted
color={game.data.contra.members.includes(member.id) ? 'green' : undefined}
onClick={() => chooseSide(false)}>Contra</Button>
</>}
</Modal.Content>;
}
57 changes: 40 additions & 17 deletions client/src/pages/game/NonPenalty.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,31 +7,54 @@ import ExtraPoints from './ExtraPoints';
import SidePlayers from './SidePlayers';
import GamePips from './GamePips';
import Points from './Points';
import {DndProvider, useDrop} from 'react-dnd';
import {useSelectGamePlayerSide} from './SelectGamePlayerSide';
import MultiBackend from 'react-dnd-multi-backend';
import HTML5toTouch from 'react-dnd-multi-backend/dist/esm/HTML5toTouch';

function Column({Comp, isRe}: { Comp: React.FC<{ isRe: boolean }>; isRe: boolean }): ReactElement {
const selectGamePlayerSide = useSelectGamePlayerSide();
const [, drop] = useDrop<{ memberId: string; type: string }, void, {}>({
accept: 'gamePlayer',
drop(item, monitor) {
const didDrop = monitor.didDrop();
if (didDrop) {
return;
}
selectGamePlayerSide(item.memberId, isRe);
},
collect: (monitor) => ({
isOver: monitor.isOver(),
isOverCurrent: monitor.isOver({shallow: true}),
}),
});
return <div className={'column'} ref={drop}>
<Comp isRe={isRe}/>
</div>;
}

function Row({Comp}: { Comp: React.FC<{ isRe: boolean }> }): ReactElement {
return <Grid.Row>
<Grid.Column>
<Comp isRe={true}/>
</Grid.Column>
<Grid.Column>
<Comp isRe={false}/>
</Grid.Column>
<Column Comp={Comp} isRe={true}/>
<Column Comp={Comp} isRe={false}/>
</Grid.Row>;
}

export default function NonPenalty(): ReactElement {
return <>
<Segment vertical>
<Grid columns={2} relaxed='very' className="tinyVertical">
<Row Comp={Announcements}/>
<Row Comp={ExtraPoints}/>
<Row Comp={SidePlayers}/>
<Row Comp={GamePips}/>
<Row Comp={Points}/>
</Grid>
<Divider vertical>VS</Divider>
</Segment>
<UndecidedPlayers/>
<DndProvider backend={MultiBackend} options={HTML5toTouch}>
<Segment vertical>
<Grid columns={2} relaxed='very' className="tinyVertical">
<Row Comp={Announcements}/>
<Row Comp={ExtraPoints}/>
<Row Comp={SidePlayers}/>
<Row Comp={GamePips}/>
<Row Comp={Points}/>
</Grid>
<Divider vertical>VS</Divider>
</Segment>
<UndecidedPlayers/>
</DndProvider>
<GameCalcBlame/>
</>;
}
43 changes: 43 additions & 0 deletions client/src/pages/game/SelectGamePlayerSide.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {useGame, usePatchGame} from '../../store/Games';
import {useCallback} from 'react';
import {PatchableGame, soloLikeGameTypes} from '@doko/common';

export function useSelectGamePlayerSide(): (memberId: string, shallBeRe: boolean) => boolean {
const game = useGame()!;
const patchGame = usePatchGame();
return useCallback((memberId, shallBeRe) => {
const isSoloLike = soloLikeGameTypes.includes(game.data.gameType);
const isGameTypePlayer = game.data.gameTypeMemberId === memberId;
const sideKey = shallBeRe ? 're' : 'contra';
const otherSideKey = shallBeRe ? 'contra' : 're';
const inParty = game.data[sideKey].members.includes(memberId);
if (inParty || isGameTypePlayer) {
return false; //nothing to do, already in party or gameType relevant player, who cannot be changed here
}

const gamePatch: PatchableGame = {
data: {
[sideKey]: {
members: [
...game.data[sideKey].members,
memberId,
],
},
},
};

if (game.data[otherSideKey].members.includes(memberId)) {
gamePatch.data![otherSideKey] = {members: game.data[otherSideKey].members.filter((id) => id !== memberId)};
}

//Move undecided players to the other party once two members are known:
if (gamePatch.data![sideKey]!.members!.length === 2 || (isSoloLike && shallBeRe)) {
const otherPartyMembers = new Set(game.data.players);
gamePatch.data![sideKey]!.members!.forEach((id) => otherPartyMembers.delete(id));
gamePatch.data![otherSideKey] = {members: [...otherPartyMembers]};
}

patchGame(gamePatch);
return true;
}, [game, patchGame]);
}
81 changes: 80 additions & 1 deletion client/yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,21 @@
resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-1.1.3.tgz#2b5a3ab3f918cca48a8c754c08168e3f03eba61b"
integrity sha512-shAmDyaQC4H92APFoIaVDHCx5bStIocgvbwQyxPRrbUY20V1EYTbSDchWbuwlMG3V17cprZhA6+78JfB+3DTPw==

"@react-dnd/asap@^4.0.0":
version "4.0.0"
resolved "https://registry.yarnpkg.com/@react-dnd/asap/-/asap-4.0.0.tgz#b300eeed83e9801f51bd66b0337c9a6f04548651"
integrity sha512-0XhqJSc6pPoNnf8DhdsPHtUhRzZALVzYMTzRwV4VI6DJNJ/5xxfL9OQUwb8IH5/2x7lSf7nAZrnzUD+16VyOVQ==

"@react-dnd/invariant@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@react-dnd/invariant/-/invariant-2.0.0.tgz#09d2e81cd39e0e767d7da62df9325860f24e517e"
integrity sha512-xL4RCQBCBDJ+GRwKTFhGUW8GXa4yoDfJrPbLblc3U09ciS+9ZJXJ3Qrcs/x2IODOdIE5kQxvMmE2UKyqUictUw==

"@react-dnd/shallowequal@^2.0.0":
version "2.0.0"
resolved "https://registry.yarnpkg.com/@react-dnd/shallowequal/-/shallowequal-2.0.0.tgz#a3031eb54129f2c66b2753f8404266ec7bf67f0a"
integrity sha512-Pc/AFTdwZwEKJxFJvlxrSmGe/di+aAOBn60sremrpLo6VI/6cmiUYNNwlI5KNYttg7uypzA3ILPMPgxB2GYZEg==

"@semantic-ui-react/event-stack@^3.1.0":
version "3.1.1"
resolved "https://registry.yarnpkg.com/@semantic-ui-react/event-stack/-/event-stack-3.1.1.tgz#3263d17511db81a743167fe45281a24b3eb6b3c8"
Expand Down Expand Up @@ -1389,7 +1404,7 @@
resolved "https://registry.yarnpkg.com/@types/history/-/history-4.7.3.tgz#856c99cdc1551d22c22b18b5402719affec9839a"
integrity sha512-cS5owqtwzLN5kY+l+KgKdRJ/Cee8tlmQoGQuIE9tWnSmS3JMKzmxo2HIAk2wODMifGwO20d62xZQLYz+RLfXmw==

"@types/hoist-non-react-statics@^3.3.0":
"@types/hoist-non-react-statics@^3.3.0", "@types/hoist-non-react-statics@^3.3.1":
version "3.3.1"
resolved "https://registry.yarnpkg.com/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#1124aafe5118cb591977aeb1ceaaed1070eb039f"
integrity sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==
Expand Down Expand Up @@ -1459,6 +1474,15 @@
dependencies:
"@types/react" "*"

"@types/react-dnd-multi-backend@^5.0.0":
version "5.0.0"
resolved "https://registry.yarnpkg.com/@types/react-dnd-multi-backend/-/react-dnd-multi-backend-5.0.0.tgz#1faa152e02b88cc44f7fadb569beb3cee5184fe7"
integrity sha512-JqGtyyR60Vt71jRD+cSVyQbFec28TW+C89kldfeDE9dY2dLlTpy2CZnybXUeEtK6PY9Sg8Oo711UJWWnm+Jt8w==
dependencies:
"@types/react" "*"
react-dnd "^10.0.2"
react-dnd-touch-backend "^10.0.2"

"@types/react-dom@^16.9.0":
version "16.9.4"
resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-16.9.4.tgz#0b58df09a60961dcb77f62d4f1832427513420df"
Expand Down Expand Up @@ -3549,6 +3573,20 @@ [email protected]:
arrify "^1.0.1"
path-type "^3.0.0"

dnd-core@^10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/dnd-core/-/dnd-core-10.0.2.tgz#051dc119682ea1185622f954667670d3d5f6a574"
integrity sha512-PrxEjxF0+6Y1n1n1Z9hSWZ1tvnDXv9syL+BccV1r1RC08uWNsyetf8AnWmUF3NgYPwy0HKQJwTqGkZK+1NlaFA==
dependencies:
"@react-dnd/asap" "^4.0.0"
"@react-dnd/invariant" "^2.0.0"
redux "^4.0.4"

dnd-multi-backend@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/dnd-multi-backend/-/dnd-multi-backend-5.0.0.tgz#afa73e541bf434cbf1a46ac717b2b9a580b5f19f"
integrity sha512-an4231UOPvipbcVXczXUHf1u3SNSTouSbu6ZLCPJvAO29yIo3C43ddS2evK5aastg2qKWTOlzI52IGq7q5oGGA==

dns-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/dns-equal/-/dns-equal-1.0.0.tgz#b39e7f1da6eb0a75ba9c17324b34753c47e0654d"
Expand Down Expand Up @@ -8477,6 +8515,47 @@ react-dev-utils@^10.0.0:
strip-ansi "5.2.0"
text-table "0.2.0"

react-dnd-html5-backend@^10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/react-dnd-html5-backend/-/react-dnd-html5-backend-10.0.2.tgz#15cb9d2b923f43576a136df854e288cb5969784c"
integrity sha512-ny17gUdInZ6PIGXdzfwPhoztRdNVVvjoJMdG80hkDBamJBeUPuNF2Wv4D3uoQJLjXssX1+i9PhBqc7EpogClwQ==
dependencies:
dnd-core "^10.0.2"

react-dnd-multi-backend@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/react-dnd-multi-backend/-/react-dnd-multi-backend-5.0.0.tgz#eeb186ac32f46dd2f0ed720b653978d9e1fdfb5c"
integrity sha512-Ql8JKAmFnzzKLxOYUTIQVQZeQd1shbZKJiEbkDY5Vte3hY/YFdv+qs2IDxeH2LX2itBYrnEIgWYcRBacPtPeag==
dependencies:
dnd-multi-backend "^5.0.0"
prop-types "^15.7.2"
react-dnd-preview "^5.0.0"

react-dnd-preview@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/react-dnd-preview/-/react-dnd-preview-5.0.0.tgz#a3a693c27195885a9f7413ccddf46a6fc5ef665a"
integrity sha512-TYXOsLUuEQpwAoxjPLWi+3bXsYGUBAslBFChUN6xyH11/ogsrQ9UrBIFf6uON7wmf2azsLAuWglfffXlZXSRsg==
dependencies:
prop-types "^15.7.2"

react-dnd-touch-backend@^10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/react-dnd-touch-backend/-/react-dnd-touch-backend-10.0.2.tgz#90cb916655539b838d49b8895e1813f8b874b3b4"
integrity sha512-+lW/Ern0dKyHToD0oP+Wc/ZD6l7qJazosLqbjzL7OnPlig6WxdlrHkJylOLkeAdZj41fIJJ551Lb57pIL0CcPw==
dependencies:
"@react-dnd/invariant" "^2.0.0"
dnd-core "^10.0.2"

react-dnd@^10.0.2:
version "10.0.2"
resolved "https://registry.yarnpkg.com/react-dnd/-/react-dnd-10.0.2.tgz#a6ad8eb3d9f2c573031f7ce05012e5c767a0b1fc"
integrity sha512-SC2Ymvntynhoqtf5zaFhZscm9xenCoMofilxPdlwUlaelAzmbl9fw82C4ZJ//+lNm3kWAKXjGDZg2/aWjKEAtg==
dependencies:
"@react-dnd/shallowequal" "^2.0.0"
"@types/hoist-non-react-statics" "^3.3.1"
dnd-core "^10.0.2"
hoist-non-react-statics "^3.3.0"

react-dom@^16.12.0:
version "16.12.0"
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-16.12.0.tgz#0da4b714b8d13c2038c9396b54a92baea633fe11"
Expand Down

0 comments on commit 270ef63

Please sign in to comment.