;\n\nexport const createGameAction = (\n gameName: string,\n interfaceType: InterfaceType,\n actionType: string\n) => createAction(`${gameName}-${interfaceType}-${actionType}`);\n\nexport const createGameActionWithPayload = (\n gameName: string,\n interfaceType: InterfaceType,\n actionType: string\n) => createAction
(`${gameName}-${interfaceType}-${actionType}`);\n\nexport const createGameMessageReceivedAction =
(\n gameName: string,\n interfaceType: InterfaceType,\n actionType: string\n) =>\n createGameActionWithPayload>(\n gameName,\n interfaceType,\n actionType\n );\n\nexport const createReceiveGameMessageReducer = (\n gameName: string,\n initialState: ReduxState,\n caseReducer: CaseReducer>>,\n interfaceType: InterfaceType = \"presenter\",\n builderCallback?: (builder: ActionReducerMapBuilder) => void\n) => {\n const receiveGameMessage = createGameMessageReceivedAction(\n gameName,\n interfaceType,\n \"receive-game-message\"\n );\n return createReducer(initialState, (builder) => {\n builder.addCase(receiveGameMessage, caseReducer);\n builderCallback && builderCallback(builder);\n });\n};\n\nexport const createReceiveReducer = (\n gameName: string,\n initialState: ReduxState,\n caseReducer: CaseReducer>,\n interfaceType: InterfaceType = \"presenter\",\n builderCallback?: (builder: ActionReducerMapBuilder) => void\n) => {\n const receiveGameMessage = createGameActionWithPayload(\n gameName,\n interfaceType,\n \"receive-game-message\"\n );\n return createReducer(initialState, (builder) => {\n builder.addCase(receiveGameMessage as any, caseReducer);\n builderCallback && builderCallback(builder);\n });\n};\n","import { combineReducers } from \"redux\";\nimport {\n createGameAction,\n createGameActionWithPayload,\n createReceiveReducer,\n} from \"store/actionHelpers\";\n\nexport const Name = \"broadcast\";\n\nexport const setTextAction = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"set-text\"\n);\n\nexport const resetAction = createGameAction(Name, \"presenter\", \"reset\");\n\ntype BroadcastClientState = {\n text: string;\n};\n\ntype BroadcastPresenterState = {\n text: string;\n dings: number;\n};\n\nexport type BroadcastState = {\n client: BroadcastClientState;\n presenter: BroadcastPresenterState;\n};\n\nconst broadcastClientReducer = createReceiveReducer<\n string,\n BroadcastClientState\n>(\n Name,\n { text: \"\" },\n (_, action) => {\n return {\n text: action.payload,\n };\n },\n \"client\"\n);\n\nconst initialPresenterState = { dings: 0, text: \"\" };\n\nconst broadcastPresenterReducer = createReceiveReducer<\n string,\n BroadcastPresenterState\n>(\n Name,\n initialPresenterState,\n (state) => ({\n ...state,\n dings: state.dings + 1,\n }),\n \"presenter\",\n (builder) => {\n builder.addCase(setTextAction, (state, action) => ({\n ...state,\n text: action.payload,\n }));\n builder.addCase(resetAction, (state) => initialPresenterState);\n }\n);\n\nexport const broadcastReducer = combineReducers({\n client: broadcastClientReducer,\n presenter: broadcastPresenterReducer,\n});\n","import React, { ChangeEvent, useEffect } from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport { presenterMessage } from \"store/lobby/actions\";\nimport { ContentContainer } from \"components/ContentContainer\";\nimport { makeStyles, TextField } from \"@material-ui/core\";\nimport { useSelector } from \"store/useSelector\";\nimport { resetAction, setTextAction } from \"./BroadcastReducer\";\n\nconst useStyles = makeStyles((theme) => ({\n container: {\n textAlign: \"center\",\n verticalAlign: \"middle\",\n },\n}));\n\nexport const BroadcastPresenter = () => {\n const classes = useStyles();\n const { dings, text } = useSelector(\n (state) => state.games.broadcast.presenter\n );\n const dispatch = useDispatch();\n\n useEffect(() => {\n dispatch(resetAction());\n dispatch(presenterMessage(\"\"));\n }, []);\n\n const updateClientText = (e: ChangeEvent) => {\n const target = e.target as HTMLInputElement;\n dispatch(setTextAction(target.value));\n dispatch(presenterMessage(target.value));\n };\n\n return (\n \n \n
Dings: {dings}
\n \n
\n \n );\n};\n","import React from \"react\";\nimport { useSelector } from \"../../store/useSelector\";\nimport { makeStyles } from \"@material-ui/core\";\nimport { Colors, ColorUtils } from \"../../Colors\";\n\nconst useStyles = makeStyles(() => ({\n root: {\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n height: \"100%\",\n flexDirection: \"column\",\n },\n}));\n\nconst NamePickerClient = () => {\n const user = useSelector((s) => s.user);\n const selectedId = useSelector(\n (state) => state.games.namePicker.player.selectedId\n );\n const won = selectedId === user.id;\n const classes = useStyles();\n\n const getBackgroundColor = () => {\n if (selectedId) {\n if (won) {\n return ColorUtils.toHtml(Colors.Green.C200);\n } else {\n return ColorUtils.toHtml(Colors.Red.C200);\n }\n }\n return \"#eeeeee\";\n };\n\n return (\n \n
{user.name}
\n {selectedId && (\n \n {won && \"You won!\"}\n {!won && \"You lost :(\"}\n
\n )}\n \n );\n};\n\nexport default NamePickerClient;\n","import React, { useState, useEffect } from \"react\";\nimport { Colors, ColorUtils } from \"../../Colors\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport { useSelector } from \"store/useSelector\";\nimport { useDispatch } from \"react-redux\";\nimport { presenterMessage } from \"store/lobby/actions\";\nimport { pick, between } from \"Random\";\n\ninterface AnimatedText extends PIXI.Text {\n dx: number;\n dy: number;\n}\n\nconst speed = 2;\nconst fadeOut = 5;\nconst fadeUpdateMs = 250;\nconst fadeOutMs = fadeOut * 1000;\n\nconst NamePickerPresenter = () => {\n const [app, setApp] = useState();\n const [pickStarted, setPickStarted] = useState();\n const [id, setId] = useState();\n const dispatch = useDispatch();\n\n const users = useSelector((state) => state.lobby.players);\n const { shouldPick } = useSelector(\n (state) => state.games.namePicker.presenter\n );\n\n useEffect(() => {\n const timer = setInterval(() => {\n const alpha = calculateAlpha();\n if (pickStarted && app) {\n app.stage.children.forEach((displayObject) => {\n const text = displayObject as AnimatedText;\n if (text.name !== id) {\n text.alpha = alpha;\n } else {\n text.alpha = 1;\n }\n });\n }\n }, fadeUpdateMs);\n return () => clearTimeout(timer);\n }, [id, pickStarted, app]);\n\n useEffect(() => {\n const timer = setTimeout(() => {\n if (id) {\n dispatch(presenterMessage({ id }));\n }\n }, fadeOutMs);\n return () => clearTimeout(timer);\n }, [id, pickStarted, app]);\n\n useEffect(() => {\n if (shouldPick) {\n if (users.length) {\n setPickStarted(new Date());\n const selected = pick(users).id;\n console.log(\"selecting \" + selected);\n setId(selected);\n }\n } else {\n console.log(\"resetting\");\n setId(undefined);\n setPickStarted(undefined);\n app?.stage.children.forEach((t) => (t.alpha = 1));\n dispatch(presenterMessage({}));\n }\n }, [shouldPick]);\n\n const getText = (id: string, name: string) => {\n const getTextSize = (name: string) => {\n if (name.length > 20) return 24;\n if (name.length > 10) return 32;\n return 48;\n };\n const text = new PIXI.Text(name, {\n fill: ColorUtils.randomColor().shades[4].shade,\n fontSize: getTextSize(name),\n }) as AnimatedText;\n text.name = id;\n text.pivot.set(text.width / 2, text.height / 2);\n text.dx = pick([-speed, speed]);\n text.dy = pick([-speed, speed]);\n return text;\n };\n\n const randomizeOrder = (text: AnimatedText) =>\n app?.stage.addChildAt(text, between(0, app?.stage.children.length - 1));\n\n const calculateAlpha = () => {\n if (pickStarted) {\n const timeDiff = new Date().getTime() - pickStarted.getTime();\n let alpha = (fadeOutMs - timeDiff) / fadeOutMs;\n if (alpha < 0) alpha = 0;\n return alpha;\n }\n return 1;\n };\n\n const draw = (delta: number) => {\n if (app) {\n users.forEach((u) => {\n const text = app.stage.children.find(\n (t) => t.name === u.id\n ) as AnimatedText;\n\n if (text) {\n text.position.x += text.dx * delta;\n text.position.y += text.dy * delta;\n if (text.position.x < text.width / 2) {\n text.position.x = text.width / 2;\n text.dx *= -1;\n randomizeOrder(text);\n }\n if (text.position.x > app.screen.width - text.width / 2) {\n text.position.x = app.screen.width - text.width / 2;\n text.dx *= -1;\n randomizeOrder(text);\n }\n if (text.position.y < text.height / 2) {\n text.position.y = text.height / 2;\n text.dy *= -1;\n randomizeOrder(text);\n }\n if (text.position.y > app.screen.height - text.height / 2) {\n text.position.y = app.screen.height - text.height / 2;\n text.dy *= -1;\n randomizeOrder(text);\n }\n }\n });\n }\n };\n\n useEffect(() => {\n app?.ticker.add(draw);\n return () => {\n app?.ticker.remove(draw);\n };\n }, [app, users]);\n\n useEffect(() => {\n if (app) {\n users.forEach((u) => {\n if (!app.stage.children.find((p) => p.name === u.id)) {\n const text = getText(u.id, u.name);\n text.alpha = u.id === id ? 1 : calculateAlpha();\n randomizeOrder(text);\n text.position.set(\n between(text.width / 2, app.screen.width - text.width / 2),\n between(text.height / 2, app.screen.height - text.height / 2)\n );\n console.log(\n `placing ${u.name} at ${text.position.x},${text.position.y}`\n );\n }\n });\n app.stage.children.forEach((displayObject) => {\n const currentUser = users.find((u) => u.id === displayObject.name);\n if (!currentUser) {\n app.stage.removeChild(displayObject);\n }\n });\n }\n }, [users, app]);\n\n return (\n setApp(app)} />\n );\n};\n\nexport default NamePickerPresenter;\n","import { combineReducers } from \"redux\";\nimport {\n createGameAction,\n createReceiveGameMessageReducer,\n createReceiveReducer,\n} from \"../../store/actionHelpers\";\n\nexport const Name = \"namepicker\";\n\ninterface NamePickerPresenterState {\n shouldPick: boolean;\n}\n\nexport const reset = createGameAction(Name, \"presenter\", \"reset\");\nexport const pick = createGameAction(Name, \"presenter\", \"pick\");\n\nexport const namePickerPresenterReducer = createReceiveGameMessageReducer<\n string,\n NamePickerPresenterState\n>(\n Name,\n { shouldPick: false },\n (state) => state,\n \"presenter\",\n (builder) => {\n builder.addCase(reset, () => ({ shouldPick: false }));\n builder.addCase(pick, () => ({ shouldPick: true }));\n }\n);\n\nexport interface NamePickerState {\n presenter: NamePickerPresenterState;\n player: NamePickerPlayerState;\n}\n\ninterface NamePickerPlayerState {\n selectedId: string | undefined;\n}\n\nexport const namePickerPlayerReducer = createReceiveReducer<\n string,\n NamePickerPlayerState\n>(\n Name,\n { selectedId: undefined },\n (_, action) => ({ selectedId: action.payload }),\n \"client\"\n);\n\nexport const namePickerReducer = combineReducers({\n player: namePickerPlayerReducer,\n presenter: namePickerPresenterReducer,\n});\n","import React from \"react\";\nimport Button from \"../../layout/components/CustomButtons/Button\";\nimport { reset, pick } from \"./NamePickerReducer\";\nimport { useDispatch } from \"react-redux\";\nimport { ListItem } from \"@material-ui/core\";\n\nconst NamePickerMenu = () => {\n const dispatch = useDispatch();\n return (\n <>\n \n \n \n \n \n \n >\n );\n};\n\nexport default NamePickerMenu;\n","import React, { useState, useEffect } from \"react\";\nimport { Colors } from \"../../Colors\";\nimport { Graph } from \"../pixi/Graph\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport { useResizeListener } from \"../pixi/useResizeListener\";\nimport { useSelector } from \"../../store/useSelector\";\n\nconst YesNoMaybePresenter = () => {\n const [pixi, setPixi] = useState();\n const state = useSelector((state) => state.games.yesnomaybe);\n\n const init = (app?: PIXI.Application) => {\n if (app) {\n setPixi(app);\n }\n };\n\n const resize = () => {\n const data = [\n { label: \"Yes\", value: state.yes, color: Colors.Red.C500 },\n { label: \"No\", value: state.no, color: Colors.Blue.C500 },\n { label: \"Maybe\", value: state.maybe, color: Colors.Grey.C500 },\n ];\n if (pixi) {\n pixi.stage.removeChildren();\n new Graph(pixi, data);\n } else {\n console.log(\"no pixi\");\n }\n };\n\n useResizeListener(resize);\n useEffect(() => resize(), [pixi, state]);\n\n return init(app)} />;\n};\n\nexport default YesNoMaybePresenter;\n","import React from \"react\";\nimport Button from \"../../layout/components/CustomButtons/Button\";\nimport { clientMessage } from \"../../store/lobby/actions\";\nimport { makeStyles } from \"@material-ui/core\";\nimport { useDispatch } from \"react-redux\";\n\nconst useStyles = makeStyles((theme) => ({\n container: {\n display: \"flex\",\n flexDirection: \"column\",\n alignItems: \"center\",\n justifyContent: \"center\",\n height: \"100%\",\n },\n button: {\n height: 100,\n width: 300,\n fontSize: 20,\n },\n}));\n\nconst YesNoMaybeClient = () => {\n const dispatch = useDispatch();\n const classes = useStyles();\n\n const choose = (newChoice: string) => {\n dispatch(clientMessage(newChoice));\n };\n\n return (\n \n \n \n
\n );\n};\nexport default YesNoMaybeClient;\n","import { createReceiveGameMessageReducer } from \"../../store/actionHelpers\";\n\nexport interface YesNoMaybeState {\n yes: number;\n no: number;\n maybe: number;\n}\n\nexport const Name = \"yes-no-maybe\";\n\nexport const yesNoMaybeReducer = createReceiveGameMessageReducer(\n Name,\n { yes: 0, no: 0, maybe: 0 },\n (_, action) => action.payload.payload\n);\n","import {\n blackColor,\n whiteColor,\n hexToRgb,\n} from \"../../material-dashboard-react\";\n\nconst cardStyle: any = {\n card: {\n border: \"0\",\n marginBottom: \"30px\",\n marginTop: \"30px\",\n borderRadius: \"6px\",\n color: \"rgba(\" + hexToRgb(blackColor) + \", 0.87)\",\n background: whiteColor,\n width: \"100%\",\n boxShadow: \"0 1px 4px 0 rgba(\" + hexToRgb(blackColor) + \", 0.14)\",\n position: \"relative\",\n display: \"flex\",\n flexDirection: \"column\",\n minWidth: \"0\",\n wordWrap: \"break-word\",\n fontSize: \".875rem\",\n },\n cardPlain: {\n background: \"transparent\",\n boxShadow: \"none\",\n },\n cardProfile: {\n marginTop: \"30px\",\n textAlign: \"center\",\n },\n cardChart: {\n \"& p\": {\n marginTop: \"0px\",\n paddingTop: \"0px\",\n },\n },\n};\n\nexport default cardStyle;\n","import React from \"react\";\n// nodejs library that concatenates classes\nimport classNames from \"classnames\";\n// nodejs library to set properties for components\nimport PropTypes from \"prop-types\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\n// @material-ui/icons\n\n// core components\nimport styles from \"../../assets/jss/material-dashboard-react/components/cardStyle\";\n\nconst useStyles = makeStyles(styles);\n\nexport default function Card(props) {\n const classes = useStyles();\n const { className, children, plain, profile, chart, ...rest } = props;\n const cardClasses = classNames({\n [classes.card]: true,\n [classes.cardPlain]: plain,\n [classes.cardProfile]: profile,\n [classes.cardChart]: chart,\n [className]: className !== undefined,\n });\n return (\n \n {children}\n
\n );\n}\n\nCard.propTypes = {\n className: PropTypes.string,\n plain: PropTypes.bool,\n profile: PropTypes.bool,\n chart: PropTypes.bool,\n children: PropTypes.node,\n};\n","import { grayColor } from \"../../material-dashboard-react\";\n\nconst cardFooterStyle: any = {\n cardFooter: {\n padding: \"0\",\n paddingTop: \"10px\",\n margin: \"0 15px 10px\",\n borderRadius: \"0\",\n justifyContent: \"space-between\",\n alignItems: \"center\",\n display: \"flex\",\n backgroundColor: \"transparent\",\n border: \"0\",\n },\n cardFooterProfile: {\n marginTop: \"-15px\",\n },\n cardFooterPlain: {\n paddingLeft: \"5px\",\n paddingRight: \"5px\",\n backgroundColor: \"transparent\",\n },\n cardFooterStats: {\n borderTop: \"1px solid \" + grayColor[10],\n marginTop: \"20px\",\n \"& svg\": {\n position: \"relative\",\n top: \"4px\",\n marginRight: \"3px\",\n marginLeft: \"3px\",\n width: \"16px\",\n height: \"16px\",\n },\n \"& .fab,& .fas,& .far,& .fal,& .material-icons\": {\n fontSize: \"16px\",\n position: \"relative\",\n top: \"4px\",\n marginRight: \"3px\",\n marginLeft: \"3px\",\n },\n },\n cardFooterChart: {\n borderTop: \"1px solid \" + grayColor[10],\n },\n};\n\nexport default cardFooterStyle;\n","import React from \"react\";\n// nodejs library that concatenates classes\nimport classNames from \"classnames\";\n// nodejs library to set properties for components\nimport PropTypes from \"prop-types\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\n// @material-ui/icons\n\n// core components\nimport styles from \"../../assets/jss/material-dashboard-react/components/cardFooterStyle\";\n\nconst useStyles = makeStyles(styles);\n\nexport default function CardFooter(props) {\n const classes = useStyles();\n const { className, children, plain, profile, stats, chart, ...rest } = props;\n const cardFooterClasses = classNames({\n [classes.cardFooter]: true,\n [classes.cardFooterPlain]: plain,\n [classes.cardFooterProfile]: profile,\n [classes.cardFooterStats]: stats,\n [classes.cardFooterChart]: chart,\n [className]: className !== undefined,\n });\n return (\n \n {children}\n
\n );\n}\n\nCardFooter.propTypes = {\n className: PropTypes.string,\n plain: PropTypes.bool,\n profile: PropTypes.bool,\n stats: PropTypes.bool,\n chart: PropTypes.bool,\n children: PropTypes.node,\n};\n","const cardBodyStyle: any = {\n cardBody: {\n padding: \"0.9375rem 20px\",\n flex: \"1 1 auto\",\n WebkitBoxFlex: \"1\",\n position: \"relative\",\n },\n cardBodyPlain: {\n paddingLeft: \"5px\",\n paddingRight: \"5px\",\n },\n cardBodyProfile: {\n marginTop: \"15px\",\n },\n};\n\nexport default cardBodyStyle;\n","import React from \"react\";\n// nodejs library that concatenates classes\nimport classNames from \"classnames\";\n// nodejs library to set properties for components\nimport PropTypes from \"prop-types\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\n// @material-ui/icons\n\n// core components\nimport styles from \"../../assets/jss/material-dashboard-react/components/cardBodyStyle\";\n\nconst useStyles = makeStyles(styles);\n\nexport default function CardBody(props) {\n const classes = useStyles();\n const { className, children, plain, profile, ...rest } = props;\n const cardBodyClasses = classNames({\n [classes.cardBody]: true,\n [classes.cardBodyPlain]: plain,\n [classes.cardBodyProfile]: profile,\n [className]: className !== undefined,\n });\n return (\n \n {children}\n
\n );\n}\n\nCardBody.propTypes = {\n className: PropTypes.string,\n plain: PropTypes.bool,\n profile: PropTypes.bool,\n children: PropTypes.node,\n};\n","import {\n warningCardHeader,\n successCardHeader,\n dangerCardHeader,\n infoCardHeader,\n primaryCardHeader,\n roseCardHeader,\n whiteColor,\n} from \"../../material-dashboard-react\";\n\nconst cardHeaderStyle: any = {\n cardHeader: {\n padding: \"0.75rem 1.25rem\",\n marginBottom: \"0\",\n borderBottom: \"none\",\n background: \"transparent\",\n zIndex: \"3 !important\",\n \"&$cardHeaderPlain,&$cardHeaderIcon,&$cardHeaderStats,&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader\": {\n margin: \"0 15px\",\n padding: \"0\",\n position: \"relative\",\n color: whiteColor,\n },\n \"&:first-child\": {\n borderRadius: \"calc(.25rem - 1px) calc(.25rem - 1px) 0 0\",\n },\n \"&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader\": {\n \"&:not($cardHeaderIcon)\": {\n borderRadius: \"3px\",\n marginTop: \"-20px\",\n padding: \"15px\",\n },\n },\n \"&$cardHeaderStats svg\": {\n fontSize: \"36px\",\n lineHeight: \"56px\",\n textAlign: \"center\",\n width: \"36px\",\n height: \"36px\",\n margin: \"10px 10px 4px\",\n },\n \"&$cardHeaderStats i,&$cardHeaderStats .material-icons\": {\n fontSize: \"36px\",\n lineHeight: \"56px\",\n width: \"56px\",\n height: \"56px\",\n textAlign: \"center\",\n overflow: \"unset\",\n marginBottom: \"1px\",\n },\n \"&$cardHeaderStats$cardHeaderIcon\": {\n textAlign: \"right\",\n },\n },\n cardHeaderPlain: {\n marginLeft: \"0px !important\",\n marginRight: \"0px !important\",\n },\n cardHeaderStats: {\n \"& $cardHeaderIcon\": {\n textAlign: \"right\",\n },\n \"& h1,& h2,& h3,& h4,& h5,& h6\": {\n margin: \"0 !important\",\n },\n },\n cardHeaderIcon: {\n \"&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader\": {\n background: \"transparent\",\n boxShadow: \"none\",\n },\n \"& i,& .material-icons\": {\n width: \"33px\",\n height: \"33px\",\n textAlign: \"center\",\n lineHeight: \"33px\",\n },\n \"& svg\": {\n width: \"24px\",\n height: \"24px\",\n textAlign: \"center\",\n lineHeight: \"33px\",\n margin: \"5px 4px 0px\",\n },\n },\n warningCardHeader: {\n color: whiteColor,\n \"&:not($cardHeaderIcon)\": {\n ...warningCardHeader,\n },\n },\n successCardHeader: {\n color: whiteColor,\n \"&:not($cardHeaderIcon)\": {\n ...successCardHeader,\n },\n },\n dangerCardHeader: {\n color: whiteColor,\n \"&:not($cardHeaderIcon)\": {\n ...dangerCardHeader,\n },\n },\n infoCardHeader: {\n color: whiteColor,\n \"&:not($cardHeaderIcon)\": {\n ...infoCardHeader,\n },\n },\n primaryCardHeader: {\n color: whiteColor,\n \"&:not($cardHeaderIcon)\": {\n ...primaryCardHeader,\n },\n },\n roseCardHeader: {\n color: whiteColor,\n \"&:not($cardHeaderIcon)\": {\n ...roseCardHeader,\n },\n },\n};\n\nexport default cardHeaderStyle;\n","import React from \"react\";\n// nodejs library that concatenates classes\nimport classNames from \"classnames\";\n// nodejs library to set properties for components\nimport PropTypes from \"prop-types\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\n// @material-ui/icons\n\n// core components\nimport styles from \"../../assets/jss/material-dashboard-react/components/cardHeaderStyle\";\n\nconst useStyles = makeStyles(styles);\n\nexport default function CardHeader(props) {\n const classes = useStyles();\n const { className, children, color, plain, stats, icon, ...rest } = props;\n const cardHeaderClasses = classNames({\n [classes.cardHeader]: true,\n [classes[color + \"CardHeader\"]]: color,\n [classes.cardHeaderPlain]: plain,\n [classes.cardHeaderStats]: stats,\n [classes.cardHeaderIcon]: icon,\n [className]: className !== undefined,\n });\n return (\n \n {children}\n
\n );\n}\n\nCardHeader.propTypes = {\n className: PropTypes.string,\n color: PropTypes.oneOf([\n \"warning\",\n \"success\",\n \"danger\",\n \"info\",\n \"primary\",\n \"rose\",\n ]),\n plain: PropTypes.bool,\n stats: PropTypes.bool,\n icon: PropTypes.bool,\n children: PropTypes.node,\n};\n","import React from \"react\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport CardHeader from \"./CardHeader.js\";\n\nconst styles: any = {\n cardCategoryWhite: {\n color: \"rgba(255,255,255,.62)\",\n margin: \"0\",\n fontSize: \"14px\",\n marginTop: \"0\",\n marginBottom: \"0\",\n },\n cardTitleWhite: {\n color: \"#FFFFFF\",\n marginTop: \"0px\",\n minHeight: \"auto\",\n fontWeight: \"300\",\n fontFamily: \"'Roboto', 'Helvetica', 'Arial', sans-serif\",\n marginBottom: \"3px\",\n textDecoration: \"none\",\n },\n};\n\nconst useStyles = makeStyles(styles);\n\ntype Props = {\n title: string;\n subTitle?: string;\n};\n\nconst CardTitle = ({ title, subTitle }: Props) => {\n const classes = useStyles();\n return (\n \n {title}
\n {subTitle && {subTitle}
}\n \n );\n};\nexport default CardTitle;\n","import {\n primaryColor,\n dangerColor,\n successColor,\n grayColor,\n defaultFont,\n} from \"../../material-dashboard-react.js\";\n\nconst customInputStyle: any = {\n disabled: {\n \"&:before\": {\n backgroundColor: \"transparent !important\",\n },\n },\n underline: {\n \"&:hover:not($disabled):before,&:before\": {\n borderColor: grayColor[4] + \" !important\",\n borderWidth: \"1px !important\",\n },\n \"&:after\": {\n borderColor: primaryColor[0],\n },\n },\n underlineError: {\n \"&:after\": {\n borderColor: dangerColor[0],\n },\n },\n underlineSuccess: {\n \"&:after\": {\n borderColor: successColor[0],\n },\n },\n labelRoot: {\n ...defaultFont,\n color: grayColor[3] + \" !important\",\n fontWeight: \"400\",\n fontSize: \"14px\",\n lineHeight: \"1.42857\",\n letterSpacing: \"unset\",\n },\n labelRootError: {\n color: dangerColor[0],\n },\n labelRootSuccess: {\n color: successColor[0],\n },\n feedback: {\n position: \"absolute\",\n top: \"18px\",\n right: \"0\",\n zIndex: \"2\",\n display: \"block\",\n width: \"24px\",\n height: \"24px\",\n textAlign: \"center\",\n pointerEvents: \"none\",\n },\n marginTop: {\n marginTop: \"16px\",\n },\n formControl: {\n paddingBottom: \"10px\",\n margin: \"27px 0 0 0\",\n position: \"relative\",\n verticalAlign: \"unset\",\n },\n};\n\nexport default customInputStyle;\n","import React, { ReactNode } from \"react\";\nimport classNames from \"classnames\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport FormControl, { FormControlProps } from \"@material-ui/core/FormControl\";\nimport InputLabel, { InputLabelProps } from \"@material-ui/core/InputLabel\";\nimport Input, { InputProps } from \"@material-ui/core/Input\";\n// @material-ui/icons\nimport Clear from \"@material-ui/icons/Clear\";\nimport Check from \"@material-ui/icons/Check\";\n// core components\nimport styles from \"../../assets/jss/material-dashboard-react/components/customInputStyle\";\n\nconst useStyles = makeStyles(styles);\n\ntype Props = {\n labelText: ReactNode;\n labelProps?: InputLabelProps;\n id: string;\n inputProps?: InputProps;\n formControlProps: FormControlProps;\n error?: boolean;\n success?: boolean;\n} & Partial;\n\nexport default function CustomInput(props: Props) {\n const classes = useStyles();\n const {\n formControlProps,\n labelText,\n id,\n labelProps,\n error,\n success,\n ...rest\n } = props;\n\n const labelClasses = classNames({\n [\" \" + classes.labelRootError]: error,\n [\" \" + classes.labelRootSuccess]: success && !error,\n });\n const underlineClasses = classNames({\n [classes.underlineError]: error,\n [classes.underlineSuccess]: success && !error,\n [classes.underline]: true,\n });\n const marginTop = classNames({\n [classes.marginTop]: labelText === undefined,\n });\n return (\n \n {labelText !== undefined ? (\n \n {labelText}\n \n ) : null}\n \n {error ? (\n \n ) : success ? (\n \n ) : null}\n \n );\n}\n","import React from \"react\";\nimport Grid from \"@material-ui/core/Grid\";\nimport CardBody from \"layout/components/Card/CardBody\";\nimport CardTitle from \"layout/components/Card/CardTitle\";\nimport CustomInput from \"layout/components/CustomInput/CustomInput\";\nimport { makeStyles } from \"@material-ui/core\";\n\nexport const useStyles = makeStyles((theme) => ({\n input: {\n margin: 0,\n },\n}));\n\ntype Props = {\n idea: string;\n setIdea: (idea: string) => void;\n maxCharacters: number;\n maxLines: number;\n};\n\nexport const IdeaEntry = (props: Props) => {\n const classes = useStyles();\n const { idea, setIdea, maxCharacters, maxLines } = props;\n\n const onChange = (e: React.ChangeEvent) => {\n const target = e.target as HTMLTextAreaElement;\n if (\n target.value.split(\"\\n\").length <= maxLines &&\n target.value.length < maxCharacters\n )\n setIdea(target.value);\n };\n\n return (\n <>\n \n \n \n \n \n \n \n \n >\n );\n};\n","interface Event {\n name: string;\n label: string;\n handler: Function;\n}\n\nexport const Events = {\n handlers: [] as Event[],\n emit: (name: string) => {\n Events.handlers\n .filter((e) => e.name === name)\n .forEach((e) => {\n e.handler();\n });\n },\n add: (name: string, label: string, handler: Function) => {\n Events.remove(name, label);\n Events.handlers.push({ name: name, label: label, handler: handler });\n },\n remove: (name: string, label: string) => {\n Events.handlers = Events.handlers.filter(\n (e) => !(e.label === label && e.name === name)\n );\n },\n};\n","export function clamp(value, min, max) {\n return Math.min(Math.max(value, min), max);\n}\n","import { clamp } from \"../../util/clamp\";\nimport * as PIXI from \"pixi.js\";\nimport { IdeaView } from \"./IdeaView\";\nimport { Point } from \"./Point\";\nimport { intersects } from \"../../util/intersects\";\nimport { Idea } from \"./Idea\";\n\nexport interface Lane {\n name: string;\n id: number;\n}\n\nexport class IdeaContainer {\n private app: PIXI.Application;\n private ideaWidth: number;\n private margin: number;\n private pointerData: Point | undefined;\n private ideaContainerDrag!: PIXI.Graphics;\n private ideaContainer: PIXI.Container;\n private lanes: Lane[];\n private laneContainer: PIXI.Container;\n private laneLabelBotom = 0;\n\n constructor(\n app: PIXI.Application,\n ideaWidth: number,\n margin: number,\n lanes: Lane[] = []\n ) {\n this.ideaContainer = new PIXI.Container();\n this.laneContainer = new PIXI.Container();\n\n this.app = app;\n\n this.ideaWidth = ideaWidth;\n\n this.margin = margin;\n\n this.setupDrag();\n\n this.pointerData = undefined;\n\n this.lanes = lanes;\n\n if (this.lanes.length) this.setupLanes();\n }\n\n addToStage(stage: PIXI.Container) {\n stage.addChild(\n this.laneContainer,\n this.ideaContainerDrag as PIXI.DisplayObject,\n this.ideaContainer\n );\n }\n\n reset() {\n this.ideaContainer.position.set(0);\n }\n\n clear() {\n this.ideaContainer.removeChildren();\n }\n\n setupLanes() {\n this.laneContainer.removeChildren();\n this.lanes.forEach((lane, ix) => {\n const label = new PIXI.Text(lane.name);\n label.anchor.set(0.5, 0);\n label.position.set(ix * this.laneWidth + this.laneWidth / 2, this.margin);\n this.laneContainer!.addChild(label);\n this.laneLabelBotom = label.position.y + label.height + this.margin;\n });\n\n for (let i = 1; i < this.lanes.length; i++) {\n const g = new PIXI.Graphics();\n g.lineStyle(3, 0x000000);\n g.moveTo(i * this.laneWidth, this.margin);\n g.lineTo(i * this.laneWidth, this.app.screen.height);\n this.laneContainer.addChild(g);\n }\n }\n\n private setupDrag() {\n this.ideaContainerDrag = new PIXI.Graphics();\n this.ideaContainerDrag.interactive = true;\n this.ideaContainerDrag.beginFill(0xff00000, 0);\n this.ideaContainerDrag.drawRect(0, 0, 1, 1);\n this.ideaContainerDrag.endFill();\n\n this.ideaContainerDrag.interactive = true;\n this.ideaContainerDrag.buttonMode = true;\n this.ideaContainerDrag.on(\"pointerdown\", this.onDragStart);\n this.ideaContainerDrag.on(\"pointermove\", this.onDragMove);\n this.ideaContainerDrag.on(\"pointerup\", this.onDragEnd);\n this.ideaContainerDrag.on(\"pointerupoutside\", this.onDragEnd);\n }\n\n add(idea: IdeaView, isNew: boolean = false) {\n if (idea.idea.x === undefined || idea.idea.y === undefined) {\n const point = this.getNextFreeSpot(idea.idea.lane);\n idea.x = point.x;\n idea.y = point.y;\n idea.onDragEnd();\n } else {\n idea.x = idea.idea.x;\n idea.y = idea.idea.y;\n }\n\n this.ideaContainer.addChild(idea);\n }\n\n containsIdea(idea: Idea): boolean {\n return !!this.ideaContainer.children.find(\n (i) => (i as IdeaView).idea && (i as IdeaView).idea.id === idea.id\n );\n }\n\n public get laneWidth() {\n return this.app.screen.width / (this.lanes.length || 1);\n }\n\n private getNextFreeSpot(laneId: number): Point {\n let columns = Math.floor(this.laneWidth / this.ideaWidth) || 1;\n let row = 0;\n while (row < 50) {\n for (let column = 0; column < columns; column++) {\n const x =\n this.laneWidth * laneId +\n column * this.ideaWidth +\n this.margin * column -\n this.ideaContainer.x;\n const y =\n this.laneLabelBotom +\n row * this.ideaWidth +\n this.margin * row -\n this.ideaContainer.y;\n if (this.checkIsEmpty(x, y, laneId)) {\n return { x, y };\n }\n }\n row++;\n }\n return { x: 0, y: 0 };\n }\n\n private checkIsEmpty(x: number, y: number, laneId: number) {\n const rect = { x: x, y: y, width: this.ideaWidth, height: this.ideaWidth };\n return (\n this.ideaContainer.children.filter((c) => {\n const iv = c as IdeaView;\n return intersects(iv, rect) && iv.idea.lane === laneId;\n }).length === 0\n );\n }\n\n private onDragStart = (event: PIXI.interaction.InteractionEvent) => {\n const point = event.data.getLocalPosition(this.app.stage);\n this.pointerData = {\n x: this.ideaContainer.x - point.x,\n y: this.ideaContainer.y - point.y,\n };\n };\n\n private onDragEnd = () => {\n this.pointerData = undefined;\n };\n\n private onDragMove = (event: PIXI.interaction.InteractionEvent) => {\n if (this.pointerData) {\n const point = event.data.getLocalPosition(this.app.stage);\n const x = this.pointerData.x + point.x;\n const y = this.pointerData.y + point.y;\n const mostTop = Math.min(\n ...this.ideaContainer.children.map((p) => this.app.screen.y - p.y)\n );\n let mostLeft = Math.max(...this.ideaContainer.children.map((p) => p.x));\n const mostRight = Math.max(\n ...this.ideaContainer.children.map(\n (p) => this.app.screen.width - p.x - this.ideaWidth\n )\n );\n const mostBottom = Math.max(\n ...this.ideaContainer.children.map(\n (p) => this.app.screen.height - p.y - this.ideaWidth\n )\n );\n this.ideaContainer.position.set(\n clamp(x, -mostLeft, mostRight),\n clamp(y, mostTop, mostBottom)\n );\n }\n };\n\n resize() {\n this.ideaContainerDrag.width = this.app.screen.width;\n this.ideaContainerDrag.height = this.app.screen.height;\n this.setupLanes();\n }\n\n arrange() {\n this.ideaContainer.position.set(0);\n const gap = this.ideaWidth + this.margin;\n let columns = Math.floor(this.app.screen.width / gap);\n\n if (columns * gap - this.margin + this.ideaWidth <= this.app.screen.width)\n columns++;\n\n const ideas = [...this.ideaContainer.children];\n this.ideaContainer.removeChildren();\n\n ideas.forEach((c, i) => {\n const iv = c as IdeaView;\n const point = this.getNextFreeSpot(iv.idea.lane);\n iv.position.set(point.x, point.y);\n this.ideaContainer.addChild(iv);\n iv.onDragEnd(); // saves position\n });\n }\n}\n","import { Rect } from \"./Rect\";\n\nexport function intersects(rect1: Rect, rect2: Rect) {\n var intersects =\n rect1.x + rect1.width > rect2.x &&\n rect1.x < rect2.x + rect2.width &&\n rect1.y + rect1.height > rect2.y &&\n rect1.y < rect2.y + rect2.height;\n return intersects;\n}\n","import * as PIXI from \"pixi.js\";\nimport { Idea } from \"./Idea\";\nimport { Point } from \"./Point\";\n\nconst TITLE_FONT_SIZE = 20;\nconst BODY_FONT_SIZE = 26;\n\nexport class IdeaView extends PIXI.Container {\n background: PIXI.Graphics;\n title: PIXI.Text;\n body: PIXI.Text;\n pointerData: Point | undefined;\n ideaUpdated: (idea: Idea) => void;\n idea: Idea;\n size: number;\n\n constructor(\n idea: Idea,\n size: number,\n margin: number,\n showName: boolean,\n laneWidth: number,\n ideaUpdated: (idea: Idea) => void\n ) {\n super();\n\n this.idea = { ...idea };\n this.size = size;\n\n this.ideaUpdated = ideaUpdated;\n\n this.interactive = true;\n this.buttonMode = true;\n this.on(\"pointerdown\", this.onDragStart);\n this.on(\"pointermove\", this.onDragMove);\n this.on(\"pointerup\", this.onDragEnd);\n this.on(\"pointerupoutside\", this.onDragEnd);\n\n this.title = this.getTitle(idea.playerName, margin, showName);\n this.body = this.getBody(size, laneWidth, margin, idea.idea);\n this.background = this.getBackground(\n idea.color,\n size || this.body.width + 2 * margin,\n size || this.body.height + 2 * margin\n );\n\n this.addChild(this.background as PIXI.DisplayObject, this.title, this.body);\n this.alpha = 0.85;\n this.x = idea.x || 0;\n this.y = idea.y || 0;\n }\n\n private getBackground(color: number, width: number, height: number) {\n const background = new PIXI.Graphics();\n background.beginFill(color);\n background.drawRect(0, 0, width, height);\n background.endFill();\n return background;\n }\n\n getTitle(playerName: string, margin: number, showName: boolean) {\n const title = new PIXI.Text(playerName, { fontSize: TITLE_FONT_SIZE });\n title.x = margin;\n title.visible = showName;\n return title;\n }\n\n getBody(width: number, laneWidth: number, margin: number, content: string) {\n const wordWrapWidth = width ? width - 2 * margin : laneWidth - 4 * margin;\n const body = new PIXI.Text(content, {\n fontSize: BODY_FONT_SIZE,\n breakWords: true,\n wordWrap: true,\n wordWrapWidth: wordWrapWidth,\n align: \"center\",\n });\n if (this.size) {\n body.pivot.set(body.width / 2, body.height / 2);\n body.position.set(this.size / 2, this.size / 2);\n }\n return body;\n }\n\n onDragStart = (event: PIXI.interaction.InteractionEvent) => {\n const point = event.data.getLocalPosition(this.parent);\n this.pointerData = { x: this.x - point.x, y: this.y - point.y };\n this.parent.addChild(this);\n };\n\n onDragEnd = () => {\n this.pointerData = undefined;\n this.idea.x = this.x;\n this.idea.y = this.y;\n this.ideaUpdated(this.idea);\n };\n\n onDragMove = (event: PIXI.interaction.InteractionEvent) => {\n if (this.pointerData) {\n const point = event.data.getLocalPosition(this.parent);\n const x = this.pointerData.x + point.x;\n const y = this.pointerData.y + point.y;\n this.position.set(x, y);\n }\n };\n}\n","import { Component } from \"react\";\nimport { Player } from \"../Player\";\n\nexport interface BaseGameProps {\n setMenuItems(items: JSX.Element[]): void;\n players: Player[];\n}\n\nexport class BaseGame extends Component {\n displayName = BaseGame.name;\n debug: boolean;\n\n constructor(props: T, debug: boolean = false) {\n super(props);\n\n this.debug = debug;\n if (this.debug) console.log(\"constructed\");\n }\n}\n","export function guid() {\n function s4() {\n return Math.floor((1 + Math.random()) * 0x10000)\n .toString(16)\n .substring(1);\n }\n return (\n s4() +\n s4() +\n \"-\" +\n s4() +\n \"-\" +\n s4() +\n \"-\" +\n s4() +\n \"-\" +\n s4() +\n s4() +\n s4()\n );\n}\n","class StorageManager {\n myStorage: Storage;\n constructor(storage: Storage) {\n this.myStorage = storage;\n }\n saveToStorage(storageKey: string, object: object) {\n if (this.myStorage) {\n this.myStorage.setItem(storageKey, JSON.stringify(object));\n }\n }\n getFromStorage(storageKey: string): T | undefined {\n if (this.myStorage) {\n const raw = this.myStorage.getItem(storageKey);\n if (raw) {\n return JSON.parse(raw);\n }\n }\n }\n clearStorage(storageKey: string) {\n if (this.myStorage) {\n this.myStorage.removeItem(storageKey);\n }\n }\n}\n\nexport default StorageManager;\n","import {\n createGameAction,\n createReceiveGameMessageReducer,\n createGameActionWithPayload,\n} from \"../../store/actionHelpers\";\nimport { pick } from \"Random\";\nimport { Idea } from \"./Idea\";\nimport { ServerIdea } from \"./ServerIdea\";\nimport { guid } from \"../../util/guid\";\nimport { Colors } from \"../../Colors\";\nimport StorageManager from \"store/StorageManager\";\nconst storage = new StorageManager(window.localStorage);\n\nconst IDEA_COLORS = [\n Colors.Amber.A200,\n Colors.Green.A200,\n Colors.LightGreen.A200,\n Colors.LightBlue.A200,\n Colors.DeepPurple.A100,\n Colors.Red.A100,\n];\n\nexport const Name = \"idea-wall\";\n\nexport const loadIdeasAction = createGameAction(\n Name,\n \"presenter\",\n \"load-ideas\"\n);\nexport const clearIdeasAction = createGameAction(\n Name,\n \"presenter\",\n \"clear-ideas\"\n);\nexport const arrangeIdeasAction = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"arrange-ideas\"\n);\nexport const toggleNamesAction = createGameAction(\n Name,\n \"presenter\",\n \"toggle-names\"\n);\nexport const ideaUpdatedAction = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"idea-updated\"\n);\n\nexport interface IdeaWallState {\n dynamicSize: boolean;\n storageKey: string;\n showNames: boolean;\n ideas: Idea[];\n pendingArrange: boolean;\n}\n\nconst getNewIdea = (playerName: string, idea: string | ServerIdea): Idea => {\n let content = (idea as ServerIdea).content || (idea as string);\n let lane = (idea as ServerIdea).lane || 0;\n return {\n id: guid(),\n playerName: playerName,\n idea: content,\n lane: lane,\n color: pick(IDEA_COLORS),\n x: undefined,\n y: undefined,\n };\n};\n\nexport const ideaWallReducer = createReceiveGameMessageReducer<\n string,\n IdeaWallState\n>(\n Name,\n {\n dynamicSize: false,\n storageKey: \"ideawall:ideas\",\n showNames: false,\n ideas: [],\n pendingArrange: false,\n },\n (state, { payload: { name, payload } }) => ({\n ...state,\n ideas: [...state.ideas, getNewIdea(name, payload)],\n }),\n \"presenter\",\n (builder) => {\n builder.addCase(toggleNamesAction, (state, action) => ({\n ...state,\n showNames: !state.showNames,\n }));\n builder.addCase(clearIdeasAction, (state) => {\n storage.clearStorage(state.storageKey);\n return {\n ...state,\n ideas: [],\n };\n });\n builder.addCase(loadIdeasAction, (state) => {\n const ideas = storage.getFromStorage(state.storageKey) || [];\n return {\n ...state,\n ideas: ideas,\n };\n });\n builder.addCase(ideaUpdatedAction, (state, { payload: idea }) => {\n const ideas = [\n ...state.ideas.filter((i) => i.id !== idea.id),\n { ...idea },\n ];\n storage.saveToStorage(state.storageKey, ideas);\n return {\n ...state,\n ideas,\n };\n });\n builder.addCase(arrangeIdeasAction, (state, action) => ({\n ...state,\n pendingArrange: action.payload,\n }));\n }\n);\n","import React from \"react\";\nimport { Events } from \"../../Events\";\nimport { IdeaContainer, Lane } from \"./IdeaContainer\";\nimport { IdeaView } from \"./IdeaView\";\nimport { BaseGameProps, BaseGame } from \"../BaseGame\";\nimport { Idea } from \"./Idea\";\nimport { connect, ConnectedProps } from \"react-redux\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport {\n ideaUpdatedAction,\n clearIdeasAction,\n loadIdeasAction,\n arrangeIdeasAction,\n} from \"./IdeaWallReducer\";\nimport { RootState } from \"../../store/RootState\";\n\nconst WIDTH = 200;\nconst MARGIN = 5;\n\ninterface IdeaWallPresenterProps extends BaseGameProps {\n storageKey: string;\n lanes?: Lane[];\n dynamicSize: boolean;\n}\n\ninterface IdeaWallPresenterState {\n ideas: Idea[];\n showNames: boolean;\n}\n\nconst connector = connect((state: RootState) => state.games.ideawall, {\n ideaUpdatedAction,\n clearIdeasAction,\n loadIdeasAction,\n arrangeIdeasAction,\n});\n\ntype PropsFromRedux = ConnectedProps & IdeaWallPresenterProps;\n\nclass IdeaWallPresenter extends BaseGame<\n PropsFromRedux,\n IdeaWallPresenterState\n> {\n displayName = IdeaWallPresenter.name;\n ideaContainer?: IdeaContainer;\n app?: PIXI.Application;\n\n constructor(props: PropsFromRedux) {\n super(props);\n\n this.state = {\n ideas: [],\n showNames: false,\n };\n }\n\n init(app: PIXI.Application) {\n this.app = app;\n this.draw();\n }\n\n draw() {\n if (this.app) {\n this.app.stage.removeChildren();\n this.ideaContainer = new IdeaContainer(\n this.app,\n WIDTH,\n MARGIN,\n this.props.lanes || []\n );\n this.ideaContainer.addToStage(this.app.stage);\n\n this.ideaContainer!.clear();\n this.props.ideas.forEach((idea) => {\n this.addIdeaToContainer(idea);\n });\n }\n }\n\n ideaUpdated = (idea: Idea) => {\n this.props.ideaUpdatedAction(idea);\n };\n\n addIdeaToContainer(idea: Idea, isNew: boolean = false) {\n const view = new IdeaView(\n idea,\n this.props.dynamicSize ? 0 : WIDTH,\n MARGIN,\n this.props.showNames,\n this.ideaContainer!.laneWidth,\n (idea) => this.ideaUpdated(idea)\n );\n this.ideaContainer!.add(view, isNew);\n }\n\n componentDidMount() {\n const resize = () => {\n this.ideaContainer && this.ideaContainer.resize();\n };\n\n resize();\n Events.add(\"onresize\", \"ideawall\", resize);\n this.props.loadIdeasAction();\n this.draw();\n }\n\n componentDidUpdate() {\n if (this.app) {\n if (this.props.ideas.length) {\n this.props.ideas.forEach((idea) => {\n if (!this.ideaContainer!.containsIdea(idea)) {\n this.addIdeaToContainer(idea, true);\n }\n });\n } else {\n this.ideaContainer!.reset();\n }\n if (this.props.pendingArrange) {\n this.ideaContainer!.arrange();\n this.props.arrangeIdeasAction(false);\n }\n this.draw();\n }\n }\n\n componentWillUnmount() {\n Events.remove(\"onresize\", \"ideawall\");\n }\n\n render() {\n return this.init(app)} />;\n }\n}\n\nexport default connector(IdeaWallPresenter);\n","import React, { ReactNode } from \"react\";\n\nimport Button from \"../layout/components/CustomButtons/Button\";\n\nimport {\n Dialog,\n DialogActions,\n DialogContent,\n makeStyles,\n Typography,\n} from \"@material-ui/core\";\n\nconst useStyles = makeStyles((theme) => ({\n paper: {\n width: \"50%\",\n [theme.breakpoints.down(\"sm\")]: {\n width: \"90%\",\n },\n },\n}));\n\ntype Props = {\n header: ReactNode;\n content: ReactNode;\n action: (close: Function) => void;\n setOpen: (open: boolean) => void;\n open: boolean;\n};\n\nexport const ConfirmDialog = (props: Props) => {\n const { header, content, action, setOpen, open } = props;\n\n const handleClose = () => {\n setOpen(false);\n };\n\n const handleOk = () => {\n action(() => setOpen(false));\n };\n\n const classes = useStyles();\n\n const getContent = () => {\n if (typeof content === \"string\")\n return {content};\n else return content;\n };\n const getHeader = () => {\n if (typeof header === \"string\")\n return {header};\n else return header;\n };\n\n return (\n \n );\n};\n","import React, { useState } from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport ListItem from \"@material-ui/core/ListItem\";\nimport Button from \"../../layout/components/CustomButtons/Button\";\nimport {\n clearIdeasAction,\n arrangeIdeasAction,\n toggleNamesAction,\n} from \"./IdeaWallReducer\";\nimport { ConfirmDialog } from \"../../components/ConfirmDialog\";\n\nconst IdeaWallMenu = () => {\n const dispatch = useDispatch();\n const [confirmArrangeDialogOpen, setConfirmArrangeDialogOpen] = useState(\n false\n );\n const [confirmClearDialogOpen, setConfirmClearDialogOpen] = useState(false);\n\n return (\n <>\n \n \n \n \n \n \n \n dispatch(arrangeIdeasAction(true)) &&\n setConfirmArrangeDialogOpen(false)\n }\n />\n \n \n \n \n dispatch(clearIdeasAction()) && setConfirmClearDialogOpen(false)\n }\n />\n >\n );\n};\n\nexport default IdeaWallMenu;\n","import * as PIXI from \"pixi.js\";\n\nexport class Button extends PIXI.Container {\n g1: PIXI.Graphics;\n g2: PIXI.Graphics;\n\n constructor(onPointerUp: Function, onPointerDown: Function) {\n super();\n this.interactive = true;\n this.buttonMode = true;\n this.g1 = new PIXI.Graphics();\n this.g2 = new PIXI.Graphics();\n this.addChild(this.g1);\n this.addChild(this.g2);\n this.on(\"pointerdown\", () => this.down(onPointerDown));\n this.on(\"pointerup\", () => this.up(onPointerUp));\n this.on(\"pointerupoutside\", () => this.up(onPointerUp));\n }\n\n up(callback: Function) {\n this.g2.alpha = 1;\n callback();\n }\n\n down(callback: Function) {\n this.g2.alpha = 0;\n callback();\n }\n\n render(\n upColor: number,\n downColor: number,\n x: number,\n y: number,\n width: number,\n height: number\n ) {\n this.g1.clear();\n this.g1.beginFill(downColor);\n console.log(x, y, width, height);\n this.g1.drawRect(x, y, width, height);\n this.g1.endFill();\n this.g2.clear();\n this.g2.beginFill(upColor);\n this.g2.drawRect(x, y, width, height);\n this.g2.endFill();\n }\n}\n","import React, { useState, useEffect } from \"react\";\nimport { Button } from \"../pixi/Button\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport { Colors } from \"../../Colors\";\nimport { useDispatch } from \"react-redux\";\nimport { clientMessage } from \"../../store/lobby/actions\";\nimport { useResizeListener } from \"../pixi/useResizeListener\";\n\nconst BuzzerClient = () => {\n const [pixi, setPixi] = useState();\n const dispatch = useDispatch();\n const [button] = useState(\n new Button(\n () => dispatch(clientMessage(\"up\")),\n () => dispatch(clientMessage(\"down\"))\n )\n );\n\n const resize = () => {\n if (pixi) {\n pixi.stage.addChild(button);\n button.x = pixi.screen.width / 4;\n button.y = pixi.screen.height / 4;\n button.render(\n Colors.Blue.C400,\n Colors.Red.C400,\n 0,\n 0,\n pixi.screen.width / 2,\n pixi.screen.height / 2\n );\n }\n };\n\n useResizeListener(resize);\n useEffect(resize, [pixi]);\n\n return (\n setPixi(app)}\n />\n );\n};\n\nexport default BuzzerClient;\n","import React from \"react\";\nimport List from \"@material-ui/core/List\";\nimport ListItem from \"@material-ui/core/ListItem\";\nimport { ContentContainer } from \"../../components/ContentContainer\";\nimport { useSelector } from \"../../store/useSelector\";\n\nconst BuzzerPresenter = () => {\n const players = useSelector((state) => state.games.buzzer);\n\n return (\n \n \n {players.map((p) => (\n \n {p.name}\n \n ))}\n
\n \n );\n};\n\nexport default BuzzerPresenter;\n","import React, { useState, useEffect } from \"react\";\nimport { Button } from \"../pixi/Button\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport { Colors } from \"../../Colors\";\nimport { useDispatch } from \"react-redux\";\nimport { clientMessage } from \"../../store/lobby/actions\";\nimport { useResizeListener } from \"../pixi/useResizeListener\";\n\nconst SplatClient = () => {\n const [pixi, setPixi] = useState();\n const dispatch = useDispatch();\n const [button] = useState(\n new Button(\n () => dispatch(clientMessage(\"up\")),\n () => dispatch(clientMessage(\"down\"))\n )\n );\n\n const resize = () => {\n if (pixi) {\n pixi.stage.addChild(button);\n button.x = pixi.screen.width / 4;\n button.y = pixi.screen.height / 4;\n button.render(\n Colors.Blue.C400,\n Colors.Red.C400,\n 0,\n 0,\n pixi.screen.width / 2,\n pixi.screen.height / 2\n );\n }\n };\n\n useResizeListener(resize);\n useEffect(resize, [pixi]);\n\n return (\n setPixi(app)}\n />\n );\n};\n\nexport default SplatClient;\n","import React, { useState, useEffect } from \"react\";\nimport { Colors, ColorUtils } from \"../../Colors\";\nimport { between } from \"../../Random\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport { useSelector } from \"../../store/useSelector\";\n\nconst SplatPresenter = () => {\n const [app, setApp] = useState();\n\n const splats = useSelector((state) => state.games.splat.count);\n\n const draw = () => {\n if (app) {\n while (app.stage.children.length < splats) {\n const x = between(0, app.screen.width);\n const y = between(0, app.screen.height);\n const circle = new PIXI.Graphics()\n .beginFill(ColorUtils.randomColor().shades[4].shade)\n .drawCircle(x, y, between(30, 100))\n .endFill();\n app.stage.addChild(circle);\n }\n }\n };\n\n useEffect(() => draw(), [app, splats]);\n\n return (\n setApp(app)} />\n );\n};\n\nexport default SplatPresenter;\n","import { Colors } from \"../../Colors\";\n\nexport const PongColors = {\n LeftPaddleUp: Colors.Blue.C600,\n LeftPaddleDown: Colors.Blue.C900,\n RightPaddleUp: Colors.Red.C600,\n RightPaddleDown: Colors.Red.C900,\n Background: Colors.BlueGrey.C800,\n ClientBackground: Colors.BlueGrey.C400,\n Ball: Colors.Teal.C400,\n Score: Colors.BlueGrey.C500,\n};\n","import { combineReducers } from \"redux\";\nimport {\n createReceiveReducer,\n createReceiveGameMessageReducer,\n createGameActionWithPayload,\n createGameAction,\n} from \"../../store/actionHelpers\";\nimport { PongColors as Colors } from \"./PongColors\";\nimport { clamp } from \"../../util/clamp\";\n\nexport const Name = \"pong\";\n\ninterface PongState {\n client: PongClientState;\n presenter: PongPresenterState;\n}\n\nexport interface PongClientState {\n releasedColor: number;\n pressedColor: number;\n team: string;\n}\n\nexport interface PongPresenterState {\n paddleHeight: number;\n paddleWidth: number;\n paddleSpeed: number;\n ballSpeed: number;\n score: number[];\n leftSpeed: number;\n rightSpeed: number;\n leftTeam: number;\n rightTeam: number;\n}\n\nexport const rightScores = createGameAction(Name, \"presenter\", \"right-scores\");\nexport const leftScores = createGameAction(Name, \"presenter\", \"left-scores\");\nexport const resetScores = createGameAction(Name, \"presenter\", \"reset-scores\");\nexport const setPaddleHeight = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"set-paddle-height\"\n);\nexport const setPaddleWidth = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"set-paddle-width\"\n);\nexport const setPaddleSpeed = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"set-paddle-speed\"\n);\nexport const setBallSpeed = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"set-ball-speed\"\n);\n\ntype PaddleDyMessage = {\n command: \"paddleDy\";\n left: number;\n right: number;\n};\n\ntype TeamsMessage = {\n command: \"teams\";\n left: number;\n right: number;\n};\n\nconst adminReducer = createReceiveGameMessageReducer<\n PaddleDyMessage | TeamsMessage,\n PongPresenterState\n>(\n Name,\n {\n leftSpeed: 0,\n rightSpeed: 0,\n leftTeam: 0,\n rightTeam: 0,\n paddleSpeed: 200,\n paddleHeight: 5,\n paddleWidth: 55,\n ballSpeed: 3,\n score: [0, 0],\n },\n (\n state,\n {\n payload: {\n payload: { command, left, right },\n },\n }\n ) => {\n switch (command) {\n case \"paddleDy\":\n return {\n ...state,\n leftSpeed: left,\n rightSpeed: right,\n };\n case \"teams\": {\n return {\n ...state,\n leftTeam: left,\n rightTeam: right,\n };\n }\n }\n },\n \"presenter\",\n (builder) => {\n builder.addCase(rightScores, (state) => ({\n ...state,\n score: [state.score[0], state.score[1] + 1],\n }));\n builder.addCase(leftScores, (state) => ({\n ...state,\n score: [state.score[0] + 1, state.score[1]],\n }));\n builder.addCase(resetScores, (state) => ({\n ...state,\n score: [0, 0],\n }));\n builder.addCase(setPaddleHeight, (state, { payload }) => ({\n ...state,\n paddleHeight: clamp(payload, 2, 20),\n }));\n builder.addCase(setPaddleWidth, (state, { payload }) => ({\n ...state,\n paddleWidth: payload,\n }));\n builder.addCase(setPaddleSpeed, (state, { payload }) => ({\n ...state,\n paddleSpeed: clamp(payload, 1, 100),\n }));\n builder.addCase(setBallSpeed, (state, { payload }) => ({\n ...state,\n ballSpeed: payload,\n }));\n }\n);\n\nconst clientReducer = createReceiveReducer(\n Name,\n { releasedColor: 0xffffff, pressedColor: 0xffffff, team: \"\" },\n (_, { payload: response }) => {\n const result = response.split(\":\");\n if (result[0] === \"team\") {\n switch (result[1]) {\n case \"0\":\n return {\n releasedColor: Colors.LeftPaddleUp,\n pressedColor: Colors.LeftPaddleDown,\n team: \"blue\",\n };\n case \"1\":\n return {\n releasedColor: Colors.RightPaddleUp,\n pressedColor: Colors.RightPaddleDown,\n team: \"red\",\n };\n default:\n console.log(`Unexpected response: ${response}`);\n }\n } else {\n console.log(`Unexpected response: ${response}`);\n }\n },\n \"client\"\n);\n\nexport const pongReducer = combineReducers({\n client: clientReducer,\n presenter: adminReducer,\n});\n","import React from \"react\";\nimport { PongColors as Colors } from \"./PongColors\";\nimport * as PIXI from \"pixi.js\";\nimport ReactAnimationFrame from \"react-animation-frame\";\nimport { BaseGame, BaseGameProps } from \"../BaseGame\";\nimport { between } from \"../../Random\";\nimport { connect, ConnectedProps } from \"react-redux\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport { RootState } from \"../../store/RootState\";\nimport { rightScores, leftScores } from \"./PongReducer\";\n\nconst connector = connect((state: RootState) => state.games.pong.presenter, {\n rightScores,\n leftScores,\n});\n\ntype PropsFromRedux = ConnectedProps & BaseGameProps;\n\nconst defaultMaxBounceAngle = 45;\n\nfunction getRadians(degrees: number) {\n return (degrees * Math.PI) / 180;\n}\n\nclass PongPresenter extends BaseGame {\n app!: PIXI.Application;\n score!: PIXI.Text;\n ballDx = 0;\n ballDy = 0;\n leftPaddle!: PIXI.Graphics;\n rightPaddle!: PIXI.Graphics;\n ball!: PIXI.Graphics;\n\n constructor(props: PropsFromRedux) {\n super(props);\n this.ballDx = props.ballSpeed;\n this.state = {\n gameOver: false,\n };\n }\n\n componentDidMount() {\n window.addEventListener(\"resize\", () =>\n setTimeout(() => this.resize(), 510)\n );\n }\n\n clampPaddle(paddle: PIXI.Graphics) {\n if (paddle.y < paddle.width / 2 + paddle.height / 2)\n paddle.y = paddle.width / 2 + paddle.height / 2;\n else if (\n paddle.y >\n this.app.screen.height - paddle.height / 2 - paddle.width / 2\n )\n paddle.y = this.app.screen.height - paddle.height / 2 - paddle.width / 2;\n }\n\n paddleHit(paddle: PIXI.Graphics, direction: number) {\n var relativeIntersect = paddle.y - this.ball.y;\n var normalizedRelativeIntersect = relativeIntersect / (paddle.height / 2);\n var bounceAngle =\n normalizedRelativeIntersect * defaultMaxBounceAngle + 180 * direction;\n\n this.ballDx = this.props.ballSpeed * Math.cos(getRadians(bounceAngle));\n this.ballDy = this.props.ballSpeed * Math.sin(getRadians(bounceAngle));\n\n if (direction === 0) this.ballDy *= -1;\n\n this.ball.x = paddle.x + this.ball.width * (direction === 0 ? 1 : -1); // immediately move ball off paddle - protects from double hit\n\n console.log(\n \"hit\",\n relativeIntersect,\n normalizedRelativeIntersect,\n bounceAngle,\n this.ballDx,\n this.ballDy\n );\n }\n\n checkHit() {\n if (this.ball.y > this.app.renderer.height - this.ball.height / 2) {\n this.ball.y = this.app.renderer.height - this.ball.height / 2;\n this.ballDy *= -1;\n } else if (this.ball.y < this.ball.height / 2) {\n this.ball.y = this.ball.height / 2;\n this.ballDy *= -1;\n }\n\n if (this.ball.x < this.leftPaddle.x + this.leftPaddle.width) {\n // we've reached the left bounds\n if (this.paddleIntersection(this.leftPaddle))\n this.paddleHit(this.leftPaddle, 0);\n else {\n this.props.rightScores();\n console.log(\"death to blue\");\n this.resize();\n }\n } else if (this.ball.x > this.rightPaddle.x - this.rightPaddle.width) {\n // we've reached the right bounds\n if (this.paddleIntersection(this.rightPaddle)) {\n this.paddleHit(this.rightPaddle, -1);\n } else {\n this.props.leftScores();\n console.log(\"death to red\");\n this.resize();\n }\n }\n }\n\n paddleIntersection(paddle: PIXI.Graphics) {\n return (\n this.ball.y > paddle.y - paddle.height / 2 - this.ball.height / 2 &&\n this.ball.y < paddle.y + paddle.height / 2 + this.ball.height / 2\n );\n }\n\n onAnimationFrame(time: number, lastTime: number) {\n const delta = (time - lastTime) / 1000;\n\n if (this.ball) {\n this.ball.y += this.ballDy;\n this.ball.x += this.ballDx;\n }\n\n if (this.leftPaddle && this.rightPaddle) {\n this.leftPaddle.y -=\n this.props.paddleSpeed * delta * this.props.leftSpeed;\n this.rightPaddle.y -=\n this.props.paddleSpeed * delta * this.props.rightSpeed;\n\n this.clampPaddle(this.leftPaddle);\n this.clampPaddle(this.rightPaddle);\n\n this.checkHit();\n }\n }\n\n init(app: PIXI.Application) {\n if (app) {\n this.app = app;\n this.resize();\n }\n }\n\n setPaddleSizes() {\n const paddleWidth = this.app.screen.width / this.props.paddleWidth;\n const paddleHeight = this.app.screen.height / this.props.paddleHeight;\n this.leftPaddle = this.getBlock(\n Colors.LeftPaddleUp,\n paddleWidth,\n paddleHeight,\n this.leftPaddle\n );\n this.rightPaddle = this.getBlock(\n Colors.RightPaddleUp,\n paddleWidth,\n paddleHeight,\n this.rightPaddle\n );\n return { paddleWidth, paddleHeight };\n }\n\n resize() {\n if (this.app) {\n const { paddleWidth } = this.setPaddleSizes();\n\n this.app.stage.removeChildren();\n this.ball = this.getBlock(Colors.Ball, paddleWidth, paddleWidth);\n\n this.leftPaddle.position.set(paddleWidth, this.app.screen.height / 2);\n this.rightPaddle.position.set(\n this.app.screen.width - paddleWidth,\n this.app.screen.height / 2\n );\n this.ball.position.set(\n this.app.screen.width / 2,\n this.app.screen.height / 2\n );\n\n this.score = new PIXI.Text(this.getScore(), {\n fontSize: this.app.renderer.width / 15,\n fill: Colors.Score,\n });\n this.score.anchor.set(0.5, 0);\n this.score.position.set(this.app.screen.width / 2, 0);\n\n this.app.stage.addChild(\n this.score,\n this.leftPaddle,\n this.rightPaddle,\n this.ball\n );\n\n this.setSpeed();\n }\n }\n\n getScore() {\n return `${this.props.score[0]}-${this.props.score[1]}`;\n }\n\n componentDidUpdate(prevProps: PropsFromRedux) {\n if (this.app) {\n this.score.text = this.getScore();\n this.setPaddleSizes();\n if (prevProps.ballSpeed !== this.props.ballSpeed) {\n this.ballDx =\n (this.ballDx / prevProps.ballSpeed) * this.props.ballSpeed;\n this.ballDy =\n (this.ballDy / prevProps.ballSpeed) * this.props.ballSpeed;\n }\n }\n }\n\n setSpeed() {\n const direction = between(1, 2) - 1 || -1;\n\n const angle =\n between(defaultMaxBounceAngle, defaultMaxBounceAngle * 3) * direction;\n\n this.ballDx = this.props.ballSpeed * Math.sin(getRadians(angle));\n this.ballDy = this.props.ballSpeed * Math.cos(getRadians(angle));\n }\n\n getBlock(\n color: number,\n width: number,\n height: number,\n g: PIXI.Graphics = new PIXI.Graphics()\n ) {\n g.clear();\n g.beginFill(color);\n g.drawRect(0, 0, width, height);\n g.pivot.set(width / 2, height / 2);\n return g;\n }\n\n render() {\n return (\n <>\n \n \n \n
\n this.init(app)}\n />\n >\n );\n }\n}\n\nexport default connector(ReactAnimationFrame(PongPresenter));\n","export enum ShapeType {\n Circle,\n Triangle,\n Square,\n}\n","import React from \"react\";\nimport Button from \"../layout/components/CustomButtons/Button\";\nimport { whiteColor } from \"../layout/assets/jss/material-dashboard-react\";\nimport Grid from \"@material-ui/core/Grid\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport Typography from \"@material-ui/core/Typography\";\n\nconst useStyles = makeStyles((theme) => ({\n container: {\n margin: \"0px 6px\",\n },\n stepper: {\n width: \"70px\",\n },\n button: {\n backgroundColor: \"#9d9d9d\",\n fontSize: \"15px\",\n },\n label: {\n textTransform: \"uppercase\",\n color: whiteColor,\n fontSize: \"12px\",\n lineHeight: \".5em\",\n },\n value: {\n fontSize: \"15px\",\n lineHeight: 1.42857143,\n padding: \"12px 30px\",\n background: \"#535353\",\n borderRadius: \"3px\",\n margin: \".3125rem 1px\", // same as button\n },\n}));\n\ninterface StepProps {\n label: string;\n step: number;\n value: number;\n setValue: (value: number) => void;\n}\n\nconst Stepper = ({ label, step, value, setValue }: StepProps) => {\n const classes = useStyles();\n\n const increaseValue = () => setValue(value + step);\n\n const decreaseValue = () => setValue(value - step);\n\n return (\n \n \n {label}\n \n \n \n \n \n {value}\n \n \n \n \n \n );\n};\n\nexport default Stepper;\n","import React from \"react\";\nimport Button from \"../../layout/components/CustomButtons/Button\";\nimport Stepper from \"../../components/Stepper\";\nimport {\n resetScores,\n setPaddleHeight,\n setPaddleWidth,\n setPaddleSpeed,\n setBallSpeed,\n} from \"./PongReducer\";\nimport { useDispatch } from \"react-redux\";\nimport { useSelector } from \"../../store/useSelector\";\nimport { ListItem } from \"@material-ui/core\";\n\nconst PongMenu = () => {\n const dispatch = useDispatch();\n const state = useSelector((state) => state.games.pong.presenter);\n return (\n <>\n \n \n \n \n dispatch(setPaddleHeight(value))}\n />\n \n \n dispatch(setPaddleWidth(value))}\n />\n \n \n dispatch(setPaddleSpeed(value))}\n />\n \n \n dispatch(setBallSpeed(value))}\n />\n \n >\n );\n};\n\nexport default PongMenu;\n","import * as PIXI from \"pixi.js\";\nimport { Colors } from \"../../Colors\";\nimport { Shape } from \"./Shape\";\nimport { ShapeType } from \"./ShapeType\";\n\nexport class ShapeView {\n view: PIXI.Container;\n private countView: PIXI.Text;\n private count = 0;\n private first: PIXI.Text;\n private size: number;\n private graphics: PIXI.Graphics;\n private shape: Shape;\n private radius: number;\n get id() {\n return this.shape.id;\n }\n constructor(size: number, shape: Shape) {\n this.size = size;\n this.view = new PIXI.Container();\n this.countView = new PIXI.Text(\"\", {\n fontSize: size / 5,\n fill: Colors.White,\n });\n this.first = new PIXI.Text(\"\", {\n fontSize: size / 10,\n fill: Colors.BlueGrey.C500,\n });\n this.shape = shape;\n this.radius = (size * 0.9) / 2;\n this.graphics = this.draw();\n this.view.addChild(\n this.graphics,\n this.countView,\n this.first\n );\n }\n\n private draw() {\n const graphics = new PIXI.Graphics().beginFill(this.shape.color);\n\n return drawShape(graphics, this.shape.type, 0, 0, this.radius);\n }\n\n update(count: number, first: string) {\n if (count) {\n this.count = count;\n this.countView.text = this.count.toString();\n this.countView.pivot.set(\n this.countView.width / 2,\n this.countView.height / 2\n );\n }\n if (first) {\n this.first.text = first;\n this.first.pivot.set(\n this.first.width / 2,\n this.first.height / 2 - 20 - this.radius\n );\n }\n }\n}\n\nexport function drawShape(\n graphics: PIXI.Graphics,\n type: ShapeType,\n x: number,\n y: number,\n radius: number\n): PIXI.Graphics {\n switch (type) {\n case ShapeType.Circle:\n return graphics.drawCircle(x, y, radius);\n case ShapeType.Square:\n return graphics.drawRect(x - radius, y - radius, 2 * radius, 2 * radius);\n case ShapeType.Triangle:\n return graphics.drawPolygon([\n x + radius,\n y + radius,\n x,\n y - radius,\n x - radius,\n y + radius,\n x + radius,\n y + radius,\n ]);\n }\n}\n","export const Name = \"reaction\";\n","import { Player } from \"Player\";\nimport { combineReducers } from \"redux\";\nimport {\n createGameAction,\n createGameActionWithPayload,\n createReceiveGameMessageReducer,\n createReceiveReducer,\n} from \"store/actionHelpers\";\nimport { Name } from \"./\";\nimport { Shape } from \"./Shape\";\n\ninterface Choice {\n id: string;\n choice: number;\n isFirst?: boolean;\n}\n\ninterface Score {\n id: string;\n name: string;\n score: number;\n}\n\ninterface ReactionPresenterState {\n shapes: Shape[];\n shape: Shape | undefined;\n showScores: boolean;\n scores: Score[];\n choices: Choice[];\n autoAgain: boolean;\n}\n\ntype Payload = {\n selectedId: number;\n};\n\ntype StartRound = {\n shapes: Shape[];\n shape: Shape;\n};\n\nexport const startRoundAction = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"start-round\"\n);\n\nexport const endRoundAction = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"end-round\"\n);\n\nexport const toggleAutoAgainAction = createGameAction(\n Name,\n \"presenter\",\n \"toggle-auto-again\"\n);\n\nexport const getPlayerName = (players: Player[], id?: string) => {\n const player = players.find((p) => p.id === id);\n return player ? player.name : \"\";\n};\n\nconst reactionPresenterReducer = createReceiveGameMessageReducer<\n Payload,\n ReactionPresenterState\n>(\n Name,\n {\n shapes: [],\n shape: undefined,\n showScores: false,\n scores: [],\n choices: [],\n autoAgain: false,\n },\n (state, action) => {\n const newChoice = {\n id: action.payload.id,\n choice: action.payload.payload.selectedId,\n } as Choice;\n const choices = [...state.choices];\n if (!choices.find((p: Choice) => p.id === newChoice.id)) {\n if (!choices.find((p) => p.choice === newChoice.choice))\n newChoice.isFirst = true;\n choices.push(newChoice);\n }\n return {\n ...state,\n choices,\n };\n },\n \"presenter\",\n (builder) => {\n builder.addCase(endRoundAction, (state, action) => {\n const correct = [...state.choices]\n .filter((p) => p.choice === state.shape!.id)\n .map((choice, ix: number) => {\n return {\n id: choice.id,\n name: getPlayerName(action.payload, choice.id),\n score: +1 + (choice.isFirst ? 1 : 0),\n };\n });\n const wrong = [...state.choices]\n .filter((p) => p.choice !== state.shape!.id)\n .map((choice) => {\n return {\n id: choice.id,\n name: getPlayerName(action.payload, choice.id),\n score: -1,\n };\n });\n\n const newScores = [...state.scores.map((s) => ({ ...s }))];\n\n [...correct, ...wrong].forEach((score) => {\n const existing = newScores.find((p) => p.id === score.id);\n if (existing) existing.score += score.score;\n else if (score.name !== \"\") newScores.push(score);\n });\n\n action.payload.forEach((p) => {\n if (!newScores.filter((s) => s.id === p.id).length)\n newScores.push({ id: p.id, name: p.name, score: 0 });\n });\n\n return {\n ...state,\n showScores: true,\n scores: newScores,\n shape: undefined,\n choices: [],\n };\n });\n builder.addCase(startRoundAction, (state, action) => ({\n ...state,\n shapes: action.payload.shapes,\n shape: action.payload.shape,\n showScores: false,\n }));\n builder.addCase(toggleAutoAgainAction, (state) => {\n return {\n ...state,\n autoAgain: !!!state.autoAgain,\n };\n });\n }\n);\n\nexport const selectShape = createGameActionWithPayload(\n Name,\n \"client\",\n \"select-shape\"\n);\n\ntype ReactionPlayerState = {\n shapes: Shape[];\n selectedId?: number;\n};\n\nconst reactionPlayerReducer = createReceiveReducer<\n ReactionPlayerState,\n ReactionPlayerState\n>(\n Name,\n { shapes: [] },\n (_, action) => action.payload,\n \"client\",\n (builder) => {\n builder.addCase(selectShape, (state, action) => ({\n ...state,\n selectedId: action.payload,\n }));\n }\n);\n\nexport type ReactionState = {\n player: ReactionPlayerState;\n presenter: ReactionPresenterState;\n};\n\nexport const reactionReducer = combineReducers({\n player: reactionPlayerReducer,\n presenter: reactionPresenterReducer,\n});\n","import {\n warningColor,\n primaryColor,\n dangerColor,\n successColor,\n infoColor,\n roseColor,\n grayColor,\n defaultFont,\n} from \"../../material-dashboard-react\";\n\nconst tableStyle = (theme: any) => ({\n warningTableHeader: {\n color: warningColor[0],\n },\n primaryTableHeader: {\n color: primaryColor[0],\n },\n dangerTableHeader: {\n color: dangerColor[0],\n },\n successTableHeader: {\n color: successColor[0],\n },\n infoTableHeader: {\n color: infoColor[0],\n },\n roseTableHeader: {\n color: roseColor[0],\n },\n grayTableHeader: {\n color: grayColor[0],\n },\n table: {\n marginBottom: \"0\",\n width: \"100%\",\n maxWidth: \"100%\",\n backgroundColor: \"transparent\",\n borderSpacing: \"0\",\n borderCollapse: \"collapse\",\n },\n tableHeadCell: {\n color: \"inherit\",\n ...defaultFont,\n \"&, &$tableCell\": {\n fontSize: \"1em\",\n },\n },\n tableCell: {\n ...defaultFont,\n lineHeight: \"1.42857143\",\n padding: \"12px 8px\",\n verticalAlign: \"middle\",\n fontSize: \"0.8125rem\",\n },\n tableResponsive: {\n width: \"100%\",\n marginTop: theme.spacing(3),\n overflowX: \"auto\",\n },\n tableHeadRow: {\n height: \"56px\",\n color: \"inherit\",\n display: \"table-row\",\n outline: \"none\",\n verticalAlign: \"middle\",\n },\n tableBodyRow: {\n height: \"48px\",\n color: \"inherit\",\n display: \"table-row\",\n outline: \"none\",\n verticalAlign: \"middle\",\n },\n});\n\nexport default tableStyle;\n","import React from \"react\";\nimport PropTypes from \"prop-types\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport Table from \"@material-ui/core/Table\";\nimport TableHead from \"@material-ui/core/TableHead\";\nimport TableRow from \"@material-ui/core/TableRow\";\nimport TableBody from \"@material-ui/core/TableBody\";\nimport TableCell from \"@material-ui/core/TableCell\";\n// core components\nimport styles from \"../../assets/jss/material-dashboard-react/components/tableStyle\";\n\nconst useStyles = makeStyles(styles);\n\nexport default function CustomTable(props) {\n const classes = useStyles();\n const { tableHead, tableData, tableHeaderColor } = props;\n return (\n \n
\n {tableHead !== undefined ? (\n \n \n {tableHead.map((prop, key) => {\n return (\n \n {prop}\n \n );\n })}\n \n \n ) : null}\n \n {tableData.map((prop, key) => {\n return (\n \n {prop.map((prop, key) => {\n return (\n \n {prop}\n \n );\n })}\n \n );\n })}\n \n
\n
\n );\n}\n\nCustomTable.defaultProps = {\n tableHeaderColor: \"gray\",\n};\n\nCustomTable.propTypes = {\n tableHeaderColor: PropTypes.oneOf([\n \"warning\",\n \"primary\",\n \"danger\",\n \"success\",\n \"info\",\n \"rose\",\n \"gray\",\n ]),\n tableHead: PropTypes.arrayOf(PropTypes.string),\n tableData: PropTypes.arrayOf(PropTypes.arrayOf(PropTypes.string)),\n};\n","import React, { useEffect, useState } from \"react\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport Typography from \"@material-ui/core/Typography\";\nimport CustomInput from \"layout/components/CustomInput/CustomInput\";\nimport Alert from \"@material-ui/lab/Alert\";\nimport { useDispatch } from \"react-redux\";\nimport { Question } from \"../types/Question\";\nimport { guid } from \"util/guid\";\nimport { Answer } from \"games/shared/Poll/types/Answer\";\nimport { presenterActions } from \"games/shared/Poll/reducers/presenterActions\";\nimport { ConfirmDialog } from \"components/ConfirmDialog\";\n\nconst useStyles = makeStyles((theme) => ({\n form: {\n marginTop: theme.spacing(2),\n },\n input: {\n fontFamily: \"monospace\",\n lineHeight: 1,\n },\n}));\n\nexport enum ErrorMessages {\n FIRST_LINE_SHOULD_BE_QUESTION = \"First line should be a question, starting with a dash\",\n ONLY_ONE_CORRECT_ANSWER_PER_QUESTION = \"A question may have maximum one correct answer\",\n ONLY_TRIVIA_MODE_MAY_HAVE_CORRECT_ANSWERS = \"Poll questions do not have correct answers, try Trivia instead\",\n}\n\ntype ValidateResponse = {\n isValid: boolean;\n questions: Question[];\n errorMessage: string | undefined;\n errorLine: number | undefined;\n};\n\nexport const validate = (\n questionsAndAnswers: string,\n isTriviaMode: boolean\n): ValidateResponse => {\n let questions: Question[] = [];\n let errorMessage: string | undefined;\n let errorLine: number | undefined;\n\n const lines = questionsAndAnswers\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line); // remove empty lines\n\n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n if (line.startsWith(\"-\")) {\n const trimmed = line.substr(1).trim();\n questions.push({\n answers: [],\n id: guid(),\n responses: [],\n text: trimmed,\n });\n } else if (i === 0) {\n errorLine = 1;\n errorMessage = ErrorMessages.FIRST_LINE_SHOULD_BE_QUESTION;\n break;\n } else if (line.startsWith(\"*\")) {\n if (!isTriviaMode) {\n errorMessage = ErrorMessages.ONLY_TRIVIA_MODE_MAY_HAVE_CORRECT_ANSWERS;\n errorLine = i + 1;\n break;\n } else {\n const trimmed = line.substr(1).trim();\n const currentAnswers = questions[questions.length - 1].answers;\n if (currentAnswers.find((a) => a.correct)) {\n errorMessage = ErrorMessages.ONLY_ONE_CORRECT_ANSWER_PER_QUESTION;\n errorLine = i + 1;\n break;\n }\n currentAnswers.push({\n correct: true,\n id: guid(),\n text: trimmed,\n });\n }\n } else {\n const trimmed = line.trim();\n const answer: Answer = {\n id: guid(),\n text: trimmed,\n };\n if (isTriviaMode) {\n answer.correct = false;\n }\n questions[questions.length - 1].answers.push(answer);\n }\n }\n\n return {\n isValid: errorMessage === undefined,\n questions,\n errorMessage,\n errorLine,\n };\n};\n\nconst questionsForBulkEditSelector = (\n questions: Question[],\n isTriviaMode: boolean\n) =>\n questions\n .map((q) => {\n const answers = q.answers.map((a) => {\n if (isTriviaMode) {\n if (a.correct) return `* ${a.text}`;\n }\n return a.text;\n });\n return `- ${q.text}\\n${answers.join(\"\\n\")}`;\n })\n .join(\"\\n\");\n\ntype Props = {\n gameName: string;\n isTriviaMode: boolean;\n questions: Question[];\n open: boolean;\n setOpen: (open: boolean) => void;\n};\nexport const BulkEdit = (props: Props) => {\n const { gameName, isTriviaMode, questions, open, setOpen } = props;\n const { importQuestionsAction } = presenterActions(gameName);\n const classes = useStyles();\n const dispatch = useDispatch();\n const questionsFromRedux = questionsForBulkEditSelector(\n questions,\n isTriviaMode\n );\n\n const [questionLines, setQuestionLines] = useState(\n questionsFromRedux\n );\n useEffect(() => {\n setQuestionLines(questionsFromRedux);\n }, [questionsFromRedux]);\n const [error, setError] = useState(\"\");\n\n const handleOk = () => {\n const { errorLine, errorMessage, isValid, questions } = validate(\n questionLines,\n isTriviaMode\n );\n if (isValid) {\n setError(\"\");\n dispatch(importQuestionsAction(questions));\n setOpen(false);\n } else {\n setError(`Line ${errorLine}: ${errorMessage}`);\n }\n };\n\n const onChange = (e: React.ChangeEvent) => {\n const target = e.target as HTMLTextAreaElement;\n setQuestionLines(target.value);\n };\n\n return (\n \n \n \n - First line should be a question\n \n \n - Questions start with a dash\n \n \n Answers are just plain lines like this\n \n {gameName !== \"poll\" && (\n \n * Correct answers start with an asterix (zero or one per\n question)\n \n )}\n \n \n \n Using bulk edit will clear audience responses\n \n \n 0}\n className={classes.input}\n formControlProps={{\n className: classes.form,\n fullWidth: true,\n }}\n value={questionLines}\n onChange={onChange}\n />\n {error.length > 0 && {error}}\n >\n }\n />\n );\n};\n","export const Name = \"poll\";\n","import { Question } from \"games/shared/Poll/types/Question\";\nimport { RootState } from \"store/RootState\";\nimport { createSelector } from \"@reduxjs/toolkit\";\n\nexport interface GameState {\n currentQuestionId: string | undefined;\n questions: Question[];\n showResponses: boolean;\n}\n\nexport const currentQuestionSelector = (\n gameStateSelector: (state: RootState) => GameState\n) =>\n createSelector(\n (state: RootState) => gameStateSelector(state),\n (state) => {\n const question = state.questions.find(\n (q) => q.id === (state.currentQuestionId || \"\")\n );\n const currentQuestionId = state.currentQuestionId;\n const totalQuestions = state.questions.length;\n const responseCount = question?.responses?.length || 0;\n const questionIds = state.questions.map((q) => q.id);\n const currentQuestionIndex = currentQuestionId\n ? questionIds.indexOf(currentQuestionId)\n : -1;\n const currentQuestionNumber = currentQuestionIndex + 1;\n const previousQuestionId =\n currentQuestionIndex > 0 ? questionIds[currentQuestionIndex - 1] : null;\n const nextQuestionId =\n currentQuestionIndex !== -1 &&\n currentQuestionIndex < questionIds.length + 1\n ? questionIds[currentQuestionIndex + 1]\n : null;\n return {\n currentQuestionId,\n question,\n questionIds,\n responseCount,\n previousQuestionId,\n nextQuestionId,\n currentQuestionNumber,\n totalQuestions,\n showResponses: state.showResponses,\n };\n }\n );\n","import React, { useState, useCallback } from \"react\";\nimport { Text } from \"recharts\";\n\n// https://github.com/recharts/recharts/issues/961#issuecomment-618265830\n\ninterface Props {\n maxLines: number;\n payload?: { value: string };\n props?: any;\n}\n\nconst CustomisedAccessTick = ({ maxLines = 3, payload, ...rest }: Props) => {\n const [text, setText] = useState(payload!.value);\n const [suffix, setSuffix] = useState(\"\");\n\n const measuredRef = useCallback(\n (node) => {\n if (node === null) {\n return;\n }\n\n let numberOfLines = node.state.wordsByLines.length;\n let tempText = text;\n const tempSuffix = numberOfLines > maxLines ? \"…\" : \"\";\n\n while (numberOfLines > maxLines) {\n tempText = tempText.slice(0, -1);\n numberOfLines = node.getWordsByLines(\n {\n ...rest,\n children: tempText + tempSuffix,\n },\n true\n ).length;\n }\n\n if (tempText !== text) {\n setText(tempText);\n setSuffix(tempSuffix);\n }\n },\n [maxLines, rest, text]\n );\n\n return (\n \n \n {text + suffix}\n \n {payload!.value}\n \n );\n};\n\nexport default CustomisedAccessTick;\n","import React from \"react\";\nimport { useSelector } from \"../../../../store/useSelector\";\nimport {\n currentQuestionSelector,\n GameState,\n} from \"../reducers/currentQuestionSelector\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport { BarChart, Bar, XAxis, YAxis, ResponsiveContainer } from \"recharts\";\nimport { primaryColor } from \"../../../../layout/assets/jss/material-dashboard-react\";\nimport CustomisedAxisTick from \"./CustomisedAccessTick\";\nimport { RootState } from \"store/RootState\";\n\nconst useStyles = makeStyles((theme) => ({\n data: {\n display: \"none\",\n },\n container: {\n padding: theme.spacing(3),\n width: \"80%\",\n height: \"80%\",\n },\n paper: {\n height: \"100%\",\n padding: theme.spacing(3),\n },\n cardHeader: {\n height: \"100%\",\n },\n chart: {\n height: \"100%\",\n \"& .ct-label\": {\n fontSize: \"20px\",\n },\n },\n question: {\n margin: 0,\n },\n}));\n\ntype Props = {\n gameStateSelector: (state: RootState) => GameState;\n isTriviaMode: boolean;\n};\nconst ResponseChart = ({ gameStateSelector, isTriviaMode }: Props) => {\n const classes = useStyles();\n const { question } = useSelector(currentQuestionSelector(gameStateSelector));\n\n const answers = question\n ? question.answers.map((a) => {\n let answer = a.text;\n if (isTriviaMode && a.correct) {\n answer = `✅ ${a.text}`;\n }\n const responses = (question ? question.responses : []).filter(\n (r) => r.answerId === a.id\n ).length;\n return {\n id: a.id,\n answer,\n responses,\n };\n })\n : [];\n\n const data = answers.map((a) => ({ name: a.answer, count: a.responses }));\n\n return (\n <>\n {question && (\n \n
\n {answers.map((a) => (\n - \n {a.responses}\n
\n ))}\n
\n
\n \n \n }\n width={100}\n tickLine={false}\n />\n \n \n \n
{question.text}
\n
\n )}\n >\n );\n};\n\nexport default ResponseChart;\n","import {\n createGameAction,\n createGameActionWithPayload,\n} from \"store/actionHelpers\";\nimport { Question } from \"../types/Question\";\n\nexport const presenterActions = (gameName: string) => {\n const toggleShowResponsesAction = createGameAction(\n gameName,\n \"presenter\",\n \"toggle-show-responses\"\n );\n const clearResponsesAction = createGameAction(\n gameName,\n \"presenter\",\n \"clear-responses\"\n );\n const addQuestionAction = createGameActionWithPayload(\n gameName,\n \"presenter\",\n \"add-question\"\n );\n const updateQuestionAction = createGameActionWithPayload(\n gameName,\n \"presenter\",\n \"update-question\"\n );\n const deleteQuestionAction = createGameActionWithPayload(\n gameName,\n \"presenter\",\n \"delete-question\"\n );\n const importQuestionsAction = createGameActionWithPayload(\n gameName,\n \"presenter\",\n \"import-questions\"\n );\n const setCurrentQuestionAction = createGameActionWithPayload(\n gameName,\n \"presenter\",\n \"set-current-question\"\n );\n return {\n toggleShowResponsesAction,\n clearResponsesAction,\n addQuestionAction,\n updateQuestionAction,\n deleteQuestionAction,\n importQuestionsAction,\n setCurrentQuestionAction,\n };\n};\n","import { Question } from \"../types/Question\";\nimport { guid } from \"../../../../util/guid\";\nimport { PresenterState } from \"../types/PresenterState\";\nimport { ActionReducerMapBuilder } from \"@reduxjs/toolkit\";\nimport { presenterActions } from \"games/shared/Poll/reducers/presenterActions\";\nimport StorageManager from \"store/StorageManager\";\n\nconst storage = new StorageManager(window.localStorage);\nexport type ShouldShowResponses = (\n state: S,\n newQuestion: Question | undefined\n) => boolean;\n\nexport const presenterActionReducers = (\n gameName: string,\n storageKey: string\n) => (\n builder: ActionReducerMapBuilder,\n shouldShowResponses: ShouldShowResponses\n) => {\n const {\n toggleShowResponsesAction,\n clearResponsesAction,\n addQuestionAction,\n updateQuestionAction,\n deleteQuestionAction,\n importQuestionsAction,\n setCurrentQuestionAction,\n } = presenterActions(gameName);\n\n builder.addCase(addQuestionAction, (state, action) => {\n const questions = [\n ...state.questions,\n {\n id: action.payload,\n isVisible: true,\n order: state.questions.length,\n responses: [],\n text: \"Change this text to your question\",\n answers: [\n {\n id: guid(),\n text: \"An answer\",\n correct: false,\n },\n ],\n },\n ];\n storage.saveToStorage(storageKey, questions);\n const currentQuestionId = state.questions.length\n ? state.currentQuestionId\n : action.payload; // TODO: Why? This is being done because the currentQuestionSelector is not being recalculated unless save is hit\n return {\n currentQuestionId,\n ...state,\n questions,\n } as T;\n });\n builder.addCase(updateQuestionAction, (state, { payload: question }) => {\n const questions = state.questions.map((q) =>\n q.id !== question.id ? q : question\n );\n storage.saveToStorage(storageKey, questions);\n return {\n ...state,\n questions,\n } as T;\n });\n builder.addCase(deleteQuestionAction, (state, { payload: question }) => {\n const questions = state.questions.filter((q) => q.id !== question.id);\n storage.saveToStorage(storageKey, questions);\n return {\n ...state,\n questions,\n } as T;\n });\n builder.addCase(importQuestionsAction, (state, { payload: questions }) => {\n storage.saveToStorage(storageKey, questions);\n let currentQuestionId: string | undefined;\n if (questions.length) {\n currentQuestionId = questions[0].id;\n }\n return {\n ...state,\n questions,\n currentQuestionId,\n } as T;\n });\n builder.addCase(\n setCurrentQuestionAction,\n (state, { payload: currentQuestionId }) => {\n const newQuestion = state.questions.find(\n (q) => q.id === currentQuestionId\n );\n const showResponses = shouldShowResponses(state, newQuestion);\n return {\n ...state,\n showScoreBoard: false,\n showResponses,\n currentQuestionId,\n } as T;\n }\n );\n\n builder.addCase(\n toggleShowResponsesAction,\n (state: PresenterState) =>\n ({\n ...state,\n showResponses: !state.showResponses,\n } as T)\n );\n\n builder.addCase(\n clearResponsesAction,\n (state) =>\n ({\n ...state,\n questions: state.questions.map((q) => ({\n ...q,\n responses: [],\n })),\n } as T)\n );\n};\n","import { Question } from \"../types/Question\";\nimport StorageManager from \"store/StorageManager\";\n\nconst storage = new StorageManager(window.localStorage);\nexport const initialPresenterState = (storageKey: string) => ({\n questions: (storage.getFromStorage(storageKey) || []) as Question[],\n currentQuestionId: undefined,\n showResponses: false,\n});\n","import { Question } from \"games/shared/Poll/types/Question\";\nimport { SelectedAnswer } from \"games/shared/Poll/types/SelectedAnswer\";\nimport { PresenterState } from \"games/shared/Poll/types/PresenterState\";\n\nexport const presenterPayloadReducer = (\n state: S,\n answers: SelectedAnswer[],\n playerId: string,\n playerName: string\n) => {\n const questions: Question[] = state.questions.map((q) => {\n const answer = answers.find((a) => a.questionId === q.id);\n if (answer) {\n return {\n ...q,\n responses: [\n ...q.responses.filter((r) => r.playerId !== playerId),\n { playerName, playerId, answerId: answer.answerId },\n ],\n };\n } else {\n return q;\n }\n });\n return {\n ...state,\n questions,\n };\n};\n","export const Name = \"trivia\";\n","import {\n createGameAction,\n createReceiveGameMessageReducer,\n} from \"store/actionHelpers\";\nimport { Question } from \"games/shared/Poll/types/Question\";\nimport { SelectedAnswer } from \"games/shared/Poll/types/SelectedAnswer\";\nimport {\n PresenterState,\n TriviaPresenterState,\n} from \"games/shared/Poll/types/PresenterState\";\nimport { Player } from \"Player\";\nimport {\n presenterActionReducers,\n ShouldShowResponses,\n} from \"games/shared/Poll/reducers/presenterActionReducers\";\nimport { initialPresenterState } from \"games/shared/Poll/reducers/initialPresenterState\";\nimport { presenterPayloadReducer } from \"games/shared/Poll/reducers/presenterPayloadReducer\";\nimport { Name } from \"..\";\n\nexport const storageKey = \"trivia:questions\";\n\ntype UserScore = {\n name: string;\n id: string;\n score: number;\n};\n\nexport const sort = (userScores: UserScore[]) => {\n return userScores.sort((n1, n2) => {\n if (n1.score < n2.score) {\n return 1;\n }\n\n if (n1.score > n2.score) {\n return -1;\n }\n\n return 0;\n });\n};\n\nexport const getPlayersWithNoScore = (players: Player[], scores: UserScore[]) =>\n players\n .filter((user) => !scores.find((score) => score.id === user.id))\n .map((user) => ({ name: user.name, score: 0, id: user.id }));\n\nexport const toggleShowScoreBoardAction = createGameAction(\n Name,\n \"presenter\",\n \"toggle-show-scoreboard\"\n);\n\nconst shouldShowTriviaResponses = (\n state: S,\n newQuestion: Question | undefined\n) => {\n let showResponses = state.showResponses;\n if (newQuestion && newQuestion.answers.filter((a) => a.correct).length)\n showResponses = false;\n return showResponses;\n};\n\nexport const triviaPresenterReducer = createReceiveGameMessageReducer<\n SelectedAnswer[],\n TriviaPresenterState\n>(\n Name,\n { ...initialPresenterState(storageKey), showScoreBoard: false },\n (state, { payload: { id: playerId, name: playerName, payload: answers } }) =>\n presenterPayloadReducer(state, answers, playerId, playerName),\n \"presenter\",\n (builder) => {\n presenterActionReducers(Name, storageKey)(\n builder,\n shouldShowTriviaResponses as ShouldShowResponses\n );\n builder.addCase(toggleShowScoreBoardAction, (state) => ({\n ...state,\n showScoreBoard: !state.showScoreBoard,\n }));\n }\n);\n","import React from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport IconButton from \"@material-ui/core/IconButton\";\nimport BarChart from \"@material-ui/icons/BarChart\";\nimport LiveHelp from \"@material-ui/icons/LiveHelp\";\nimport { presenterActions } from \"games/shared/Poll/reducers/presenterActions\";\n\ntype Props = {\n showResponses: boolean;\n showScoreBoard: boolean;\n gameName: string;\n};\n\nexport const ShowResponsesButton = ({\n showResponses,\n showScoreBoard,\n gameName,\n}: Props) => {\n const dispatch = useDispatch();\n const { toggleShowResponsesAction } = presenterActions(gameName);\n return (\n dispatch(toggleShowResponsesAction())}\n >\n {showResponses ? : }\n \n );\n};\n","import { makeStyles } from \"@material-ui/core/styles\";\n\nexport const useButtonStyles = makeStyles((theme) => ({\n root: {\n position: \"fixed\",\n bottom: 0,\n right: 0,\n padding: theme.spacing(2),\n },\n}));\n","import React from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport IconButton from \"@material-ui/core/IconButton\";\nimport NavigateBefore from \"@material-ui/icons/NavigateBefore\";\nimport NavigateNext from \"@material-ui/icons/NavigateNext\";\nimport ScoreIcon from \"@material-ui/icons/Score\";\nimport CloseIcon from \"@material-ui/icons/Close\";\nimport { NameAndMode } from \"games/shared/Poll/types/NameAndMode\";\nimport { toggleShowScoreBoardAction } from \"games/Trivia/reducers/triviaPresenterReducer\";\nimport { ShowResponsesButton } from \"./ShowResponsesButton\";\nimport { useButtonStyles } from \"./useButtonStyles\";\n\ntype Props = {\n gotoNextQuestion: Function;\n gotoPreviousQuestion: Function;\n nextQuestionId: string | null;\n previousQuestionId: string | null;\n showResponses: boolean;\n showScoreBoard: boolean;\n} & NameAndMode;\n\nconst PollButtons = ({\n gotoNextQuestion,\n gotoPreviousQuestion,\n nextQuestionId,\n previousQuestionId,\n showResponses,\n showScoreBoard,\n isTriviaMode,\n gameName,\n}: Props) => {\n const dispatch = useDispatch();\n const classes = useButtonStyles();\n return (\n \n gotoPreviousQuestion()}\n >\n \n \n \n {isTriviaMode && (\n dispatch(toggleShowScoreBoardAction())}\n >\n {showScoreBoard ? : }\n \n )}\n gotoNextQuestion()}>\n \n \n
\n );\n};\n\nexport default PollButtons;\n","import { Response } from \"../../shared/Poll/types/Response\";\nimport { RootState } from \"../../../store/RootState\";\nimport { createSelector } from \"@reduxjs/toolkit\";\nimport { sort, getPlayersWithNoScore } from \"./triviaPresenterReducer\";\n\nexport const scoreBoardSelector = createSelector(\n (state: RootState) => ({\n questions: state.games.trivia.presenter.questions,\n users: state.lobby.players,\n }),\n (state) => {\n const correctResponsesAsIds = state.questions.map((q) => {\n const correctAnswer = q.answers.find((a) => a.correct);\n const correctResponses = q.responses.filter(\n (r) => r.answerId === correctAnswer?.id\n );\n return correctResponses;\n });\n\n /// https://schneidenbach.gitbooks.io/typescript-cookbook/content/functional-programming/flattening-array-of-arrays.html\n const flattened = ([] as Response[]).concat(...correctResponsesAsIds);\n\n const ids = flattened.map((p) => p.playerId);\n const onlyUnique = (value: T, index: number, self: T[]) =>\n self.indexOf(value) === index;\n const uniqueIds = ids.filter(onlyUnique);\n\n const scores = uniqueIds.map((u) => ({\n name: flattened!.find((r) => u === r.playerId)!.playerName,\n id: u,\n score: flattened.filter((id) => id.playerId === u).length,\n }));\n\n const sorted = sort(scores);\n\n sorted.push(...getPlayersWithNoScore(state.users, scores));\n\n return {\n scores: sorted,\n };\n }\n);\n","import React from \"react\";\nimport Table from \"@material-ui/core/Table\";\nimport TableBody from \"@material-ui/core/TableBody\";\nimport { useSelector } from \"../../../../store/useSelector\";\nimport { scoreBoardSelector } from \"../../../Trivia/reducers/scoreBoardSelector\";\nimport TableRow from \"@material-ui/core/TableRow\";\nimport TableCell from \"@material-ui/core/TableCell\";\nimport { makeStyles } from \"@material-ui/core/styles\";\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n flexDirection: \"column\",\n },\n scores: {\n maxWidth: \"50%\",\n },\n}));\n\nconst ScoreBoard = () => {\n const { scores } = useSelector(scoreBoardSelector);\n const classes = useStyles();\n return (\n \n
Scores
\n
\n \n {scores.map((user, ix) => (\n \n \n {user.name}\n \n \n {user.score}\n \n \n ))}\n \n
\n
\n );\n};\n\nexport default ScoreBoard;\n","import React from \"react\";\nimport Typography from \"@material-ui/core/Typography\";\nimport { makeStyles } from \"@material-ui/core/styles\";\n\nconst useStyles = makeStyles((theme) => ({\n question: {\n margin: 0,\n padding: 0,\n textAlign: \"center\",\n },\n responseCount: {\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n flexDirection: \"column\",\n marginTop: 25,\n },\n}));\n\ntype Props = {\n responseCount: number;\n playerCount: number;\n};\n\nexport const getCountMessage = (responseCount: number, playerCount: number) => {\n if (playerCount === 0) return \"Waiting for participants to join...\";\n return responseCount !== playerCount\n ? `${responseCount} of ${playerCount} participants have responded`\n : `All ${playerCount} participants have responded`;\n};\n\nexport const ResponseCount = ({ responseCount, playerCount }: Props) => {\n const classes = useStyles();\n const countMessage = getCountMessage(responseCount, playerCount);\n\n return (\n \n Responses\n {countMessage}\n
\n );\n};\n","import React from \"react\";\nimport { Question } from \"../types/Question\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport Typography from \"@material-ui/core/Typography\";\nimport { ResponseCount } from \"./ResponseCount\";\n\nconst useStyles = makeStyles((theme) => ({\n question: {\n margin: 0,\n padding: 0,\n textAlign: \"center\",\n },\n}));\n\ntype Props = {\n question: Question;\n responseCount: number;\n playerCount: number;\n totalQuestions: number;\n currentQuestionNumber: number;\n};\n\nconst QuestionAndResponseCount = (props: Props) => {\n const {\n question,\n responseCount,\n playerCount,\n totalQuestions,\n currentQuestionNumber,\n } = props;\n const classes = useStyles();\n\n return (\n <>\n \n Question {currentQuestionNumber} of {totalQuestions}\n \n \n {question.text}\n
\n \n >\n );\n};\n\nexport default QuestionAndResponseCount;\n","import { makeStyles } from \"@material-ui/core\";\nimport Button from \"layout/components/CustomButtons/Button\";\nimport React from \"react\";\nimport { useHistory } from \"react-router-dom\";\n\nconst useStyles = makeStyles((theme) => ({\n header: {\n margin: 0,\n padding: 0,\n textAlign: \"center\",\n },\n}));\n\nexport const NoQuestions = () => {\n const history = useHistory();\n const classes = useStyles();\n return (\n <>\n No questions
\n \n >\n );\n};\n","import { useEffect } from \"react\";\nimport { useSelector } from \"store/useSelector\";\nimport { presenterMessage } from \"store/lobby/actions\";\nimport { useDispatch } from \"react-redux\";\nimport {\n currentQuestionSelector,\n GameState,\n} from \"../shared/Poll/reducers/currentQuestionSelector\";\nimport { RootState } from \"store/RootState\";\nimport { presenterActions } from \"games/shared/Poll/reducers/presenterActions\";\n\nexport const useQuestionState = (\n gameName: string,\n gameStateSelector: (state: RootState) => GameState\n) => {\n const dispatch = useDispatch();\n const {\n currentQuestionId,\n question,\n questionIds,\n responseCount,\n nextQuestionId,\n previousQuestionId,\n currentQuestionNumber,\n totalQuestions,\n showResponses,\n } = useSelector(currentQuestionSelector(gameStateSelector));\n\n const { setCurrentQuestionAction } = presenterActions(gameName);\n\n const { playerCount } = useSelector((state) => ({\n playerCount: state.lobby.players.length,\n }));\n\n // TODO: move this garbage to the reducer\n useEffect(() => {\n if (questionIds.length && !questionIds.find((f) => currentQuestionId)) {\n dispatch(setCurrentQuestionAction(questionIds[0]));\n }\n }, [questionIds, currentQuestionId]);\n\n const gotoNextQuestion = () =>\n nextQuestionId && dispatch(setCurrentQuestionAction(nextQuestionId));\n const gotoPreviousQuestion = () =>\n previousQuestionId &&\n dispatch(setCurrentQuestionAction(previousQuestionId));\n\n useEffect(() => {\n if (question) {\n dispatch(\n presenterMessage({\n questionId: question.id,\n answers: question.answers,\n question: question.text,\n })\n );\n } else {\n dispatch(presenterMessage(null));\n }\n }, [currentQuestionId]);\n\n return {\n currentQuestionId,\n question,\n questionIds,\n responseCount,\n nextQuestionId,\n previousQuestionId,\n currentQuestionNumber,\n totalQuestions,\n showResponses,\n playerCount,\n gotoNextQuestion,\n gotoPreviousQuestion,\n };\n};\n","import React from \"react\";\nimport ResponseChart from \"./components/ResponseChart\";\nimport PollButtons from \"./components/PollButtons\";\nimport ScoreBoard from \"./components/ScoreBoard\";\nimport QuestionAndResponseCount from \"./components/QuestionAndResponseCount\";\nimport { NoQuestions } from \"./components/NoQuestions\";\nimport { useQuestionState } from \"../../Poll/useQuestionState\";\nimport { makeStyles } from \"@material-ui/core\";\nimport { RootState } from \"store/RootState\";\nimport { GameState } from \"games/shared/Poll/reducers/currentQuestionSelector\";\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n height: \"100%\",\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n flexDirection: \"column\",\n },\n question: {\n margin: 0,\n padding: 0,\n textAlign: \"center\",\n },\n}));\n\ntype Props = {\n isTriviaMode: boolean;\n showScoreBoard: boolean;\n gameName: string;\n gameStateSelector: (state: RootState) => GameState;\n};\n\nexport const Presenter = ({\n isTriviaMode,\n showScoreBoard,\n gameName,\n gameStateSelector,\n}: Props) => {\n const classes = useStyles();\n const {\n question,\n responseCount,\n nextQuestionId,\n previousQuestionId,\n currentQuestionNumber,\n totalQuestions,\n showResponses,\n playerCount,\n gotoNextQuestion,\n gotoPreviousQuestion,\n } = useQuestionState(gameName, gameStateSelector);\n\n const QuestionDisplay = () =>\n showResponses ? (\n \n ) : (\n \n );\n\n const QuestionOrScoreBoard = () => {\n if (isTriviaMode && showScoreBoard) {\n return ;\n }\n return (\n \n {question ? : }\n
\n );\n };\n\n return (\n <>\n \n \n >\n );\n};\n","import {\n createGameActionWithPayload,\n createGameAction,\n} from \"store/actionHelpers\";\n\nexport const playerActions = (gameName: string) => {\n const selectAnswerAction = createGameActionWithPayload(\n gameName,\n \"client\",\n \"select-answer\"\n );\n const lockAnswerAction = createGameAction(gameName, \"client\", \"lock-answer\");\n\n return {\n selectAnswerAction,\n lockAnswerAction,\n };\n};\n","import { GamesState, RootState } from \"store/RootState\";\nimport { PollState, TriviaState } from \"./types/State\";\n\nexport const getPollOrTriviaState = (state: RootState, gameName: string) => {\n if (gameName === \"fist-of-five\") {\n gameName = \"fistOfFive\"; // naughty!\n }\n return state.games[gameName as keyof GamesState] as TriviaState | PollState;\n};\n","import React from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport { clientMessage } from \"../../../store/lobby/actions\";\nimport { ContentContainer } from \"../../../components/ContentContainer\";\nimport List from \"@material-ui/core/List\";\nimport ListItem from \"@material-ui/core/ListItem\";\nimport Typography from \"@material-ui/core/Typography\";\nimport Button from \"../../../layout/components/CustomButtons/Button\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport { infoColor } from \"../../../layout/assets/jss/material-dashboard-react\";\nimport { playerActions } from \"./reducers/playerActions\";\nimport { useSelector } from \"store/useSelector\";\nimport { Name as PollName } from \"games/Poll\";\nimport clsx from \"clsx\";\nimport { getPollOrTriviaState } from \"./getPollOrTriviaState\";\nimport { PollPlayerState, TriviaPlayerState } from \"./types/PlayerState\";\n\nconst useStyles = makeStyles(() => ({\n item: {\n background: \"white\",\n padding: 10,\n marginBottom: 10,\n borderRadius: 5,\n \"&.MuiListItem-button:hover\": {\n backgroundColor: \"white\",\n },\n \"&.MuiListItem-root.Mui-selected\": {\n backgroundColor: `${infoColor[3]} !important`,\n },\n },\n text: {\n fontSize: \"30px\",\n },\n button: {\n width: \"100%\",\n },\n}));\n\nconst Client = () => {\n const dispatch = useDispatch();\n const {\n questionId,\n selectedAnswerId,\n answerLocked,\n canAnswer,\n question,\n currentGame,\n answers,\n } = useSelector((state) => {\n const currentGame = state.lobby.currentGame;\n const both = getPollOrTriviaState(state, currentGame!);\n const playerState = both.player;\n if (currentGame === PollName) {\n return {\n ...(playerState as PollPlayerState),\n currentGame,\n canAnswer: true,\n };\n } else return { ...(playerState as TriviaPlayerState), currentGame };\n });\n\n const { lockAnswerAction, selectAnswerAction } = playerActions(currentGame!);\n\n const classes = useStyles();\n const lockAnswer = () => {\n dispatch(\n clientMessage({\n questionId,\n answerId: selectedAnswerId,\n })\n );\n dispatch(lockAnswerAction());\n };\n return (\n \n {canAnswer || answerLocked ? (\n <>\n {question}\n \n {answers.map((answer) => (\n dispatch(selectAnswerAction(answer.id))}\n selected={selectedAnswerId === answer.id}\n >\n \n {answer.text}\n \n \n ))}\n \n
\n >\n ) : (\n Please wait...
\n )}\n \n );\n};\n\nexport default Client;\n","const move = (array, index, delta) => {\n //ref: https://gist.github.com/albertein/4496103\n var newIndex = index + delta;\n if (newIndex < 0 || newIndex === array.length) return; //Already at the top or bottom.\n var indexes = [index, newIndex].sort((a, b) => a - b); //Sort the indixes (fixed)\n array.splice(indexes[0], 2, array[indexes[1]], array[indexes[0]]); //Replace from lowest index, two elements, reverting the order\n};\n\nconst moveUp = (array, element) => {\n const newArray = [...array];\n move(newArray, element, -1);\n return newArray;\n};\n\nconst moveDown = (array, element) => {\n const newArray = [...array];\n move(newArray, element, 1);\n return newArray;\n};\n\n// eslint-disable-next-line\nexport default {\n moveUp,\n moveDown,\n};\n","import React, { useState } from \"react\";\nimport Typography from \"@material-ui/core/Typography\";\nimport Alert from \"@material-ui/lab/Alert\";\nimport { useDispatch } from \"react-redux\";\nimport { Question } from \"../types/Question\";\nimport { presenterActions } from \"games/shared/Poll/reducers/presenterActions\";\nimport { ConfirmDialog } from \"components/ConfirmDialog\";\nimport { useSelector } from \"store/useSelector\";\nimport { RootState } from \"store/RootState\";\nimport { guid } from \"util/guid\";\nimport { shuffle } from \"Random\";\nimport CustomInput from \"layout/components/CustomInput/CustomInput\";\nimport {\n FormControl,\n InputLabel,\n makeStyles,\n MenuItem,\n Select,\n} from \"@material-ui/core\";\n\nconst useStyles = makeStyles((theme) => ({\n header: {\n display: \"flex\",\n justifyContent: \"space-between\",\n marginBottom: theme.spacing(1),\n },\n difficulty: {\n marginTop: 27,\n marginLeft: theme.spacing(2),\n },\n opentdb: {\n backgroundColor: \"black\",\n padding: theme.spacing(0.5),\n height: 32,\n },\n}));\n\nconst htmlDecode = (input: string): string => {\n var doc = new DOMParser().parseFromString(input, \"text/html\");\n const result = doc.documentElement.textContent || \"\";\n return result;\n};\n\ntype QuestionType = \"multiple\";\n\ntype Response = {\n response_code: number;\n results: Result[];\n};\n\ntype Result = {\n category: string;\n correct_answer: string;\n difficulty: string;\n incorrect_answers: string[];\n question: string;\n type: QuestionType;\n};\n\ntype Props = {\n open: boolean;\n setOpen: (open: boolean) => void;\n};\nexport const AutoQuestions = (props: Props) => {\n const currentGame = useSelector(\n (state: RootState) => state.lobby.currentGame\n );\n const { open, setOpen } = props;\n const { importQuestionsAction } = presenterActions(currentGame!);\n const dispatch = useDispatch();\n\n const handleOk = async () => {\n if (!countIsValid()) {\n setError(\"Number of Questions is Invalid\");\n } else {\n setError(\"\");\n let url = `https://opentdb.com/api.php?amount=${count}`;\n if (difficulty !== \"any\") url += `&difficulty=${difficulty}`;\n try {\n const response = await fetch(url);\n const json = (await response.json()) as Response;\n const questions: Question[] = json.results.map((q) => ({\n id: guid(),\n responses: [],\n text: htmlDecode(q.question),\n answers: shuffle([\n {\n correct: true,\n id: guid(),\n text: htmlDecode(q.correct_answer),\n },\n ...q.incorrect_answers.map((a) => ({\n correct: false,\n id: guid(),\n text: htmlDecode(a),\n })),\n ]),\n }));\n dispatch(importQuestionsAction(questions));\n setOpen(false);\n } catch (error) {\n setError(\"Could not get questions\");\n }\n }\n };\n\n const handleCountChange: React.ChangeEventHandler<\n HTMLTextAreaElement | HTMLInputElement\n > = (e) => {\n setCount(e.target.value);\n };\n\n const handleDifficultyChange = (\n event: React.ChangeEvent<{ value: unknown }>\n ) => {\n setDifficulty(event.target.value as string);\n };\n\n const [count, setCount] = useState(\"10\");\n const [error, setError] = useState(\"\");\n const [difficulty, setDifficulty] = useState(\"easy\");\n\n const countIsValid = () => {\n const result = Number.isInteger(parseInt(count));\n\n return result;\n };\n\n const classes = useStyles();\n\n return (\n \n Auto questions\n \n
\n \n \n }\n action={handleOk}\n open={open}\n setOpen={setOpen}\n content={\n <>\n \n \n Generating questions will clear all questions and audience\n responses\n \n \n handleCountChange(e)}\n error={!countIsValid()}\n />\n \n Difficulty\n \n \n {!!error && {error}}\n >\n }\n />\n );\n};\n","import React, { useRef, useState } from \"react\";\nimport Card from \"../../../../layout/components/Card/Card\";\nimport CardTitle from \"../../../../layout/components/Card/CardTitle\";\nimport CardFooter from \"../../../../layout/components/Card/CardFooter\";\nimport CardBody from \"../../../../layout/components/Card/CardBody\";\nimport Button from \"../../../../layout/components/CustomButtons/Button\";\nimport ArrowUpward from \"@material-ui/icons/ArrowUpward\";\nimport ArrowDownward from \"@material-ui/icons/ArrowDownward\";\nimport IconButton from \"@material-ui/core/IconButton\";\nimport Delete from \"@material-ui/icons/Delete\";\nimport Edit from \"@material-ui/icons/Edit\";\nimport { useSelector } from \"../../../../store/useSelector\";\nimport { ContentContainer } from \"../../../../components/ContentContainer\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport Table from \"@material-ui/core/Table\";\nimport TableBody from \"@material-ui/core/TableBody\";\nimport TableCell from \"@material-ui/core/TableCell\";\nimport TableContainer from \"@material-ui/core/TableContainer\";\nimport TableHead from \"@material-ui/core/TableHead\";\nimport TableRow from \"@material-ui/core/TableRow\";\nimport Paper from \"@material-ui/core/Paper\";\nimport { useHistory, NavLink } from \"react-router-dom\";\nimport { guid } from \"../../../../util/guid\";\nimport { useDispatch } from \"react-redux\";\nimport array from \"../../../../util/array\";\nimport { saveAs } from \"file-saver\";\nimport { BulkEdit } from \"./BulkEdit\";\nimport { presenterActions } from \"games/shared/Poll/reducers/presenterActions\";\nimport { Name as PollName } from \"games/Poll\";\nimport { getPollOrTriviaState } from \"../getPollOrTriviaState\";\nimport { ConfirmDialog } from \"components/ConfirmDialog\";\nimport { AutoQuestions } from \"./AutoQuestions\";\n\nconst useStyles = makeStyles((theme) => ({\n table: {},\n answers: {\n paddingLeft: 0,\n listStyle: \"none\",\n },\n file: {\n display: \"none\",\n },\n checked: {\n \"&::before\": { content: '\"☑️\"' },\n },\n unchecked: {\n \"&::before\": {\n content: '\"☐\"',\n paddingRight: 8,\n },\n },\n footer: {\n flexWrap: \"wrap\",\n justifyContent: \"normal\",\n },\n}));\n\nconst EditQuestions = () => {\n const classes = useStyles();\n const history = useHistory();\n const dispatch = useDispatch();\n const { questions, gameName, isTriviaMode } = useSelector((state) => {\n const gameName = state.lobby.currentGame!;\n const isTriviaMode = gameName !== PollName;\n const questions = getPollOrTriviaState(state, gameName).presenter.questions;\n\n return {\n questions,\n gameName,\n isTriviaMode,\n };\n });\n const {\n addQuestionAction,\n importQuestionsAction,\n clearResponsesAction,\n deleteQuestionAction,\n } = presenterActions(gameName);\n\n const addQuestion = () => {\n const id = guid();\n dispatch(addQuestionAction(id));\n history.push(`/questions/${id}`);\n };\n const fileUpload = useRef(null);\n\n const exportQuestions = () => {\n const fileName = \"questions.json\";\n const fileToSave = new Blob([JSON.stringify(questions, null, 4)], {\n type: \"application/json\",\n });\n saveAs(fileToSave, fileName);\n };\n\n const importQuestions = () => {\n if (null !== fileUpload.current) {\n const file = fileUpload.current.files![0];\n if (file) {\n var reader = new FileReader();\n reader.readAsText(file, \"UTF-8\");\n reader.onload = (evt: ProgressEvent) => {\n if (evt.target && typeof evt.target.result === \"string\") {\n const questions = JSON.parse(evt.target.result);\n dispatch(importQuestionsAction(questions));\n if (fileUpload.current !== null) {\n fileUpload.current.value = \"\";\n }\n }\n };\n }\n }\n };\n\n // const {\n // component: ConfirmClearQuestions,\n // open: openConfirmClearQuestions,\n // } = useConfirmDialog(\n // ,\n // ,\n // (close) => dispatch(importQuestionsAction([])) && close()\n // );\n\n // const {\n // component: ConfirmClearResponses,\n // open: openConfirmClearResponses,\n // } = useConfirmDialog(\n\n // );\n\n // const {\n // dialog: BulkEditDialog,\n // openDialog: openBulkEditDialog,\n // } = useBulkEdit(gameName, isTriviaMode, questions);\n\n const [confirmClearQuestionsOpen, setConfirmClearQuestionsOpen] = useState(\n false\n );\n const [confirmClearResponsesOpen, setConfirmClearResponsesOpen] = useState(\n false\n );\n const [bulkEditOpen, setBulkEditOpen] = useState(false);\n const [autoQuestionsOpen, setAutoQuestionsOpen] = useState(false);\n return (\n <>\n \n {isTriviaMode && (\n \n )}\n \n \n \n \n \n \n {isTriviaMode && (\n \n )}\n \n \n dispatch(importQuestionsAction([])) &&\n setConfirmClearQuestionsOpen(false)\n }\n open={confirmClearQuestionsOpen}\n setOpen={setConfirmClearQuestionsOpen}\n />\n \n \n dispatch(clearResponsesAction()) &&\n setConfirmClearResponsesOpen(false)\n }\n open={confirmClearResponsesOpen}\n setOpen={setConfirmClearResponsesOpen}\n />\n \n \n importQuestions()}\n />\n \n \n \n \n \n \n Question\n Answers\n Responses\n \n \n \n \n {questions.map((question, ix) => (\n \n \n {question.text}\n \n \n \n {question.answers.map((a, ix) => (\n - \n {a.text}\n
\n ))}\n
\n \n {question.responses.length}\n \n \n \n \n \n \n \n dispatch(\n importQuestionsAction(\n array.moveUp(\n questions,\n questions.indexOf(question)\n )\n )\n )\n }\n >\n \n \n \n dispatch(\n importQuestionsAction(\n array.moveDown(\n questions,\n questions.indexOf(question)\n )\n )\n )\n }\n >\n \n \n \n dispatch(deleteQuestionAction(question))\n }\n >\n \n \n \n \n ))}\n \n
\n \n \n \n \n >\n );\n};\n\nexport default EditQuestions;\n","import {\n defaultFont,\n primaryBoxShadow,\n infoBoxShadow,\n successBoxShadow,\n warningBoxShadow,\n dangerBoxShadow,\n roseBoxShadow,\n whiteColor,\n blackColor,\n grayColor,\n infoColor,\n successColor,\n dangerColor,\n roseColor,\n primaryColor,\n warningColor,\n hexToRgb,\n} from \"../../material-dashboard-react\";\n\nconst snackbarContentStyle: any = {\n root: {\n ...defaultFont,\n flexWrap: \"unset\",\n position: \"relative\",\n padding: \"20px 15px\",\n lineHeight: \"20px\",\n marginBottom: \"20px\",\n fontSize: \"14px\",\n backgroundColor: whiteColor,\n color: grayColor[7],\n borderRadius: \"3px\",\n minWidth: \"unset\",\n maxWidth: \"unset\",\n boxShadow:\n \"0 12px 20px -10px rgba(\" +\n hexToRgb(whiteColor) +\n \", 0.28), 0 4px 20px 0px rgba(\" +\n hexToRgb(blackColor) +\n \", 0.12), 0 7px 8px -5px rgba(\" +\n hexToRgb(whiteColor) +\n \", 0.2)\",\n },\n top20: {\n top: \"20px\",\n },\n top40: {\n top: \"40px\",\n },\n info: {\n backgroundColor: infoColor[3],\n color: whiteColor,\n ...infoBoxShadow,\n },\n success: {\n backgroundColor: successColor[3],\n color: whiteColor,\n ...successBoxShadow,\n },\n warning: {\n backgroundColor: warningColor[3],\n color: whiteColor,\n ...warningBoxShadow,\n },\n danger: {\n backgroundColor: dangerColor[3],\n color: whiteColor,\n ...dangerBoxShadow,\n },\n primary: {\n backgroundColor: primaryColor[3],\n color: whiteColor,\n ...primaryBoxShadow,\n },\n rose: {\n backgroundColor: roseColor[3],\n color: whiteColor,\n ...roseBoxShadow,\n },\n message: {\n padding: \"0\",\n display: \"block\",\n maxWidth: \"89%\",\n },\n close: {\n width: \"11px\",\n height: \"11px\",\n },\n iconButton: {\n width: \"24px\",\n height: \"24px\",\n padding: \"0px\",\n },\n icon: {\n display: \"block\",\n left: \"15px\",\n position: \"absolute\",\n top: \"50%\",\n marginTop: \"-15px\",\n width: \"30px\",\n height: \"30px\",\n },\n infoIcon: {\n color: infoColor[3],\n },\n successIcon: {\n color: successColor[3],\n },\n warningIcon: {\n color: warningColor[3],\n },\n dangerIcon: {\n color: dangerColor[3],\n },\n primaryIcon: {\n color: primaryColor[3],\n },\n roseIcon: {\n color: roseColor[3],\n },\n iconMessage: {\n paddingLeft: \"50px\",\n display: \"block\",\n },\n actionRTL: {\n marginLeft: \"-8px\",\n marginRight: \"auto\",\n },\n};\n\nexport default snackbarContentStyle;\n","import React from \"react\";\nimport PropTypes from \"prop-types\";\nimport classNames from \"classnames\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport Snack from \"@material-ui/core/SnackbarContent\";\nimport IconButton from \"@material-ui/core/IconButton\";\n// @material-ui/icons\nimport Close from \"@material-ui/icons/Close\";\n// core components\nimport styles from \"../../assets/jss/material-dashboard-react/components/snackbarContentStyle\";\n\nconst useStyles = makeStyles(styles);\n\nexport default function SnackbarContent(props) {\n const classes = useStyles();\n const { message, color, close, icon, rtlActive } = props;\n var action = [];\n const messageClasses = classNames({\n [classes.iconMessage]: icon !== undefined,\n });\n if (close !== undefined) {\n action = [\n \n \n ,\n ];\n }\n return (\n \n {icon !== undefined ? : null}\n {message}\n \n }\n classes={{\n root: classes.root + \" \" + classes[color],\n message: classes.message,\n action: classNames({ [classes.actionRTL]: rtlActive }),\n }}\n action={action}\n />\n );\n}\n\nSnackbarContent.propTypes = {\n message: PropTypes.node.isRequired,\n color: PropTypes.oneOf([\"info\", \"success\", \"warning\", \"danger\", \"primary\"]),\n close: PropTypes.bool,\n icon: PropTypes.object,\n rtlActive: PropTypes.bool,\n};\n","import React, { ReactNode } from \"react\";\nimport Table from \"@material-ui/core/Table\";\nimport TableBody from \"@material-ui/core/TableBody\";\nimport TableCell from \"@material-ui/core/TableCell\";\nimport TableRow from \"@material-ui/core/TableRow\";\nimport ArrowUpward from \"@material-ui/icons/ArrowUpward\";\nimport ArrowDownward from \"@material-ui/icons/ArrowDownward\";\nimport IconButton from \"@material-ui/core/IconButton\";\nimport Delete from \"@material-ui/icons/Delete\";\nimport array from \"util/array\";\nimport CustomInput from \"layout/components/CustomInput/CustomInput\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport { Answer } from \"../types/Answer\";\n\nconst useStyles = makeStyles((theme) => ({\n cell: {\n \"&.MuiTableCell-root\": {\n borderBottom: \"none\",\n },\n padding: \"0 0 0 16px !important\",\n },\n input: {\n \"&.MuiInputBase-root\": {\n margin: \"0 !important\",\n },\n },\n formControl: {\n margin: 0,\n padding: 0,\n },\n correctLabel: {\n verticalAlign: \"middle\",\n display: \"inline\",\n },\n}));\n\ntype Props = {\n answers: Answer[];\n setAnswers: (answers: Answer[]) => void;\n questionId: string;\n children: ReactNode;\n};\n\nconst EditAnswers = ({ answers, setAnswers, children }: Props) => {\n const classes = useStyles();\n\n return (\n \n \n {answers.map((answer, ix) => (\n \n \n \n setAnswers(\n answers.map((a) =>\n a.id !== answer.id\n ? a\n : { ...answer, text: e.target.value }\n )\n )\n }\n error={answer.text.length < 1}\n />\n \n {children}\n \n \n setAnswers(array.moveUp(answers, answers.indexOf(answer)))\n }\n disabled={ix === 0}\n >\n \n \n \n setAnswers(array.moveDown(answers, answers.indexOf(answer)))\n }\n disabled={ix === answers.length - 1}\n >\n \n \n \n setAnswers(answers.filter((a) => a.id !== answer.id))\n }\n disabled={answers.length < 2}\n >\n \n \n \n \n ))}\n \n
\n );\n};\n\nexport default EditAnswers;\n","import React, { ReactNode, useState } from \"react\";\nimport Card from \"../../../../layout/components/Card/Card\";\nimport CardTitle from \"../../../../layout/components/Card/CardTitle\";\nimport CardFooter from \"../../../../layout/components/Card/CardFooter\";\nimport CardBody from \"../../../../layout/components/Card/CardBody\";\nimport Button from \"../../../../layout/components/CustomButtons/Button\";\nimport Grid from \"@material-ui/core/Grid\";\nimport { useSelector } from \"../../../../store/useSelector\";\nimport { ContentContainer } from \"../../../../components/ContentContainer\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport { useParams, useHistory } from \"react-router\";\nimport CustomInput from \"../../../../layout/components/CustomInput/CustomInput\";\nimport SnackbarContent from \"../../../../layout/components/Snackbar/SnackbarContent\";\nimport { Question } from \"../types/Question\";\nimport Typography from \"@material-ui/core/Typography\";\nimport EditAnswers from \"./EditAnswers\";\nimport { guid } from \"../../../../util/guid\";\nimport { grayColor } from \"../../../../layout/assets/jss/material-dashboard-react\";\nimport { useDispatch } from \"react-redux\";\nimport { presenterActions } from \"games/shared/Poll/reducers/presenterActions\";\nimport { NameAndMode } from \"../types/NameAndMode\";\nimport { Answer } from \"../types/Answer\";\nimport { getPollOrTriviaState } from \"../getPollOrTriviaState\";\nimport { RootState } from \"store/RootState\";\nimport { Name as PollName } from \"games/Poll\";\n\nconst useStyles = makeStyles((theme) => ({\n header: {\n marginBottom: theme.spacing(3),\n marginTop: theme.spacing(2),\n },\n formControl: {\n margin: 0,\n padding: 0,\n },\n noResponses: {\n color: grayColor[0],\n fontStyle: \"italic\",\n },\n}));\ntype SetAnswers = (answers: Answer[]) => void;\n\ntype Props = {\n question: Question;\n editAnswersChildren?: (setAnswers: SetAnswers) => ReactNode;\n} & NameAndMode;\n\nconst QuestionEditor = ({ question, gameName, editAnswersChildren }: Props) => {\n const dispatch = useDispatch();\n const totalQuestions = useSelector((store) => {\n const state = getPollOrTriviaState(store, gameName);\n return state.presenter.questions.length;\n });\n const [text, setText] = useState(question.text);\n const [answers, setAnswers] = useState(question.answers);\n\n const classes = useStyles();\n const history = useHistory();\n\n const isValid = () => {\n return text.length < 3;\n };\n\n const { updateQuestionAction, deleteQuestionAction } = presenterActions(\n gameName\n );\n\n const saveQuestion = () => {\n dispatch(\n updateQuestionAction({\n id: question.id,\n answers,\n responses: question.responses,\n text: text,\n })\n );\n history.push(\"/questions\");\n };\n\n const deleteQuestion = () => {\n dispatch(deleteQuestionAction(question));\n history.push(\"/questions\");\n };\n\n return (\n \n \n \n \n \n \n \n Question\n \n \n \n setText(e.target.value)}\n error={isValid()}\n />\n \n \n \n Answers\n \n setAnswers(answers)}\n questionId={question.id}\n children={editAnswersChildren}\n />\n \n \n \n \n Responses\n \n {question.responses.length === 0 && (\n \n No responses\n \n )}\n \n \n \n \n \n \n \n \n \n );\n};\n\nconst EditQuestion = () => {\n const gameName = useSelector((state: RootState) => state.lobby.currentGame!);\n const isTriviaMode = gameName !== PollName;\n const questionId = useParams<{ id: string }>().id;\n\n const question = useSelector((store) => {\n const state = getPollOrTriviaState(store, gameName);\n return state.presenter.questions.find((q) => q.id === questionId);\n });\n\n return question ? (\n \n ) : (\n \n \n \n );\n};\n\nexport default EditQuestion;\n","import { GameMessage } from \"games/GameMessage\";\nimport {\n createGameAction,\n createGameActionWithPayload,\n createReceiveGameMessageReducer,\n} from \"store/actionHelpers\";\nimport { PayloadFromParticipant } from \"./Participant\";\nimport StorageManager from \"store/StorageManager\";\nconst storage = new StorageManager(window.localStorage);\nconst storageKey = \"retrospective\";\n\nexport const Name = \"retrospective\";\n\nexport type Category = {\n id: number;\n name: string;\n};\n\nexport interface RetrospectivePresenterState {\n ideas: GameMessage[];\n categories: Category[];\n}\n\nexport const clearIdeasAction = createGameAction(\n Name,\n \"presenter\",\n \"clear-ideas\"\n);\n\nexport const setCategories = createGameActionWithPayload(\n Name,\n \"presenter\",\n \"set-categories\"\n);\n\nexport const loadFromStore = createGameAction(Name, \"presenter\", \"load\");\n\nexport const presenterReducer = createReceiveGameMessageReducer<\n PayloadFromParticipant,\n RetrospectivePresenterState\n>(\n Name,\n { ideas: [], categories: [] },\n (state, action) => {\n const result = {\n ...state,\n ideas: [...state.ideas, action.payload],\n };\n storage.saveToStorage(storageKey, result);\n return result;\n },\n \"presenter\",\n (builder) => {\n builder.addCase(clearIdeasAction, (state) => {\n const result = { ...state, ideas: [] };\n storage.saveToStorage(storageKey, result);\n return result;\n });\n builder.addCase(setCategories, (state, action) => {\n const result = {\n ideas: [],\n categories: [...action.payload],\n };\n storage.saveToStorage(storageKey, result);\n return result;\n });\n builder.addCase(\n loadFromStore,\n (state) =>\n storage.getFromStorage(storageKey) || {\n ...state,\n }\n );\n }\n);\n","import { GameMessage } from \"games/GameMessage\";\nimport { PayloadFromParticipant } from \"./Participant\";\n\nexport const ideasByCategory = (\n ideas: GameMessage[],\n category: number\n) => ideas.filter((idea) => idea.payload.category === category);\n","import React, { useState } from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport { clientMessage } from \"../../store/lobby/actions\";\nimport { ContentContainer } from \"../../components/ContentContainer\";\nimport Grid from \"@material-ui/core/Grid\";\nimport Card from \"../../layout/components/Card/Card\";\nimport CardFooter from \"../../layout/components/Card/CardFooter\";\nimport Button from \"../../layout/components/CustomButtons/Button\";\nimport { IdeaEntry } from \"games/shared/IdeaEntry\";\nimport { useSelector } from \"store/useSelector\";\nimport { makeStyles } from \"@material-ui/core/styles\";\n\nconst useStyles = makeStyles((theme) => ({\n footer: {\n display: \"flex\",\n flexWrap: \"wrap\",\n },\n}));\n\nexport type PayloadFromParticipant = {\n category: number;\n message: string;\n};\n\nexport const Participant = () => {\n const dispatch = useDispatch();\n const classes = useStyles();\n const [idea, setIdea] = useState(\"\");\n const categories = useSelector(\n (state) => state.games.retrospective.participant.categories\n );\n\n const onClick = (category: number) => {\n if (idea.length) {\n dispatch(\n clientMessage({ category, message: idea } as PayloadFromParticipant)\n );\n setIdea(\"\");\n }\n };\n\n return (\n \n \n \n \n \n \n {categories.map((category, ix) => (\n \n ))}\n \n \n \n \n \n );\n};\n","import React from \"react\";\n// nodejs library to set properties for components\nimport PropTypes from \"prop-types\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport Grid from \"@material-ui/core/Grid\";\n\nconst styles = {\n grid: {\n margin: \"0 -15px !important\",\n width: \"unset\",\n },\n};\n\nconst useStyles = makeStyles(styles);\n\nexport default function GridContainer(props) {\n const classes = useStyles();\n const { children, ...rest } = props;\n return (\n \n {children}\n \n );\n}\n\nGridContainer.propTypes = {\n children: PropTypes.node,\n};\n","import React from \"react\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport Grid from \"@material-ui/core/Grid\";\n\nconst styles = {\n grid: {\n padding: \"0 15px !important\",\n },\n};\n\nconst useStyles = makeStyles(styles);\n\nexport default function GridItem(props: any) {\n const classes = useStyles();\n const { children, ...rest } = props;\n return (\n \n {children}\n \n );\n}\n","import { Card, CardActions, makeStyles } from \"@material-ui/core\";\nimport CardContent from \"@material-ui/core/CardContent\";\nimport Typography from \"@material-ui/core/Typography\";\nimport Grid from \"@material-ui/core/Grid\";\nimport Button from \"layout/components/CustomButtons/Button\";\nimport React, { useState } from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport { Category, setCategories } from \"./presenterReducer\";\nimport CustomInput from \"layout/components/CustomInput/CustomInput\";\nimport Alert from \"@material-ui/lab/Alert\";\n\nconst useStyles = makeStyles((theme) => ({\n form: {\n marginTop: 0,\n },\n input: {\n fontFamily: \"monospace\",\n lineHeight: 1,\n },\n}));\n\nconst categories: Category[][] = [\n [\n { id: 0, name: \"Start\" },\n { id: 1, name: \"Stop\" },\n { id: 2, name: \"Continue\" },\n ],\n [\n { id: 0, name: \"What went well\" },\n { id: 1, name: \"What didn't go well\" },\n ],\n];\n\nconst SetCategories = () => {\n const dispatch = useDispatch();\n const [error, setError] = useState(\"\");\n const classes = useStyles();\n const [categoryLines, setCategoryLines] = useState(\n \"Category 1\\nCategory 2\\nOne per line\"\n );\n const onChange = (e: React.ChangeEvent) => {\n const target = e.target as HTMLTextAreaElement;\n setCategoryLines(target.value);\n };\n const setCustomCategories = () => {\n const trimmed = categoryLines\n .split(\"\\n\")\n .map((line) => line.trim())\n .filter((line) => line);\n if (!trimmed.length) {\n setError(\"Specify at least one category\");\n } else {\n setError(\"\");\n dispatch(setCategories(trimmed.map((t, ix) => ({ id: ix, name: t }))));\n }\n };\n return (\n <>\n Set categories\n \n {categories.map((category) => (\n \n \n \n \n {category.map((section) => (\n - {section.name}
\n ))}\n
\n \n \n \n \n \n \n ))}\n \n \n \n 0}\n className={classes.input}\n formControlProps={{\n className: classes.form,\n fullWidth: true,\n }}\n value={categoryLines}\n onChange={onChange}\n />\n {error.length > 0 && {error}}\n \n \n \n \n \n \n \n >\n );\n};\n\nexport default SetCategories;\n","import React, { useEffect } from \"react\";\nimport { ContentContainer } from \"components/ContentContainer\";\nimport GridContainer from \"layout/components/Grid/GridContainer\";\nimport GridItem from \"layout/components/Grid/GridItem\";\nimport CardBody from \"layout/components/Card/CardBody\";\nimport Card from \"layout/components/Card/Card\";\nimport { makeStyles, Typography } from \"@material-ui/core\";\nimport { useSelector } from \"store/useSelector\";\nimport { RootState } from \"store/RootState\";\nimport { ideasByCategory } from \"./ideasByCategory\";\nimport SetCategories from \"./SetCategories\";\nimport { useDispatch } from \"react-redux\";\nimport { presenterMessage } from \"store/lobby/actions\";\nimport { loadFromStore } from \"./presenterReducer\";\n\nconst useStyles = makeStyles((theme) => ({\n header: {},\n waiting: {\n marginLeft: theme.spacing(3),\n padding: theme.spacing(2),\n },\n card: {\n marginTop: theme.spacing(1),\n marginBottom: theme.spacing(1),\n },\n}));\n\nexport const Presenter = () => {\n const classes = useStyles();\n const dispatch = useDispatch();\n const { ideas, categories } = useSelector(\n (state: RootState) => state.games.retrospective.presenter\n );\n\n useEffect(() => {\n if (categories.length) dispatch(presenterMessage(categories));\n }, [categories]);\n\n useEffect(() => {\n dispatch(loadFromStore());\n }, []);\n\n return (\n \n {categories.length ? (\n categories.map((category, ix) => (\n <>\n \n {category.name}\n \n \n {!ideasByCategory(ideas, ix).length && (\n \n Waiting for audience...\n \n )}\n {ideasByCategory(ideas, ix).map((idea) => (\n \n \n \n \n {idea.payload.message}\n \n \n \n \n ))}\n \n >\n ))\n ) : (\n \n )}\n \n );\n};\n","import { SelectableAnswer } from \"../types/PlayerState\";\nimport { ActionReducerMapBuilder } from \"@reduxjs/toolkit\";\nimport { playerActions } from \"./playerActions\";\n\nexport const playerActionReducer = (gameName: string) => (\n builder: ActionReducerMapBuilder\n): void => {\n const { selectAnswerAction, lockAnswerAction } = playerActions(gameName);\n\n builder.addCase(\n selectAnswerAction,\n (state, { payload: selectedAnswerId }) => ({\n ...state,\n selectedAnswerId,\n })\n );\n builder.addCase(lockAnswerAction, (state) => ({\n ...state,\n answerLocked: true,\n }));\n};\n","export const initialPlayerState = {\n answers: [],\n questionId: \"\",\n answerLocked: false,\n question: \"\",\n};\n","import { createReceiveReducer } from \"store/actionHelpers\";\nimport { TriviaPlayerState } from \"games/shared/Poll/types/PlayerState\";\nimport { playerActionReducer } from \"games/shared/Poll/reducers/playerActionReducer\";\nimport { initialPlayerState } from \"games/shared/Poll/reducers/initialPlayerState\";\nimport { TriviaPayload } from \"../../../Trivia/reducers/triviaPlayerReducer\";\n\nexport const sharedTriviaPlayerReducer = (gameName: string) =>\n createReceiveReducer(\n gameName,\n { ...initialPlayerState, canAnswer: false },\n (state, { payload: availableAnswers }) => {\n if (availableAnswers.canAnswer !== undefined) {\n return { ...state, canAnswer: availableAnswers.canAnswer };\n }\n return {\n ...state,\n ...availableAnswers,\n answerLocked: !!availableAnswers.selectedAnswerId,\n };\n },\n \"client\",\n playerActionReducer(gameName)\n );\n","import { combineReducers } from \"redux\";\nimport { sharedTriviaPlayerReducer } from \"games/shared/Poll/reducers/sharedTriviaPlayerReducer\";\nimport { FistOfFiveState } from \"games/shared/Poll/types/State\";\nimport { SelectedAnswer } from \"games/shared/Poll/types/SelectedAnswer\";\nimport { createReceiveGameMessageReducer } from \"store/actionHelpers\";\nimport { FistOfFivePresenterState } from \"games/shared/Poll/types/PresenterState\";\nimport { presenterActions } from \"games/shared/Poll/reducers/presenterActions\";\nimport { presenterPayloadReducer } from \"games/shared/Poll/reducers/presenterPayloadReducer\";\n\nexport const Name = \"fist-of-five\";\n\nconst { importQuestionsAction, toggleShowResponsesAction } = presenterActions(\n Name\n);\n\nexport const pollPresenterReducer = createReceiveGameMessageReducer<\n SelectedAnswer[],\n FistOfFivePresenterState\n>(\n Name,\n {\n questions: [],\n showResponses: false,\n currentQuestionId: \"1\",\n },\n (state, { payload: { id: playerId, name: playerName, payload: answers } }) =>\n presenterPayloadReducer(state, answers, playerId, playerName),\n \"presenter\",\n (builder) => {\n builder.addCase(importQuestionsAction, (state, { payload: questions }) => {\n let currentQuestionId: string | undefined;\n if (questions.length) {\n currentQuestionId = questions[0].id;\n }\n return {\n ...state,\n showResponses: false,\n questions,\n currentQuestionId,\n };\n });\n\n builder.addCase(toggleShowResponsesAction, (state) => ({\n ...state,\n showResponses: !state.showResponses,\n }));\n }\n);\n\nexport const fistOfFiveReducer = combineReducers({\n player: sharedTriviaPlayerReducer(Name),\n presenter: pollPresenterReducer,\n});\n","import React from \"react\";\nimport { Name } from \"./reducer\";\nimport { useButtonStyles } from \"games/shared/Poll/components/useButtonStyles\";\nimport { ShowResponsesButton } from \"games/shared/Poll/components/ShowResponsesButton\";\nimport ReplayIcon from \"@material-ui/icons/Replay\";\nimport PrintIcon from \"@material-ui/icons/Print\";\nimport IconButton from \"@material-ui/core/IconButton\";\nimport { Response } from \"games/shared/Poll/types/Response\";\n\ntype Props = {\n showResponses: boolean;\n reset: () => void;\n responses?: Response[];\n};\n\nexport const Buttons = ({ showResponses, reset, responses }: Props) => {\n const classes = useButtonStyles();\n\n const print = () => {\n const data = responses\n ?.map((res) => `\"${res.playerName}\",${res.answerId}\\n`)\n .join(\"\");\n if (data) {\n const fileName = \"fist-of-five.csv\";\n const fileToSave = new Blob([data], {\n type: \"plain/text\",\n });\n saveAs(fileToSave, fileName);\n }\n };\n\n return (\n \n );\n};\n","import React from \"react\";\nimport Typography from \"@material-ui/core/Typography\";\nimport { Question } from \"games/shared/Poll/types/Question\";\nimport { makeStyles } from \"@material-ui/core/styles\";\n\nconst useStyles = makeStyles((theme) => ({\n list: {\n display: \"flex\",\n flexDirection: \"row\",\n width: \"100%\",\n //margin: 0,\n padding: 0,\n },\n item: {\n flex: 1,\n textAlign: \"center\",\n listStyle: \"none\",\n },\n header: {},\n subheader: {},\n players: {\n fontSize: \"1.25rem\",\n },\n}));\n\ntype Props = {\n question: Question;\n};\nexport const Responses = ({ question: { responses } }: Props) => {\n const classes = useStyles();\n return (\n <>\n {responses.length && (\n \n {Math.round(\n (responses\n .map((res) => parseInt(res.answerId))\n .reduce((sum, current) => sum + current, 0) /\n responses.length) *\n 100\n ) / 100}\n \n )}\n \n {Array.from({ length: 5 }, (value, key) => key).map((ix) => (\n - \n {responses.length && (\n \n {Math.round(\n (responses.filter((p) => p.answerId === `${ix + 1}`).length /\n responses.length) *\n 100\n )}\n %\n \n )}\n \n {ix + 1}\n \n {responses\n .filter((p) => p.answerId === `${ix + 1}`)\n .map((res) => (\n \n {res.playerName}\n \n ))}\n
\n ))}\n
\n >\n );\n};\n","import React, { useEffect } from \"react\";\nimport Typography from \"@material-ui/core/Typography\";\nimport { guid } from \"util/guid\";\nimport { presenterMessage } from \"store/lobby/actions\";\nimport { useDispatch } from \"react-redux\";\nimport { ResponseCount } from \"games/shared/Poll/components/ResponseCount\";\nimport { useQuestionState } from \"games/Poll/useQuestionState\";\nimport { Name } from \"./reducer\";\nimport { presenterActions } from \"games/shared/Poll/reducers/presenterActions\";\nimport { Buttons } from \"./Buttons\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport { Responses } from \"./Responses\";\n\nconst useStyles = makeStyles((theme) => ({\n root: {\n padding: theme.spacing(3),\n display: \"flex\",\n justifyContent: \"center\",\n alignItems: \"center\",\n flexDirection: \"column\",\n height: \"80%\",\n },\n}));\n\nconst getQuestion = () => [\n {\n answers: Array.from({ length: 5 }, (value, key) => key).map((ix) => ({\n id: `${ix + 1}`,\n text: `${ix + 1}`,\n })),\n id: guid(),\n text: \"\",\n responses: [],\n },\n];\n\nconst { importQuestionsAction } = presenterActions(Name);\n\nconst FistOfFivePresenter = () => {\n const classes = useStyles();\n\n const dispatch = useDispatch();\n\n const canAnswer = true;\n useEffect(() => {\n dispatch(presenterMessage({ canAnswer }));\n dispatch(importQuestionsAction(getQuestion()));\n return () => {\n dispatch(presenterMessage({ canAnswer: false }));\n };\n }, [canAnswer]);\n\n const {\n responseCount,\n showResponses,\n playerCount,\n question,\n } = useQuestionState(Name, (state) => ({\n currentQuestionId: state.games.fistOfFive.presenter.currentQuestionId,\n questions: state.games.fistOfFive.presenter.questions,\n showResponses: state.games.fistOfFive.presenter.showResponses,\n }));\n\n return (\n \n ✊ ✋\n {showResponses ? (\n question && \n ) : (\n \n )}\n dispatch(importQuestionsAction(getQuestion()))}\n responses={question?.responses}\n />\n
\n );\n};\n\nexport default FistOfFivePresenter;\n","import { ReactNode } from \"react\";\nimport DoggosVsKittehsClient from \"./DoggosVsKittehs/DoggosVsKittehsClient\";\nimport DoggosVsKittehsPresenter from \"./DoggosVsKittehs/DoggosVsKittehsPresenter\";\nimport { BroadcastClient } from \"./Broadcast/BroadcastClient\";\nimport { BroadcastPresenter } from \"./Broadcast/BroadcastPresenter\";\nimport NamePickerClient from \"./NamePicker/NamePickerClient\";\nimport NamePickerPresenter from \"./NamePicker/NamePickerPresenter\";\nimport NamePickerMenu from \"./NamePicker/NamePickerMenu\";\nimport YesNoMaybePresenter from \"./YesNoMaybe/YesNoMaybePresenter\";\nimport { YesNoMaybeMenu } from \"./YesNoMaybe/YesNoMaybeMenu\";\nimport YesNoMaybeClient from \"./YesNoMaybe/YesNoMaybeClient\";\nimport { Name as YesNoMaybeName } from \"./YesNoMaybe/YesNoMaybeReducer\";\nimport { IdeaWallClient } from \"./IdeaWall/IdeaWallClient\";\nimport IdeaWallPresenter from \"./IdeaWall/IdeaWallPresenter\";\nimport IdeaWallMenu from \"./IdeaWall/IdeaWallMenu\";\nimport BuzzerClient from \"./Buzzer/BuzzerClient\";\nimport BuzzerPresenter from \"./Buzzer/BuzzerPresenter\";\nimport SplatClient from \"./Splat/SplatClient\";\nimport SplatPresenter from \"./Splat/SplatPresenter\";\nimport PongPresenter from \"./Pong/PongPresenter\";\nimport { PongClient } from \"./Pong/PongClient\";\nimport PongMenu from \"./Pong/PongMenu\";\nimport { ReactionPlayer } from \"./Reaction/ReactionClient\";\nimport { ReactionPresenter } from \"./Reaction/ReactionPresenter\";\nimport { Name as PollName } from \"./Poll\";\nimport { PollPresenter } from \"./Poll/PollPresenter\";\nimport PollClient from \"./shared/Poll/Client\";\nimport { TriviaPresenter } from \"./Trivia/TriviaPresenter\";\nimport { Name as TriviaName } from \"./Trivia\";\nimport { RouteLink } from \"../layout/useRoutes\";\nimport LiveHelp from \"@material-ui/icons/LiveHelp\";\nimport EditQuestions from \"./shared/Poll/components/EditQuestions\";\nimport EditQuestion from \"./shared/Poll/components/EditQuestion\";\nimport { Name as IdeaWallName } from \"./IdeaWall/IdeaWallReducer\";\nimport * as Retrospective from \"./Retrospective\";\nimport { Name as FistOfFiveName } from \"./FistOfFive/reducer\";\nimport FistOfFivePresenter from \"./FistOfFive/FistOfFivePresenter\";\n\ninterface Game {\n name: string;\n client: ReactNode;\n presenter: ReactNode;\n title: string;\n description: string;\n menu?: ReactNode;\n routes?: RouteLink[];\n isNew?: boolean;\n}\n\nconst pollAndTriviaRoutes = [\n {\n component: EditQuestions,\n path: \"/questions\",\n icon: LiveHelp,\n name: \"Questions\",\n },\n {\n component: EditQuestion,\n path: \"/questions/:id\",\n },\n];\n\nconst games: Game[] = [\n {\n title: \"Poll\",\n name: PollName,\n client: PollClient,\n presenter: PollPresenter,\n description: \"Audience polling: Add questions and poll your audience.\",\n routes: pollAndTriviaRoutes,\n },\n {\n isNew: true,\n title: \"Fist of Five\",\n name: FistOfFiveName,\n client: PollClient,\n presenter: FistOfFivePresenter,\n description:\n \"Quickly getting feedback or gauging consensus during a meeting\",\n },\n {\n title: \"Trivia\",\n name: TriviaName,\n client: PollClient,\n presenter: TriviaPresenter,\n description:\n \"Like polling but with a scoreboard: Add or generate questions and run a trivia session.\",\n routes: pollAndTriviaRoutes,\n },\n {\n isNew: true,\n title: \"Retrospective\",\n name: Retrospective.Name,\n client: Retrospective.Participant,\n presenter: Retrospective.Presenter,\n description: \"Run a retrospective with your team\",\n menu: Retrospective.Menu,\n },\n {\n name: \"doggos-vs-kittehs\",\n client: DoggosVsKittehsClient,\n presenter: DoggosVsKittehsPresenter,\n title: \"Doggos vs Kittehs\",\n description: \"Audience polling - Furry friend edition\",\n },\n {\n name: \"name-picker\",\n menu: NamePickerMenu,\n client: NamePickerClient,\n presenter: NamePickerPresenter,\n title: \"Name Picker\",\n description: \"Pick a name, out of a hat! (or a swirling void)\",\n },\n {\n name: YesNoMaybeName,\n client: YesNoMaybeClient,\n presenter: YesNoMaybePresenter,\n menu: YesNoMaybeMenu,\n title: \"Yes, No, Maybe\",\n description:\n \"Audience polling - ask your audience questions and get real-time feedback\",\n },\n {\n name: \"buzzer\",\n client: BuzzerClient,\n presenter: BuzzerPresenter,\n title: \"Buzzer\",\n description: \"Let your audience get a feel for low latency\",\n },\n {\n name: \"splat\",\n client: SplatClient,\n presenter: SplatPresenter,\n title: \"Splat\",\n description: \"It's paint-ball for audiences and presenters\",\n },\n {\n name: \"pong\",\n client: PongClient,\n presenter: PongPresenter,\n menu: PongMenu,\n title: \"Pong\",\n description: \"Mob pong for large audiences - red vs blue!\",\n },\n {\n name: IdeaWallName,\n client: IdeaWallClient,\n presenter: IdeaWallPresenter,\n menu: IdeaWallMenu,\n title: \"Idea Wall\",\n description:\n \"A virtual wall of ideas. Stick 'em to the wall and move them around\",\n },\n {\n name: \"broadcast\",\n client: BroadcastClient,\n presenter: BroadcastPresenter,\n title: \"Broadcast\",\n description:\n \"Demonstration of two-way, real-time presenter and audience participation\",\n },\n {\n title: \"Reaction\",\n name: \"reaction\",\n client: ReactionPlayer,\n presenter: ReactionPresenter,\n description:\n \"Test audience reflexes in this all-vs-all shape-matching game\",\n },\n];\n\nexport default games;\n","import React from \"react\";\nimport { RootState } from \"store/RootState\";\nimport { Presenter } from \"../shared/Poll/Presenter\";\nimport { Name } from \"./\";\n\nexport const PollPresenter = () => {\n return (\n ({\n currentQuestionId: state.games.poll.presenter.currentQuestionId,\n questions: state.games.poll.presenter.questions,\n showResponses: state.games.poll.presenter.showResponses,\n })}\n />\n );\n};\n","import React, { useEffect } from \"react\";\nimport { useSelector } from \"store/useSelector\";\nimport { presenterMessage } from \"store/lobby/actions\";\nimport { useDispatch } from \"react-redux\";\nimport { Presenter } from \"../shared/Poll/Presenter\";\nimport { Name } from \".\";\n\nexport const TriviaPresenter = () => {\n const dispatch = useDispatch();\n const { showScoreBoard, showResponses } = useSelector((state) => ({\n showScoreBoard: state.games.trivia.presenter.showScoreBoard,\n showResponses: state.games.trivia.presenter.showResponses,\n }));\n const canAnswer = !showResponses && !showScoreBoard;\n useEffect(() => {\n dispatch(presenterMessage({ canAnswer }));\n return () => {\n dispatch(presenterMessage({ canAnswer: false }));\n };\n }, [canAnswer]);\n return (\n ({\n currentQuestionId: state.games.trivia.presenter.currentQuestionId,\n questions: state.games.trivia.presenter.questions,\n showResponses: state.games.trivia.presenter.showResponses,\n })}\n />\n );\n};\n","import React, { useState } from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport ListItem from \"@material-ui/core/ListItem\";\nimport Button from \"../../layout/components/CustomButtons/Button\";\nimport { clearIdeasAction, setCategories } from \"./presenterReducer\";\nimport { ConfirmDialog } from \"../../components/ConfirmDialog\";\nimport { useSelector } from \"store/useSelector\";\nimport { RootState } from \"store/RootState\";\nimport { ideasByCategory } from \"./ideasByCategory\";\n\nexport const Menu = () => {\n const dispatch = useDispatch();\n const [confirmClearDialogOpen, setConfirmClearDialogOpen] = useState(false);\n const [\n confirmSetCategoriesDialogOpen,\n setConfirmSetCategoriesDialogOpen,\n ] = useState(false);\n const { ideas, categories } = useSelector(\n (state: RootState) => state.games.retrospective.presenter\n );\n\n const exportIdeas = () => {\n const fileName = \"retrospective.txt\";\n const text = categories\n .map(\n (category) =>\n `* ${category.name}\\n` +\n ideasByCategory(ideas, category.id)\n .map((idea) => ` * ${idea.payload.message}\\n`)\n .join(\"\")\n )\n .join(\"\");\n const fileToSave = new Blob([text], {\n type: \"plain/text\",\n });\n saveAs(fileToSave, fileName);\n };\n\n return (\n <>\n \n \n \n \n \n \n \n \n \n \n dispatch(clearIdeasAction()) && setConfirmClearDialogOpen(false)\n }\n />\n \n dispatch(setCategories([])) &&\n setConfirmSetCategoriesDialogOpen(false)\n }\n />\n >\n );\n};\n","import React from \"react\";\nimport Button from \"../../layout/components/CustomButtons/Button\";\nimport { useDispatch } from \"react-redux\";\nimport { presenterMessage } from \"../../store/lobby/actions\";\nimport ListItem from \"@material-ui/core/ListItem\";\n\nexport const YesNoMaybeMenu = () => {\n const dispatch = useDispatch();\n const reset = () => {\n dispatch(presenterMessage(\"reset\"));\n };\n return (\n \n \n \n );\n};\n","import React, { useState, useEffect } from \"react\";\nimport { Button } from \"../pixi/Button\";\nimport { PongColors as Colors } from \"./PongColors\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport { useDispatch } from \"react-redux\";\nimport { clientMessage } from \"../../store/lobby/actions\";\nimport { useResizeListener } from \"../pixi/useResizeListener\";\nimport { useSelector } from \"../../store/useSelector\";\n\nexport const PongClient = () => {\n const [app, setApp] = useState();\n const { pressedColor, releasedColor, team } = useSelector(\n (state) => state.games.pong.client\n );\n const dispatch = useDispatch();\n\n const message = (action: string) => () => dispatch(clientMessage(action));\n\n const appHandler = (newApp?: PIXI.Application) => {\n if (newApp) {\n setApp(newApp);\n }\n resize();\n };\n\n const resize = () => {\n if (app) {\n console.log(\"sizing buttons\");\n app.stage.removeChildren();\n\n const topButton = new Button(message(\"release\"), message(\"up\"));\n const bottomButton = new Button(message(\"release\"), message(\"down\"));\n\n const width = app.screen.width / 2;\n\n topButton.x = app.screen.width / 4;\n topButton.y = app.screen.height / 8;\n topButton.render(\n releasedColor,\n pressedColor,\n 0,\n 0,\n width,\n (app.screen.height / 16) * 5\n );\n\n bottomButton.x = app.screen.width / 4;\n bottomButton.y = (app.screen.height / 16) * 9;\n bottomButton.render(\n releasedColor,\n pressedColor,\n 0,\n 0,\n width,\n (app.screen.height / 16) * 5\n );\n\n app.stage.addChild(topButton, bottomButton);\n\n console.log(\"button width: \" + width);\n }\n };\n\n useEffect(resize, [app, team]);\n\n useResizeListener(resize);\n\n return (\n <>\n \n {team}\n
\n \n >\n );\n};\n","import React, { useState } from \"react\";\nimport { useDispatch } from \"react-redux\";\nimport { clientMessage } from \"../../store/lobby/actions\";\nimport { ContentContainer } from \"../../components/ContentContainer\";\nimport Grid from \"@material-ui/core/Grid\";\nimport Card from \"../../layout/components/Card/Card\";\nimport CardFooter from \"../../layout/components/Card/CardFooter\";\nimport Button from \"../../layout/components/CustomButtons/Button\";\nimport { IdeaEntry } from \"../shared/IdeaEntry\";\n\nexport const IdeaWallClient = () => {\n const dispatch = useDispatch();\n const [idea, setIdea] = useState(\"\");\n\n const onClick = (e: React.SyntheticEvent) => {\n if (idea.length) {\n dispatch(clientMessage(idea));\n setIdea(\"\");\n }\n };\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\n","import { Colors } from \"../../Colors\";\nimport { Shape } from \"./Shape\";\nimport * as PIXI from \"pixi.js\";\nimport { drawShape } from \"./ShapeView\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport { useDispatch } from \"react-redux\";\nimport { clientMessage } from \"../../store/lobby/actions\";\nimport React, { useEffect, useState } from \"react\";\nimport { RootState } from \"store/RootState\";\nimport { useResizeListener } from \"games/pixi/useResizeListener\";\nimport { useSelector } from \"store/useSelector\";\nimport { selectShape } from \"./reactionReducer\";\n\nexport const ReactionPlayer = () => {\n const [pixi, setPixi] = useState();\n const dispatch = useDispatch();\n const { shapes, selectedId } = useSelector(\n (state: RootState) => state.games.reaction.player\n );\n\n const select = (id: number) => {\n dispatch(clientMessage(id));\n dispatch(selectShape(id));\n resize();\n };\n\n const draw = (shape: Shape, radius: number, leftOffset = radius) => {\n const g = new PIXI.Graphics();\n let alpha = 1;\n if (selectedId === null) {\n g.on(\"pointerdown\", () => select(shape.id));\n g.buttonMode = true;\n g.interactive = true;\n } else alpha = 0.5;\n if (selectedId === shape.id) {\n g.lineStyle(5, Colors.BlueGrey.C900);\n alpha = 1;\n }\n g.beginFill(shape.color, alpha);\n\n return drawShape(g, shape.type, leftOffset, radius, radius).endFill();\n };\n\n const resize = () => {\n if (pixi) {\n console.log(\"performing layout\");\n pixi.stage.removeChildren();\n const margin = 25;\n const radius = Math.min(\n (pixi.screen.width - 3 * margin) / 4,\n (pixi.screen.height - (shapes.length / 2 + 1) * margin) / 6\n );\n for (let i = 0; i < shapes.length; i += 2) {\n const g1 = draw(shapes[i], radius);\n const g2 = draw(shapes[i + 1], radius, radius * 3 + margin);\n const container = new PIXI.Container();\n container.addChild(g1, g2);\n container.position.set(\n pixi.screen.width / 2 - container.width / 2,\n margin + (i / 2) * (radius * 2 + margin)\n );\n pixi.stage.addChild(container);\n }\n }\n };\n\n useResizeListener(resize);\n useEffect(resize, [pixi, shapes, selectedId]);\n\n return (\n setPixi(app)} />\n );\n};\n","import React, { useEffect, useRef, useState } from \"react\";\nimport { Shape } from \"./Shape\";\nimport { Colors, ColorUtils } from \"../../Colors\";\nimport { ShapeType } from \"./ShapeType\";\nimport { shuffle } from \"../../Random\";\nimport * as PIXI from \"pixi.js\";\nimport { ShapeView } from \"./ShapeView\";\nimport * as gsap from \"gsap\";\nimport { useDispatch } from \"react-redux\";\nimport { presenterMessage } from \"store/lobby/actions\";\nimport Button from \"../../layout/components/CustomButtons/Button\";\nimport Table from \"../../layout/components/Table/Table\";\nimport { Pixi } from \"../pixi/Pixi\";\nimport { RootState } from \"../../store/RootState\";\nimport { ContentContainer } from \"../../components/ContentContainer\";\nimport { useSelector } from \"store/useSelector\";\nimport AutoRenewIcon from \"@material-ui/icons/Autorenew\";\nimport {\n startRoundAction,\n toggleAutoAgainAction,\n getPlayerName,\n endRoundAction,\n} from \"./reactionReducer\";\nimport { useResizeListener } from \"games/pixi/useResizeListener\";\nimport { useTimeout } from \"util/useTimeout\";\n\nexport const ReactionPresenter = () => {\n const [pixi, setPixi] = useState();\n const [againTween, setAgainTween] = useState();\n const players = useSelector((state: RootState) => state.lobby.players);\n const againProgress = useRef(null);\n const dispatch = useDispatch();\n const { shape, shapes, scores, showScores, autoAgain, choices } = useSelector(\n (state: RootState) => state.games.reaction.presenter\n );\n\n const getOtherShapes = () => shapes.filter((s) => s.id !== shape!.id);\n\n const resize = () => {\n if (pixi && shape) {\n pixi.stage.removeChildren();\n const bottomShapes = getOtherShapes();\n const size = pixi.screen.height * 0.7;\n const mainShape = new ShapeView(size, shape);\n mainShape.view.position.set(pixi.screen.width / 2, size / 2);\n const bottomShapesContainer = new PIXI.Container();\n const views = [mainShape];\n\n let smallShapeWidth: number = 0;\n const shapeMargin = 20;\n bottomShapes.forEach((s) => {\n const shapeView = new ShapeView(pixi!.screen.height * 0.2, s);\n shapeView.view.position.set(\n (shapeView.view.width + shapeMargin) *\n bottomShapesContainer.children.length,\n 0\n );\n smallShapeWidth = shapeView.view.width;\n bottomShapesContainer.addChild(shapeView.view);\n views.push(shapeView);\n });\n bottomShapesContainer.position.set(\n pixi.screen.width / 2 -\n ((bottomShapes.length - 1) * (smallShapeWidth + shapeMargin)) / 2,\n pixi.screen.height - shapeMargin + smallShapeWidth / 2\n );\n bottomShapesContainer.pivot.set(0, bottomShapesContainer.height);\n pixi.stage.addChild(mainShape.view, bottomShapesContainer);\n views.forEach((view) => {\n view.update(\n choices.filter((choice) => choice.choice === view.id).length,\n getPlayerName(\n players,\n choices.find(\n (choice) => choice.isFirst && choice.choice === view.id\n )?.id\n )\n );\n });\n }\n };\n\n const setShape = () => {\n const colors = [\n Colors.Red.C500,\n Colors.Green.C500,\n Colors.Blue.C500,\n Colors.Indigo.C500,\n Colors.Orange.C500,\n ];\n let counter = 0;\n const allShapes: Shape[] = [];\n [ShapeType.Circle, ShapeType.Triangle, ShapeType.Square].forEach((s) => {\n allShapes.push(\n ...colors.map((c) => {\n return { id: counter++, type: s, color: c };\n })\n );\n });\n\n const newRoundShapes = shuffle(allShapes).slice(0, 6);\n dispatch(\n startRoundAction({ shapes: newRoundShapes, shape: newRoundShapes[0] })\n );\n dispatch(presenterMessage(shuffle([...newRoundShapes])));\n };\n\n useResizeListener(resize);\n useEffect(resize, [pixi, shape, choices]);\n\n useEffect(() => {\n if (autoAgain) {\n setAgainTween(\n gsap.TweenLite.to(againProgress.current!.style, 5, {\n width: \"0px\",\n ease: \"power1.in\",\n onComplete: () => setShape(),\n })\n );\n } else againTween && againTween.kill();\n }, [autoAgain, scores]);\n\n useTimeout(\n () => {\n if (shape) {\n dispatch(endRoundAction([...players]));\n }\n },\n 2000,\n [shape, autoAgain]\n );\n\n useEffect(() => setShape(), []);\n\n if (showScores) {\n if (pixi)\n pixi.view.parentElement && pixi.view.parentElement.removeChild(pixi.view);\n\n const tableData: any[] = [...scores]\n .sort((a, b) => b.score - a.score)\n .map((p, ix) => [p.score, p.name]);\n\n return (\n \n \n : undefined}\n onClick={() => {\n dispatch(toggleAutoAgainAction());\n }}\n >\n Again\n \n \n \n );\n } else {\n return (\n setPixi(app)}\n />\n );\n }\n};\n","import { useEffect } from \"react\";\n\nexport const useTimeout = (\n action: () => void,\n delayInMilliseconds: number,\n deps?: React.DependencyList\n) => {\n useEffect(() => {\n const timer = setTimeout(() => {\n action();\n }, delayInMilliseconds);\n return () => clearTimeout(timer);\n }, deps);\n};\n","/*eslint-disable*/\nimport React from \"react\";\nimport classNames from \"classnames\";\nimport { NavLink } from \"react-router-dom\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport Drawer from \"@material-ui/core/Drawer\";\nimport Hidden from \"@material-ui/core/Hidden\";\nimport List from \"@material-ui/core/List\";\nimport ListItem from \"@material-ui/core/ListItem\";\nimport ListItemText from \"@material-ui/core/ListItemText\";\nimport Icon from \"@material-ui/core/Icon\";\nimport styles from \"../../assets/jss/material-dashboard-react/components/sidebarStyle\";\nimport { toggleDrawer } from \"../../../store/shell/actions\";\nimport { useDispatch } from \"react-redux\";\nimport { useLocation } from \"react-router\";\nimport SidebarFooter from \"../../../components/SidebarFooter\";\nimport { useSelector } from \"../../../store/useSelector\";\nimport LobbyQrCode from \"../../../components/LobbyQrCode\";\nimport Games from \"../../../games/Games\";\n\nconst useStyles = (showQrCode) =>\n makeStyles((theme) => styles(showQrCode)(theme));\n\nexport default function Sidebar(props) {\n const dispatch = useDispatch();\n const location = useLocation();\n const lobby = useSelector((state) => state.lobby);\n const isPresenter = lobby.isPresenter;\n const isLobby = location.pathname === \"/\";\n const classes = useStyles(lobby.id && !isLobby)();\n const gameName = useSelector((state) => state.lobby.currentGame);\n const game = Games.find((g) => g.name == gameName);\n const GameMenu = game && game.menu;\n\n // verifies if routeName is the one active (in browser input)\n function activeRoute(routeName) {\n return location.pathname === routeName;\n }\n const { color, logo, logoText, routes } = props;\n var links = (\n \n {routes\n .filter((r) => r.name)\n .map((prop, key) => {\n var activePro = \" \";\n var listItemClasses;\n\n listItemClasses = classNames({\n [\" \" + classes[color]]: activeRoute(prop.path),\n });\n\n const whiteFontClasses = classNames({\n [\" \" + classes.whiteFont]: activeRoute(prop.path),\n });\n return (\n <>\n dispatch(toggleDrawer(false))}\n >\n \n {typeof prop.icon === \"string\" ? (\n \n {prop.icon}\n \n ) : (\n \n )}\n \n \n \n {isPresenter &&\n prop.path === \"/game\" &&\n location.pathname === \"/game\" &&\n GameMenu && }\n >\n );\n })}\n
\n );\n var brand = (\n \n
\n \n

\n
\n {logoText}\n \n
\n );\n\n const qrCode = lobby.id && !isLobby && ;\n\n return (\n \n
\n dispatch(toggleDrawer())}\n ModalProps={{\n keepMounted: true, // Better open performance on mobile.\n }}\n >\n {brand}\n \n {qrCode}\n {links}\n \n
\n \n \n \n
\n \n {brand}\n \n {qrCode}\n {links}\n \n
\n \n \n \n
\n );\n}\n","import React, { useState } from \"react\";\nimport GridItem from \"../layout/components/Grid/GridItem\";\nimport GridContainer from \"../layout/components/Grid/GridContainer.js\";\nimport CustomInput from \"../layout/components/CustomInput/CustomInput\";\nimport Button from \"../layout/components/CustomButtons/Button\";\nimport Card from \"../layout/components/Card/Card.js\";\nimport CardBody from \"../layout/components/Card/CardBody.js\";\nimport CardFooter from \"../layout/components/Card/CardFooter.js\";\nimport { useDispatch } from \"react-redux\";\nimport { createLobby } from \"../store/lobby/actions\";\nimport CardTitle from \"../layout/components/Card/CardTitle\";\nimport { ContentContainer } from \"./ContentContainer\";\n\nexport default function CreateLobby() {\n const dispatch = useDispatch();\n\n const [lobbyName, setLobbyName] = useState(\"My Lobby\");\n\n const isValid = () => {\n return lobbyName.trim().length > 2;\n };\n\n const handleChange: React.ChangeEventHandler<\n HTMLTextAreaElement | HTMLInputElement\n > = (e) => {\n setLobbyName(e.target.value);\n console.log(e.target.value, lobbyName);\n };\n\n const onClick = () => {\n if (isValid()) {\n dispatch(createLobby(lobbyName!));\n }\n };\n\n return (\n \n \n \n \n \n \n \n \n handleChange(e)}\n error={!isValid()}\n />\n \n \n \n \n \n \n \n \n \n \n );\n}\n","import React from \"react\";\nimport Button from \"../layout/components/CustomButtons/Button\";\nimport { useDispatch } from \"react-redux\";\nimport { closeLobby } from \"../store/lobby/actions\";\nimport GridItem from \"../layout/components/Grid/GridItem\";\nimport GridContainer from \"../layout/components/Grid/GridContainer.js\";\nimport Card from \"../layout/components/Card/Card.js\";\nimport CardFooter from \"../layout/components/Card/CardFooter.js\";\nimport CardTitle from \"../layout/components/Card/CardTitle\";\nimport { ContentContainer } from \"./ContentContainer\";\n\nexport default function CloseLobby() {\n const dispatch = useDispatch();\n\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n );\n}\n","import { Player } from \"../../Player\";\n\nexport interface UserState {\n name: string;\n id: string;\n isRegistered: boolean;\n}\n\nexport const SET_USER = \"SET_USER\";\nexport const SET_USER_NAME = \"SET_NAME\";\n\ninterface SetUserAction {\n type: typeof SET_USER;\n user: Player;\n}\n\ninterface SetNameAction {\n type: typeof SET_USER_NAME;\n name: string;\n}\n\nexport type UserActionTypes = SetUserAction | SetNameAction;\n","import { SET_USER, UserActionTypes, SET_USER_NAME } from \"./types\";\nimport { Player } from \"../../Player\";\n\nexport function setUser(user: Player): UserActionTypes {\n return { type: SET_USER, user };\n}\n\nexport function setUserName(name: string): UserActionTypes {\n return { type: SET_USER_NAME, name };\n}\n","import React, { useState } from \"react\";\nimport { useSelector } from \"../store/useSelector\";\nimport { setUserName } from \"../store/user/actions\";\nimport { goToDefaultUrl } from \"../store/shell/actions\";\nimport GridItem from \"../layout/components/Grid/GridItem\";\nimport GridContainer from \"../layout/components/Grid/GridContainer.js\";\nimport CustomInput from \"../layout/components/CustomInput/CustomInput\";\nimport Button from \"../layout/components/CustomButtons/Button\";\nimport Card from \"../layout/components/Card/Card.js\";\nimport CardBody from \"../layout/components/Card/CardBody.js\";\nimport CardFooter from \"../layout/components/Card/CardFooter.js\";\nimport { useDispatch } from \"react-redux\";\nimport CardTitle from \"../layout/components/Card/CardTitle\";\nimport { ContentContainer } from \"../components/ContentContainer\";\n\nconst Register = () => {\n const initialName = useSelector((state) => state.user.name || \"\");\n const [name, setName] = useState(initialName);\n const dispatch = useDispatch();\n\n const isValid = () => {\n return name.trim().length > 2;\n };\n\n const onSubmit = (e: any) => {\n if (isValid()) {\n dispatch(setUserName(name));\n dispatch(goToDefaultUrl());\n }\n };\n\n return (\n \n \n \n \n \n \n \n \n setName(e.target.value)}\n error={!isValid()}\n />\n \n \n \n \n \n \n \n \n \n \n );\n};\nexport default Register;\n","import React, { useEffect, useState } from \"react\";\nimport GridItem from \"../layout/components/Grid/GridItem\";\nimport GridContainer from \"../layout/components/Grid/GridContainer.js\";\nimport CustomInput from \"../layout/components/CustomInput/CustomInput\";\nimport Button from \"../layout/components/CustomButtons/Button\";\nimport Card from \"../layout/components/Card/Card.js\";\nimport CardBody from \"../layout/components/Card/CardBody.js\";\nimport CardFooter from \"../layout/components/Card/CardFooter.js\";\nimport { useDispatch } from \"react-redux\";\nimport { joinLobby } from \"../store/lobby/actions\";\nimport CardTitle from \"../layout/components/Card/CardTitle\";\nimport { ContentContainer } from \"./ContentContainer\";\nimport { useParams } from \"react-router\";\n\ninterface RouteParams {\n id: string;\n}\n\nexport default function Join() {\n const dispatch = useDispatch();\n const { id } = useParams();\n\n const [lobbyCode, setLobbyCode] = useState(\"\");\n\n useEffect(() => {\n setLobbyCode(id || \"\");\n if (id) {\n join(id);\n }\n }, [id]);\n\n const isValid = (code: string | undefined) => {\n return code && code.trim().length === 4;\n };\n\n const handleChange: React.ChangeEventHandler<\n HTMLTextAreaElement | HTMLInputElement\n > = (e) => {\n setLobbyCode(e.target.value);\n console.log(e.target.value, lobbyCode);\n };\n\n const join = (lobbyCode: string | undefined) => {\n if (isValid(lobbyCode)) {\n dispatch(joinLobby(lobbyCode!));\n }\n };\n\n return (\n \n \n \n \n \n \n \n \n handleChange(e)}\n error={!isValid(lobbyCode)}\n />\n \n \n \n \n \n \n \n \n \n \n );\n}\n","import React from \"react\";\nimport GridItem from \"../layout/components/Grid/GridItem\";\nimport GridContainer from \"../layout/components/Grid/GridContainer.js\";\nimport Card from \"../layout/components/Card/Card.js\";\nimport CardBody from \"../layout/components/Card/CardBody\";\nimport CardTitle from \"../layout/components/Card/CardTitle\";\nimport { ContentContainer } from \"./ContentContainer\";\nimport CardFooter from '../layout/components/Card/CardFooter';\nimport Button from '../layout/components/CustomButtons/Button';\nimport { useHistory } from 'react-router';\n\nconst LobbyClosed = () => {\n const history = useHistory();\n return (\n \n \n \n \n \n \n \n \n \n \n \n \n \n );\n};\nexport default LobbyClosed;\n","import React from \"react\";\nimport LobbyQrCode from \"./LobbyQrCode\";\n\nexport const Lobby = () => {\n return ;\n};\n","export class ChangelogItem {\n date: Date;\n change: string;\n link: string | undefined;\n\n constructor(\n date: Date,\n change: string,\n link: string | undefined = undefined\n ) {\n this.date = date;\n this.change = change;\n this.link = link;\n }\n\n static fromParts(year: number, month: number, day: number, change: string) {\n return new ChangelogItem(new Date(year, month - 1, day), change);\n }\n}\n","import React, { useEffect, useState } from \"react\";\nimport { ChangelogItem } from \"../ChangelogItem\";\nimport Parser from \"rss-parser\";\nimport { CircularProgress, makeStyles, Typography } from \"@material-ui/core\";\nimport { DateTime } from \"luxon\";\n\nconst useStyles = makeStyles((theme) => ({\n updates: {\n display: \"inline\",\n },\n list: {\n paddingLeft: theme.spacing(3),\n marginTop: 0,\n },\n}));\n\ntype CustomFeed = {};\ntype CustomItem = {\n pubDate: string;\n contentSnippet: string;\n};\n\nconst parser: Parser = new Parser({});\n\nexport function Changelog() {\n const [changelogs, setChangelogs] = useState([\n ChangelogItem.fromParts(2020, 4, 24, \"added #poll\"),\n ChangelogItem.fromParts(2020, 4, 20, \"reskin\"),\n ChangelogItem.fromParts(2020, 4, 15, \"added #splat + dev tutorial\"),\n ChangelogItem.fromParts(2019, 11, 17, \"added #reaction\"),\n ChangelogItem.fromParts(2019, 6, 3, \"added auto-arrange to #ideawall\"),\n ChangelogItem.fromParts(\n 2019,\n 5,\n 29,\n \"fixed chart overflow in #yesnomaybe, #doggosvskittehs\"\n ),\n ChangelogItem.fromParts(2019, 5, 7, \"added #broadcast\"),\n ChangelogItem.fromParts(2019, 5, 4, \"added score to #pong\"),\n ChangelogItem.fromParts(2019, 4, 2, \"added #ideawall\"),\n ]);\n\n const [isLoading, setIsLoading] = useState(true);\n\n useEffect(() => {\n async function readFeed() {\n try {\n const feed = await parser.parseURL(\n \"https://staffordwilliams.com/devlog/digital-icebreakers.xml\"\n );\n\n const changes = feed.items\n .map((entry) => {\n const date = new Date(entry.pubDate);\n const change = new ChangelogItem(date, entry.title!, entry.link!);\n return change;\n })\n .sort((a, b) => b.date.getTime() - a.date.getTime());\n\n setChangelogs([...changes, ...changelogs]);\n }\n catch (e) {\n console.error(e);\n }\n setIsLoading(false);\n }\n\n readFeed();\n }, []);\n const classes = useStyles();\n return (\n \n
\n {isLoading &&
}\n
\n {changelogs.map((item, ix) => (\n - \n \n {item.link ? (\n {item.change}\n ) : (\n {item.change}\n )}{\" \"}\n • {DateTime.fromJSDate(item.date).toRelative()}\n \n
\n ))}\n
\n
\n );\n}\n","import React from \"react\";\nimport { makeStyles } from \"@material-ui/core/styles\";\n\nconst useStyles = makeStyles((theme) => ({\n link: {\n float: \"right\",\n position: \"absolute\",\n right: 0,\n [theme.breakpoints.down(\"sm\")]: {\n left: 0,\n right: \"auto\",\n transform: \"rotate(-90deg)\",\n },\n },\n}));\n\nexport const GithubFork = () => {\n const classes = useStyles();\n return (\n \n
\n \n );\n};\n","import React from \"react\";\nimport { Changelog } from \"./Changelog\";\nimport { GithubFork } from \"./GithubFork\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport { ContentContainer } from \"./ContentContainer\";\nimport Button from \"../layout/components/CustomButtons/Button\";\nimport { useHistory } from \"react-router-dom\";\nimport { Grid, Typography } from \"@material-ui/core\";\n\nconst useStyles = makeStyles((theme) => ({\n imageContainer: {\n backgroundColor: \"#191919\",\n textAlign: \"center\",\n },\n image: {\n maxHeight: \"320px\",\n maxWidth: \"100%\",\n },\n howItWorks: {\n [theme.breakpoints.up(\"sm\")]: {\n marginTop: theme.spacing(2),\n },\n },\n buttonContainer: {\n display: \"flex\",\n justifyContent: \"center\",\n marginBottom: theme.spacing(2),\n },\n joinButton: {\n marginLeft: theme.spacing(2),\n },\n}));\n\nexport const Home = () => {\n const classes = useStyles();\n const history = useHistory();\n return (\n <>\n \n \n

\n
\n \n \n \n \n
\n \n \n Feedback\n \n Feature requests, suggestions, bugs & feedback to{\" \"}\n \n backlog\n {\" \"}\n or stafford@atqu.in\n \n \n How it works\n \n \n A presenter creates a Lobby and audience members join by pointing\n their phone cameras at the presenter's screen and scanning the QR\n code. The presenter can then guide the audience through games and\n experiences by clicking New Activity.\n \n \n \n \n \n \n \n >\n );\n};\n","import {\n warningCardHeader,\n successCardHeader,\n dangerCardHeader,\n infoCardHeader,\n primaryCardHeader,\n roseCardHeader,\n grayColor,\n} from \"../../material-dashboard-react\";\n\nconst cardIconStyle: any = {\n cardIcon: {\n \"&$warningCardHeader,&$successCardHeader,&$dangerCardHeader,&$infoCardHeader,&$primaryCardHeader,&$roseCardHeader\": {\n borderRadius: \"3px\",\n backgroundColor: grayColor[0],\n padding: \"15px\",\n marginTop: \"-20px\",\n marginRight: \"15px\",\n float: \"left\",\n },\n },\n warningCardHeader,\n successCardHeader,\n dangerCardHeader,\n infoCardHeader,\n primaryCardHeader,\n roseCardHeader,\n};\n\nexport default cardIconStyle;\n","import React from \"react\";\n// nodejs library that concatenates classes\nimport classNames from \"classnames\";\n// nodejs library to set properties for components\nimport PropTypes from \"prop-types\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\n// @material-ui/icons\n\n// core components\nimport styles from \"../../assets/jss/material-dashboard-react/components/cardIconStyle\";\n\nconst useStyles = makeStyles(styles);\n\nexport default function CardIcon(props) {\n const classes = useStyles();\n const { className, children, color, ...rest } = props;\n const cardIconClasses = classNames({\n [classes.cardIcon]: true,\n [classes[color + \"CardHeader\"]]: color,\n [className]: className !== undefined,\n });\n return (\n \n {children}\n
\n );\n}\n\nCardIcon.propTypes = {\n className: PropTypes.string,\n color: PropTypes.oneOf([\n \"warning\",\n \"success\",\n \"danger\",\n \"info\",\n \"primary\",\n \"rose\",\n ]),\n children: PropTypes.node,\n};\n","import React from \"react\";\nimport { Switch, Route, Redirect } from \"react-router-dom\";\n// creates a beautiful scrollbar\nimport PerfectScrollbar from \"perfect-scrollbar\";\nimport \"perfect-scrollbar/css/perfect-scrollbar.css\";\n// @material-ui/core components\nimport { makeStyles } from \"@material-ui/core/styles\";\n// core components\nimport Navbar from \"../components/Navbars/Navbar.js\";\nimport { toggleDrawer } from \"../../store/shell/actions\";\nimport { useSelector } from \"../../store/useSelector\";\nimport Sidebar from \"../components/Sidebar/Sidebar.js\";\n\nimport useRoutes from \"../useRoutes\";\n\nimport styles from \"../assets/jss/material-dashboard-react/layouts/adminStyle\";\n\nlet ps;\n\nconst switchRoutes = (routes) => (\n \n {routes.map((prop, key) => (\n \n ))}\n {\n if (location.pathname.length === 5) {\n console.log(\n `redirecting to /join-lobby${location.pathname} from ${location.pathname}`\n );\n return ;\n }\n console.log(\"redirecting to / from \" + location.pathname);\n return ;\n }}\n />\n \n);\n\nconst useStyles = makeStyles(styles);\n\nexport default function Admin({ isPresenter, currentGame, lobbyId, ...rest }) {\n // styles\n const classes = useStyles();\n // ref to help us initialize PerfectScrollbar on windows devices\n const mainPanel = React.createRef();\n const showDrawer = useSelector((state) => state.shell.showDrawer);\n\n const getRoute = () => {\n return window.location.pathname !== \"/admin/maps\";\n };\n const resizeFunction = () => {\n if (window.innerWidth >= 960) {\n toggleDrawer(false);\n }\n };\n // initialize and destroy the PerfectScrollbar plugin\n React.useEffect(() => {\n if (navigator.platform.indexOf(\"Win\") > -1) {\n ps = new PerfectScrollbar(mainPanel.current, {\n suppressScrollX: true,\n suppressScrollY: false,\n });\n document.body.style.overflow = \"hidden\";\n }\n window.addEventListener(\"resize\", resizeFunction);\n // Specify how to clean up after this effect:\n return function cleanup() {\n if (navigator.platform.indexOf(\"Win\") > -1) {\n ps.destroy();\n }\n window.removeEventListener(\"resize\", resizeFunction);\n };\n }, [mainPanel]);\n\n const routes = useRoutes();\n\n return (\n \n
\n
\n
\n {/* On the /maps route we want the map to be on full screen - this is not possible if the content and conatiner classes are present because they have some paddings which would make the map smaller */}\n {getRoute() ? (\n
\n
{switchRoutes(routes)}
\n
\n ) : (\n
{switchRoutes(routes)}
\n )}\n {/* {getRoute() ?
: null} */}\n
\n
\n );\n}\n","import React from \"react\";\nimport Games from \"../games/Games\";\nimport { startNewGame } from \"../store/lobby/actions\";\nimport { useDispatch } from \"react-redux\";\nimport { ContentContainer } from \"./ContentContainer\";\nimport Card from \"../layout/components/Card/Card\";\nimport CardFooter from \"../layout/components/Card/CardFooter\";\nimport CardBody from \"../layout/components/Card/CardBody\";\nimport CardIcon from \"../layout/components/Card/CardIcon\";\nimport GridContainer from \"../layout/components/Grid/GridContainer\";\nimport GridItem from \"../layout/components/Grid/GridItem\";\nimport FiberNew from \"@material-ui/icons/FiberNew\";\nimport { makeStyles } from \"@material-ui/core/styles\";\nimport { grayColor } from \"../layout/assets/jss/material-dashboard-react\";\nimport Button from \"../layout/components/CustomButtons/Button\";\n\nconst useStyles = makeStyles((theme) => ({\n cardCategory: {\n color: grayColor[0],\n margin: \"0\",\n fontSize: \"14px\",\n marginTop: \"0\",\n paddingTop: \"10px\",\n marginBottom: \"0\",\n },\n cardTitle: {\n color: grayColor[2],\n marginTop: \"0px\",\n minHeight: \"auto\",\n fontWeight: 300,\n fontFamily: \"'Roboto', 'Helvetica', 'Arial', sans-serif\",\n marginBottom: \"3px\",\n textDecoration: \"none\",\n \"& small\": {\n color: grayColor[1],\n fontWeight: 400,\n lineHeight: 1,\n },\n },\n icon: {\n display: \"flex\",\n position: \"absolute\",\n right: 0,\n width: \"24px\",\n },\n}));\n\nconst NewGame = () => {\n const dispatch = useDispatch();\n const classes = useStyles();\n\n const newGame = (name: string) => {\n dispatch(startNewGame(name));\n };\n\n return (\n \n New activity
\n \n {Games.map((g) => (\n \n \n {g.isNew && (\n \n \n \n )}\n \n {g.title}
\n {g.description}
\n \n \n \n \n
\n \n \n \n ))}\n \n \n );\n};\n\nexport default NewGame;\n","import React from \"react\";\nimport Games from \"../games/Games\";\nimport { useSelector } from \"../store/useSelector\";\n\nexport const Game = () => {\n const name = useSelector((state) => state.lobby.currentGame);\n const game = Games.find((g) => g.name === name);\n const isPresenter = useSelector((state) => state.lobby.isPresenter);\n const Component = isPresenter ? game!.presenter : (game!.client as any);\n\n if (!game) return No game
;\n else return ;\n};\n","import React from 'react';\n\nconst Blank = () => <>>\n\nexport default Blank;","import AddCircle from \"@material-ui/icons/AddCircle\";\nimport LiveTv from \"@material-ui/icons/LiveTv\";\nimport GroupAdd from \"@material-ui/icons/GroupAdd\";\nimport People from \"@material-ui/icons/People\";\nimport SportsEsports from \"@material-ui/icons/SportsEsports\";\nimport Cancel from \"@material-ui/icons/Cancel\";\nimport CreateLobby from \"../components/CreateLobby\";\nimport CloseLobby from \"../components/CloseLobby\";\nimport Register from \"../components/Register\";\nimport JoinLobby from \"../components/JoinLobby\";\nimport LobbyClosed from \"../components/LobbyClosed\";\nimport { Lobby } from \"../components/Lobby\";\nimport { Home } from \"../components/Home\";\nimport NewGame from \"../components/NewGame\";\nimport { useSelector } from \"../store/useSelector\";\nimport Games from \"../games/Games\";\nimport { Game } from \"../components/Game\";\nimport { ReactNode } from \"react\";\nimport Blank from \"./Blank\";\n\nexport interface RouteLink {\n path: string;\n name?: string;\n icon?: ReactNode;\n component: ReactNode;\n}\n\nconst useRoutes = () => {\n const lobby = useSelector((state) => state.lobby);\n const game = useSelector((state) =>\n Games.find((g) => g.name === state.lobby.currentGame)\n );\n const gameRoutes: RouteLink[] =\n lobby.isPresenter && game && game.routes ? game.routes : [];\n\n const lobbyRoute = {\n path: \"/\",\n name: `Lobby (${lobby.players.length})`,\n icon: People,\n component: Lobby,\n testId: \"menu-lobby\",\n };\n\n const homeRoute = {\n path: \"/\",\n component: Home,\n };\n\n const createLobbyRoute = {\n path: \"/create-lobby\",\n name: \"Present\",\n icon: LiveTv,\n component: CreateLobby,\n };\n\n const joinRoute = {\n route: \"/join-lobby/:id?\",\n path: \"/join-lobby\",\n name: \"Join\",\n icon: GroupAdd,\n component: JoinLobby,\n };\n\n const newGameRoute = {\n path: \"/new-game\",\n name: \"New Activity\",\n icon: AddCircle,\n component: NewGame,\n };\n\n const noGameRoute = {\n path: \"/game\",\n component: Blank,\n };\n\n const gameRoute = {\n path: \"/game\",\n name: game?.title,\n icon: SportsEsports,\n component: Game,\n };\n\n const closeLobbyRoute = {\n path: \"/close-lobby\",\n name: \"Close Lobby\",\n icon: Cancel,\n component: CloseLobby,\n };\n\n const registerRoute = {\n path: \"/register\",\n component: Register,\n };\n\n const lobbyClosedRoute = {\n path: \"/lobby-closed\",\n component: LobbyClosed,\n };\n\n const ifNoLobby = (...routes: RouteLink[]) => (lobby.id ? [] : routes);\n const ifLobby = (...routes: RouteLink[]) => (lobby.id ? routes : []);\n const ifAdmin = (...routes: RouteLink[]) => (lobby.isPresenter ? routes : []);\n const ifGame = (yes: RouteLink[], no: RouteLink[]) => (game ? yes : no);\n\n return [\n ...ifLobby(lobbyRoute),\n ...ifNoLobby(homeRoute, createLobbyRoute, joinRoute, registerRoute),\n ...ifAdmin(newGameRoute),\n ...ifGame([gameRoute, ...gameRoutes], [noGameRoute]),\n ...ifAdmin(closeLobbyRoute),\n lobbyClosedRoute,\n ];\n};\n\nexport default useRoutes;\n","import { drawerWidth, transition } from \"../../material-dashboard-react\";\n\nconst appStyle = (theme) => ({\n wrapper: {\n position: \"relative\",\n top: \"0\",\n height: \"100vh\",\n },\n mainPanel: {\n [theme.breakpoints.up(\"md\")]: {\n width: `calc(100% - ${drawerWidth}px)`,\n },\n overflow: \"auto\",\n float: \"right\",\n ...transition,\n height: \"100%\",\n width: \"100%\",\n overflowScrolling: \"touch\",\n },\n content: {\n padding: 0,\n height: \"100%\",\n },\n container: {\n height: \"100%\",\n },\n});\n\nexport default appStyle;\n","export enum ConnectionStatus {\n NotConnected,\n Pending,\n Connected,\n}\n","import { createBrowserHistory } from \"history\";\n\nexport default createBrowserHistory({\n /* pass a configuration object here if needed */\n});\n","import { Player } from \"Player\";\nimport { ConnectionStatus } from \"../../ConnectionStatus\";\n\nexport interface ConnectionState {\n status: ConnectionStatus;\n}\n\nexport const SET_CONNECTION_STATUS = \"SET_CONNECTION_STATUS\";\nexport const CONNECTION_CONNECT = \"CONNECTION_CONNECT\";\nexport const CONNECTION_RECONNECT = \"CONNECTION_RECONNECT\";\n\ninterface SetConnectionStatusAction {\n type: typeof SET_CONNECTION_STATUS;\n status: ConnectionStatus;\n}\n\ninterface ConnectionConnectAction {\n type: typeof CONNECTION_CONNECT;\n lobbyId: string | undefined;\n}\n\ninterface ConnectionReconnectAction {\n type: typeof CONNECTION_RECONNECT;\n}\n\nexport type ConnectionActionTypes =\n | SetConnectionStatusAction\n | ConnectionConnectAction\n | ConnectionReconnectAction;\n\nexport type ReconnectPayload = {\n playerId: string;\n playerName: string;\n lobbyId: string;\n lobbyName: string;\n isPresenter: boolean;\n players: Player[];\n currentGame: string;\n isRegistered: boolean;\n};\n","import { ConnectionStatus } from \"../../ConnectionStatus\";\nimport {\n ConnectionState,\n ConnectionActionTypes,\n SET_CONNECTION_STATUS,\n} from \"./types\";\n\nconst initialState: ConnectionState = {\n status: ConnectionStatus.NotConnected,\n};\n\nexport function connectionReducer(\n state = initialState,\n action: ConnectionActionTypes\n): ConnectionState {\n switch (action.type) {\n case SET_CONNECTION_STATUS: {\n return {\n ...state,\n status: action.status,\n };\n }\n default:\n return state;\n }\n}\n","import { UserState, UserActionTypes, SET_USER, SET_USER_NAME } from \"./types\";\n\nconst initialState: UserState = {\n id: \"\",\n name: \"\",\n isRegistered: false,\n};\n\nexport function userReducer(\n state = initialState,\n action: UserActionTypes\n): UserState {\n switch (action.type) {\n case SET_USER_NAME: {\n return {\n ...state,\n name: action.name,\n isRegistered: true,\n };\n }\n case SET_USER: {\n return {\n ...state,\n id: action.user.id,\n name: action.user.name,\n };\n }\n default:\n return state;\n }\n}\n","import {\n LobbyState,\n LobbyActionTypes,\n SET_LOBBY,\n SET_LOBBY_GAME,\n SET_LOBBY_PLAYERS,\n CLEAR_LOBBY,\n JOIN_LOBBY,\n PLAYER_JOINED_LOBBY,\n PLAYER_LEFT_LOBBY,\n CREATE_LOBBY,\n} from \"./types\";\n\nconst initialState: LobbyState = {\n isPresenter: false,\n players: [],\n currentGame: \"\",\n};\n\nexport function lobbyReducer(\n state = initialState,\n action: LobbyActionTypes\n): LobbyState {\n switch (action.type) {\n case SET_LOBBY: {\n return {\n id: action.id,\n name: action.name,\n isPresenter: action.isPresenter,\n currentGame: action.game,\n players: action.players,\n };\n }\n case CREATE_LOBBY: {\n return {\n ...state,\n isPresenter: true,\n name: action.name,\n };\n }\n case JOIN_LOBBY: {\n return {\n ...state,\n joiningLobbyId: action.id,\n };\n }\n case PLAYER_JOINED_LOBBY: {\n return {\n ...state,\n players: [\n ...state.players.filter((p) => p.id !== action.player.id),\n action.player,\n ],\n };\n }\n case PLAYER_LEFT_LOBBY: {\n return {\n ...state,\n players: state.players.filter((p) => p.id !== action.player.id),\n };\n }\n case SET_LOBBY_PLAYERS: {\n return {\n ...state,\n players: action.players,\n };\n }\n case SET_LOBBY_GAME: {\n return {\n ...state,\n currentGame: action.game,\n };\n }\n case CLEAR_LOBBY: {\n return initialState;\n }\n default:\n return state;\n }\n}\n","import { Config } from \"../../config\";\n\nimport {\n ShellState,\n ShellActionTypes,\n SET_MENU_ITEMS,\n TOGGLE_MENU,\n TOGGLE_DRAWER,\n} from \"./types\";\n\nconst initialState: ShellState = {\n version: Config.version,\n menuItems: [],\n showMenu: true,\n showDrawer: false,\n};\n\nexport function shellReducer(\n state = initialState,\n action: ShellActionTypes\n): ShellState {\n switch (action.type) {\n case SET_MENU_ITEMS:\n return {\n ...state,\n menuItems: action.items,\n };\n case TOGGLE_MENU: {\n return {\n ...state,\n showMenu: action.show,\n };\n }\n case TOGGLE_DRAWER: {\n return {\n ...state,\n showDrawer: action.show === undefined ? !state.showDrawer : action.show,\n };\n }\n\n default:\n return state;\n }\n}\n","import { createReceiveGameMessageReducer } from \"../../store/actionHelpers\";\nimport { YesNoMaybeState } from \"../YesNoMaybe/YesNoMaybeReducer\";\n\nexport const Name = \"doggos-vs-kittehs\";\n\ninterface ServerState {\n doggos: number;\n kittehs: number;\n undecided: number;\n}\n\nexport const doggosVsKittehsReducer = createReceiveGameMessageReducer<\n ServerState,\n YesNoMaybeState\n>(\n Name,\n {\n yes: 0,\n no: 0,\n maybe: 0,\n },\n (_, { payload: { payload: result } }) => ({\n yes: result.doggos,\n no: result.kittehs,\n maybe: result.undecided,\n })\n);\n","import { createReceiveGameMessageReducer } from \"../../store/actionHelpers\";\n\nexport interface Player {\n id: string;\n name: string;\n state: string;\n}\n\nexport const Name = \"buzzer\";\n\nexport const buzzerReducer = createReceiveGameMessageReducer(\n Name,\n [],\n (state, { payload }) => [\n ...state.filter((p) => p.id !== payload.id),\n { id: payload.id, name: payload.name, state: payload.payload },\n ]\n);\n","import { createReceiveGameMessageReducer } from \"../../store/actionHelpers\";\n\nexport const Name = \"splat\";\n\nexport interface SplatState {\n count: number;\n}\n\nexport const splatReducer = createReceiveGameMessageReducer(\n Name,\n { count: 0 },\n (state, { payload: { payload: result } }) => {\n return {\n count: state.count + (result === \"down\" ? 1 : 0),\n };\n }\n);\n","import { createReceiveReducer } from \"store/actionHelpers\";\nimport { AvailableAnswers } from \"games/shared/Poll/types/AvailableAnswers\";\nimport { PollPlayerState } from \"games/shared/Poll/types/PlayerState\";\nimport { playerActionReducer } from \"../../shared/Poll/reducers/playerActionReducer\";\nimport { initialPlayerState } from \"../../shared/Poll/reducers/initialPlayerState\";\nimport { Name } from \"..\";\n\ntype Payload = AvailableAnswers;\n\nexport const pollPlayerReducer = createReceiveReducer(\n Name,\n initialPlayerState,\n (state, { payload: availableAnswers }) => {\n return {\n ...state,\n ...availableAnswers,\n answerLocked: !!availableAnswers.selectedAnswerId,\n };\n },\n \"client\",\n playerActionReducer(Name)\n);\n","import { createReceiveGameMessageReducer } from \"store/actionHelpers\";\nimport { SelectedAnswer } from \"games/shared/Poll/types/SelectedAnswer\";\nimport { PollPresenterState } from \"games/shared/Poll/types/PresenterState\";\nimport { Name } from \"..\";\nimport { presenterActionReducers } from \"games/shared/Poll/reducers/presenterActionReducers\";\nimport { initialPresenterState } from \"games/shared/Poll/reducers/initialPresenterState\";\nimport { presenterPayloadReducer } from \"games/shared/Poll/reducers/presenterPayloadReducer\";\nexport const storageKey = \"poll:questions\";\n\nexport const pollPresenterReducer = createReceiveGameMessageReducer<\n SelectedAnswer[],\n PollPresenterState\n>(\n Name,\n initialPresenterState(storageKey),\n (\n state: PollPresenterState,\n { payload: { id: playerId, name: playerName, payload: answers } }\n ) => presenterPayloadReducer(state, answers, playerId, playerName),\n \"presenter\",\n (builder) => presenterActionReducers(Name, storageKey)(builder, () => true)\n);\n","import { combineReducers } from \"redux\";\nimport { pollPlayerReducer } from \"./pollPlayerReducer\";\nimport { PollState } from \"games/shared/Poll/types/State\";\nimport { pollPresenterReducer } from \"./pollPresenterReducer\";\n\nexport const pollReducer = combineReducers({\n player: pollPlayerReducer,\n presenter: pollPresenterReducer,\n});\n","import { AvailableAnswers } from \"games/shared/Poll/types/AvailableAnswers\";\nimport { sharedTriviaPlayerReducer } from \"games/shared/Poll/reducers/sharedTriviaPlayerReducer\";\n\nconst TriviaName = \"trivia\";\n\ninterface CanAnswer {\n canAnswer: boolean;\n}\n\nexport type TriviaPayload = AvailableAnswers & CanAnswer;\n\nexport const triviaPlayerReducer = sharedTriviaPlayerReducer(TriviaName);\n","import { combineReducers } from \"redux\";\nimport { triviaPlayerReducer } from \"./triviaPlayerReducer\";\nimport { TriviaState } from \"games/shared/Poll/types/State\";\nimport { triviaPresenterReducer } from \"./triviaPresenterReducer\";\n\nexport const triviaReducer = combineReducers({\n player: triviaPlayerReducer,\n presenter: triviaPresenterReducer,\n});\n","import { createReceiveReducer } from \"store/actionHelpers\";\nimport { Name } from \"./presenterReducer\";\nimport { Category } from \"./presenterReducer\";\n\nexport type RetrospectiveParticipantState = {\n categories: Category[];\n};\n\nexport type PayloadFromPresenter = {\n categories: Category[];\n};\n\nexport const participantReducer = createReceiveReducer<\n PayloadFromPresenter,\n RetrospectiveParticipantState\n>(\n Name,\n { categories: [] },\n (_, action) => {\n const result = {\n categories: [...action.payload.categories],\n };\n console.log(\"Received: \", action, \"Returning: \", result);\n return result;\n },\n \"client\"\n);\n","import { combineReducers } from \"@reduxjs/toolkit\";\nimport {\n presenterReducer,\n RetrospectivePresenterState,\n} from \"./presenterReducer\";\nimport {\n participantReducer,\n RetrospectiveParticipantState,\n} from \"./participantReducer\";\n\nexport type RetrospectiveState = {\n participant: RetrospectiveParticipantState;\n presenter: RetrospectivePresenterState;\n};\n\nexport const retrospectiveReducer = combineReducers({\n participant: participantReducer,\n presenter: presenterReducer,\n});\n","import { combineReducers } from \"redux\";\nimport { connectionReducer } from \"./connection/reducers\";\nimport { userReducer } from \"./user/reducers\";\nimport { RootState, GamesState } from \"./RootState\";\nimport { lobbyReducer } from \"./lobby/reducers\";\nimport { shellReducer } from \"./shell/reducers\";\nimport { yesNoMaybeReducer } from \"games/YesNoMaybe/YesNoMaybeReducer\";\nimport { doggosVsKittehsReducer } from \"games/DoggosVsKittehs/DoggosVsKittehsReducer\";\nimport { buzzerReducer } from \"games/Buzzer/BuzzerReducer\";\nimport { splatReducer } from \"games/Splat/SplatReducer\";\nimport { pongReducer } from \"games/Pong/PongReducer\";\nimport { ideaWallReducer } from \"games/IdeaWall/IdeaWallReducer\";\nimport { pollReducer } from \"games/Poll/reducers/pollReducer\";\nimport { triviaReducer } from \"games/Trivia/reducers/triviaReducer\";\nimport { namePickerReducer } from \"games/NamePicker/NamePickerReducer\";\nimport { broadcastReducer } from \"games/Broadcast/BroadcastReducer\";\nimport { reactionReducer } from \"games/Reaction/reactionReducer\";\nimport { retrospectiveReducer } from \"games/Retrospective/reducer\";\nimport { fistOfFiveReducer } from \"games/FistOfFive/reducer\";\n\nconst gamesReducer = combineReducers({\n yesnomaybe: yesNoMaybeReducer,\n doggosVsKittehs: doggosVsKittehsReducer,\n buzzer: buzzerReducer,\n splat: splatReducer,\n pong: pongReducer,\n ideawall: ideaWallReducer,\n poll: pollReducer,\n trivia: triviaReducer,\n namePicker: namePickerReducer,\n broadcast: broadcastReducer,\n reaction: reactionReducer,\n retrospective: retrospectiveReducer,\n fistOfFive: fistOfFiveReducer,\n});\n\nexport const rootReducer = combineReducers({\n connection: connectionReducer,\n user: userReducer,\n lobby: lobbyReducer,\n shell: shellReducer,\n games: gamesReducer,\n});\n","import {\n SET_CONNECTION_STATUS,\n ConnectionActionTypes,\n CONNECTION_CONNECT,\n CONNECTION_RECONNECT,\n} from \"./types\";\nimport { ConnectionStatus } from \"../../ConnectionStatus\";\n\nexport function updateConnectionStatus(\n status: ConnectionStatus\n): ConnectionActionTypes {\n return { type: SET_CONNECTION_STATUS, status };\n}\n\nexport function connectionConnect(lobbyId?: string): ConnectionActionTypes {\n return { type: CONNECTION_CONNECT, lobbyId };\n}\n\nexport function connectionReconnect(): ConnectionActionTypes {\n return { type: CONNECTION_RECONNECT };\n}\n","import { MiddlewareAPI, Dispatch, AnyAction } from \"@reduxjs/toolkit\";\nimport { HubConnection } from \"@microsoft/signalr\";\nimport {\n CONNECTION_CONNECT,\n SET_CONNECTION_STATUS,\n ConnectionActionTypes,\n ReconnectPayload,\n} from \"./connection/types\";\nimport {\n updateConnectionStatus,\n connectionConnect,\n} from \"./connection/actions\";\nimport { ConnectionStatus } from \"../ConnectionStatus\";\nimport {\n setLobby,\n clearLobby,\n playerJoinedLobby,\n playerLeftLobby,\n setLobbyGame,\n setLobbyPlayers,\n joinLobby,\n} from \"./lobby/actions\";\nimport { setUser } from \"./user/actions\";\nimport history from \"../history\";\nimport {\n CLEAR_LOBBY,\n SET_LOBBY_GAME,\n START_NEW_GAME,\n JOIN_LOBBY,\n CLOSE_LOBBY,\n CREATE_LOBBY,\n GAME_MESSAGE_CLIENT,\n GAME_MESSAGE_PRESENTER,\n LobbyActionTypes,\n} from \"./lobby/types\";\nimport { SET_USER_NAME, UserActionTypes } from \"./user/types\";\nimport { goToDefaultUrl, setMenuItems } from \"./shell/actions\";\nimport { GO_TO_DEFAULT_URL, ShellActionTypes } from \"./shell/types\";\nimport { RootState } from \"./RootState\";\n\nconst navigateTo = (path: string) => {\n console.log(`Navigating to ${path}`);\n history.push(path);\n};\n\nexport const onReconnect = (\n getState: () => RootState,\n dispatch: Dispatch\n) => (response: ReconnectPayload) => {\n const user = getState().user;\n const { joiningLobbyId, isPresenter } = getState().lobby;\n if (getState().connection.status !== ConnectionStatus.Connected) {\n dispatch(updateConnectionStatus(ConnectionStatus.Connected));\n }\n if (\n !joiningLobbyId ||\n joiningLobbyId.toLowerCase() === response.lobbyId.toLowerCase()\n ) {\n if (!user.isRegistered && !isPresenter && !response.isRegistered) {\n dispatch(goToDefaultUrl());\n } else {\n dispatch(\n setLobby(\n response.lobbyId,\n response.lobbyName,\n response.isPresenter,\n response.players,\n response.currentGame\n )\n );\n dispatch(\n setUser({\n id: response.playerId,\n name: response.playerName,\n })\n );\n\n if (response.currentGame) {\n dispatch(setLobbyGame(response.currentGame));\n } else {\n dispatch(goToDefaultUrl());\n }\n }\n }\n};\n\nexport const SignalRMiddleware = (connectionFactory: () => HubConnection) => {\n const connectionRetrySeconds = [0, 1, 4, 9, 16, 25, 36, 49];\n let connectionTimeout = 0;\n const connection = connectionFactory();\n connection.keepAliveIntervalInMilliseconds = 2000;\n const bumpConnectionTimeout = () => {\n connectionTimeout = connectionRetrySeconds.filter(\n (s) => s > connectionTimeout\n )[0];\n if (!connectionTimeout)\n connectionTimeout =\n connectionRetrySeconds[connectionRetrySeconds.length - 1];\n };\n\n return ({ getState, dispatch }: MiddlewareAPI) => {\n connection.on(\"reconnect\", onReconnect(getState, dispatch));\n connection.on(\"joined\", (user) => {\n dispatch(playerJoinedLobby(user));\n });\n connection.on(\"left\", (user) => {\n dispatch(playerLeftLobby(user));\n });\n connection.on(\"players\", (players) => {\n dispatch(setLobbyPlayers(players));\n });\n connection.onclose(() => {\n dispatch(updateConnectionStatus(ConnectionStatus.NotConnected));\n });\n connection.on(\"closelobby\", () => {\n dispatch(clearLobby());\n });\n connection.on(\"connected\", () => {\n dispatch(updateConnectionStatus(ConnectionStatus.Connected));\n });\n connection.on(\"newgame\", (name) => {\n connection.off(\"gameMessage\");\n dispatch(setMenuItems([]));\n dispatch(setLobbyGame(name));\n });\n const invoke = (methodName: string, ...params: any[]) => {\n connection.invoke(methodName, ...params).catch((err) => console.log(err));\n };\n return (next: Dispatch) => (\n action:\n | LobbyActionTypes\n | ConnectionActionTypes\n | UserActionTypes\n | ShellActionTypes\n ) => {\n switch (action.type) {\n case CLEAR_LOBBY: {\n navigateTo(\"/lobby-closed\");\n break;\n }\n case START_NEW_GAME: {\n invoke(\"newGame\", action.name);\n break;\n }\n case SET_LOBBY_GAME: {\n const isPresenter = getState().lobby.isPresenter;\n connection.off(\"gameMessage\");\n connection.on(\"gameMessage\", (args: any) => {\n dispatch({\n type: `${action.game}-${\n isPresenter ? \"presenter\" : \"client\"\n }-receive-game-message`,\n payload: args,\n });\n });\n const value = next(action);\n dispatch(goToDefaultUrl());\n return value;\n }\n case CONNECTION_CONNECT: {\n setTimeout(() => {\n if (\n getState().connection.status === ConnectionStatus.NotConnected\n ) {\n bumpConnectionTimeout();\n dispatch(updateConnectionStatus(ConnectionStatus.Pending));\n connection\n .start()\n .then(() => {\n connectionTimeout = 0;\n connection\n .invoke(\"connect\", getState().user, action.lobbyId)\n .catch(() => {\n dispatch(connectionConnect(action.lobbyId));\n });\n })\n .catch((err) => {\n dispatch(\n updateConnectionStatus(ConnectionStatus.NotConnected)\n );\n dispatch(connectionConnect(action.lobbyId));\n return console.error(err.toString());\n });\n }\n }, connectionTimeout * 1000);\n break;\n }\n case SET_CONNECTION_STATUS: {\n const value = next(action);\n switch (action.status) {\n case ConnectionStatus.NotConnected:\n dispatch(connectionConnect());\n break;\n case ConnectionStatus.Connected: {\n const { lobby } = getState();\n if (lobby.joiningLobbyId) {\n dispatch(joinLobby(lobby.joiningLobbyId));\n } else if (lobby.id) dispatch(joinLobby(lobby.id));\n else dispatch(goToDefaultUrl());\n break;\n }\n }\n return value;\n }\n case SET_USER_NAME: {\n const value = next(action);\n const { user, lobby } = getState();\n invoke(\"connectToLobby\", user, lobby.id || lobby.joiningLobbyId);\n return value;\n }\n case JOIN_LOBBY: {\n if (getState().connection.status === ConnectionStatus.Connected) {\n invoke(\"connectToLobby\", getState().user, action.id);\n }\n break;\n }\n case CLOSE_LOBBY: {\n invoke(\"closelobby\");\n break;\n }\n case CREATE_LOBBY: {\n invoke(\"createLobby\", action.name, getState().user);\n break;\n }\n case GAME_MESSAGE_PRESENTER: {\n const payload = JSON.stringify({ admin: action.message });\n invoke(\"hubMessage\", payload);\n break;\n }\n case GAME_MESSAGE_CLIENT: {\n const payload = JSON.stringify({ client: action.message });\n invoke(\"hubMessage\", payload);\n break;\n }\n case GO_TO_DEFAULT_URL: {\n const { user, lobby } = getState();\n if (lobby.joiningLobbyId && !user.isRegistered) {\n navigateTo(\"/register\");\n } else {\n const currentGame = getState().lobby.currentGame;\n if (currentGame) {\n navigateTo(\"/game\");\n } else {\n navigateTo(\"/\");\n }\n }\n break;\n }\n }\n return next(action);\n };\n };\n};\n","import { Middleware, MiddlewareAPI, Dispatch } from \"@reduxjs/toolkit\";\n\nexport const LoggerMiddleware: Middleware = ({ getState }: MiddlewareAPI) => (\n next: Dispatch\n) => (action) => {\n console.log(\"will dispatch\", action);\n const returnValue = next(action);\n console.log(\"state after dispatch\", getState());\n return returnValue;\n};\n","import { configureStore, getDefaultMiddleware } from \"@reduxjs/toolkit\";\nimport { rootReducer } from \"./rootReducer\";\nimport { SignalRMiddleware } from \"./SignalRMiddleware\";\nimport { LoggerMiddleware } from \"./LoggerMiddleware\";\nimport { HubConnectionBuilder } from \"@microsoft/signalr\";\n\nconst connectionFactory = () =>\n new HubConnectionBuilder().withUrl(\"/gameHub\").build();\n\nexport function configureAppStore() {\n const store = configureStore({\n reducer: rootReducer,\n middleware: [\n LoggerMiddleware,\n SignalRMiddleware(connectionFactory),\n ...getDefaultMiddleware(),\n ],\n });\n\n // if (process.env.NODE_ENV !== 'production' && module.hot) {\n // module.hot.accept('./reducers', () => store.replaceReducer(rootReducer))\n // }\n\n return store;\n}\n","import React, { Component } from \"react\";\nimport Layout from \"./layout/layouts/Admin\";\nimport { guid } from \"./util/guid\";\nimport history from \"./history\";\nimport { Events } from \"./Events\";\nimport { Provider } from \"react-redux\";\nimport { configureAppStore } from \"./store/configureAppStore\";\nimport { EnhancedStore, AnyAction } from \"@reduxjs/toolkit\";\nimport { RootState } from \"./store/RootState\";\nimport { connectionConnect } from \"./store/connection/actions\";\nimport { setUser } from \"./store/user/actions\";\nimport { useSelector } from \"./store/useSelector\";\nimport { Player } from \"Player\";\n\ntype AppState = {\n user: Player;\n lobby?: AppLobby;\n players: Player[];\n menuItems: JSX.Element[];\n currentGame?: string;\n isPresenter: boolean;\n};\n\ntype AppLobby = {\n name: string;\n id: string;\n};\n\nexport default class App extends Component<{}, AppState> {\n displayName = App.name;\n private isDebug = false;\n private myStorage: Storage;\n\n private user: Player;\n private store: EnhancedStore;\n\n constructor(props: any, context: any) {\n super(props, context);\n\n this.isDebug = true;\n\n this.myStorage = window.sessionStorage;\n\n this.user = this.getUser();\n this.state = {\n user: this.user,\n isPresenter: false,\n menuItems: [],\n players: [],\n };\n\n this.store = configureAppStore();\n\n this.store.dispatch(setUser(this.user));\n\n window.onresize = () => Events.emit(\"onresize\");\n\n this.store.dispatch(connectionConnect());\n }\n\n private getUser() {\n if (this.myStorage) {\n const raw = this.myStorage.getItem(\"user\");\n if (raw) {\n try {\n const user = JSON.parse(raw);\n console.log(\"User retrieved\", user);\n return user;\n } catch {\n this.debug(\"Could not parse user\");\n }\n }\n }\n\n const user = { id: guid() };\n if (this.myStorage) this.myStorage.setItem(\"user\", JSON.stringify(user));\n\n return user;\n }\n\n debug(...a: any[]) {\n if (this.isDebug) console.log(\"[app]\", ...a);\n }\n\n getCurrentLocation() {\n return history.location || window.location;\n }\n\n setMenuItems = (items: JSX.Element[]) => {\n this.setState({ menuItems: items });\n };\n\n render() {\n return (\n \n \n \n );\n }\n}\n\nconst Main = () => {\n const lobby = useSelector((state) => state.lobby);\n\n return (\n \n );\n};\n","import \"./layout/assets/css/material-dashboard-react.css?v=1.8.0\";\nimport React from \"react\";\nimport ReactDOM from \"react-dom\";\nimport { Router } from \"react-router-dom\";\nimport App from \"./App\";\nimport history from \"./history\";\nimport ReactAI from \"./app-insights-deprecated\";\n\nconst baseUrl = document.getElementsByTagName(\"base\")[0].getAttribute(\"href\");\nconst rootElement = document.getElementById(\"root\");\n\nReactAI.init({ instrumentationKey: \"appInsightsKey\" }, history);\n\nReactDOM.render(\n \n \n ,\n rootElement\n);\n\n//registerServiceWorker();\n"],"sourceRoot":""}