aboutsummaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/account.tsx33
-rw-r--r--components/dialogBox.tsx40
-rw-r--r--components/footer.tsx52
-rw-r--r--components/gameBar.tsx148
-rw-r--r--components/gameSettings.tsx358
-rw-r--r--components/globalState.tsx6
-rw-r--r--components/logo.tsx29
-rw-r--r--components/navbar.tsx192
-rw-r--r--components/notificationsArea.tsx254
-rw-r--r--components/page.tsx48
-rw-r--r--components/preferencesContext.tsx66
-rw-r--r--components/recentGames.tsx123
-rw-r--r--components/socketContext.tsx11
-rw-r--r--components/toast.tsx192
-rw-r--r--components/ui.tsx415
-rw-r--r--components/voerBord.tsx95
16 files changed, 1176 insertions, 886 deletions
diff --git a/components/account.tsx b/components/account.tsx
index 7b825cd..f24135f 100644
--- a/components/account.tsx
+++ b/components/account.tsx
@@ -1,4 +1,5 @@
-var dummy = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2P4z/j/PwAHAQL/gXZXNQAAAABJRU5ErkJggg==";
+var dummy =
+ 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQYV2P4z/j/PwAHAQL/gXZXNQAAAABJRU5ErkJggg==';
export function AccountAvatar(props: {
size: number;
@@ -8,24 +9,24 @@ export function AccountAvatar(props: {
id?: string;
}) {
- var image = "";
- image += "/api/user/avatar";
- if (typeof props.id === "string") {
- if (!props.id) image = "";
+ var image = '';
+ image += '/api/user/avatar';
+ if (typeof props.id === 'string') {
+ if (!props.id) image = '';
else image += `?id=${props.id}`;
}
if (props.dummy) image = dummy;
- return <div style={{
- width: props.size,
- height: props.size,
- backgroundColor: props.fallbackFill || "var(--background)",
- backgroundImage: `url(${image})`,
- backgroundSize: "cover",
- display: "inline-block",
- borderRadius: props.size / 2 * Number(props.round || 0)
- }}/>;
+ return <div
+ style={{
+ width: props.size,
+ height: props.size,
+ backgroundColor: props.fallbackFill || 'var(--background)',
+ backgroundImage: `url(${image})`,
+ backgroundSize: 'cover',
+ display: 'inline-block',
+ borderRadius: props.size / 2 * Number(props.round || 0),
+ }}
+ />;
}
-
-
diff --git a/components/dialogBox.tsx b/components/dialogBox.tsx
index 5ef5c3f..7abbded 100644
--- a/components/dialogBox.tsx
+++ b/components/dialogBox.tsx
@@ -1,4 +1,4 @@
-import { ReactNode, CSSProperties } from 'react';
+import { CSSProperties, ReactNode } from 'react';
import { Vierkant } from './ui';
@@ -10,24 +10,30 @@ export function DialogBox(props: {
style?: CSSProperties;
onclick?: () => void;
}) {
- return <Vierkant style={{
- position: "fixed",
- top: "50%", left: "50%",
- transform: "translate(-50%, -50%)",
- boxShadow: "0 8px 32px -5px #0007",
- width: 392,
- ...props.style
- }}>
+ return <Vierkant
+ style={{
+ position: 'fixed',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ boxShadow: '0 8px 32px -5px #0007',
+ width: 392,
+ ...props.style,
+ }}
+ >
<h2 style={{ marginBottom: 24 }}>{props.title}</h2>
<span onClick={props.onclick}>
- <CancelIcon style={{
- position: "absolute",
- top: 25, right: 25,
- color: "var(--text)",
- opacity: .85,
- cursor: "pointer"
- }}/>
+ <CancelIcon
+ style={{
+ position: 'absolute',
+ top: 25,
+ right: 25,
+ color: 'var(--text)',
+ opacity: .85,
+ cursor: 'pointer',
+ }}
+ />
</span>
{props.children}
- </Vierkant>
+ </Vierkant>;
}
diff --git a/components/footer.tsx b/components/footer.tsx
index 2d84a1c..36ebef3 100644
--- a/components/footer.tsx
+++ b/components/footer.tsx
@@ -1,49 +1,49 @@
-import { LogoDark } from "../components/logo";
import { ReactNode } from 'react';
+import { LogoDark } from '../components/logo';
-import Home from '@material-ui/icons/Home';
-import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
+import ExitToAppOutlinedIcon from '@material-ui/icons/ExitToAppOutlined';
import ExtensionIcon from '@material-ui/icons/Extension';
+import GitHubIcon from '@material-ui/icons/GitHub';
+import Home from '@material-ui/icons/Home';
+import LockIcon from '@material-ui/icons/Lock';
+import PersonIcon from '@material-ui/icons/Person';
import SearchIcon from '@material-ui/icons/Search';
import SettingsIcon from '@material-ui/icons/Settings';
-import PersonIcon from '@material-ui/icons/Person';
-import ExitToAppOutlinedIcon from '@material-ui/icons/ExitToAppOutlined';
-import LockIcon from '@material-ui/icons/Lock';
-import GitHubIcon from '@material-ui/icons/GitHub';
+import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
function PageLink(props: {
icon: ReactNode;
href: string;
children: string;
}) {
- return <a href={props.href} className="pageLink">
+ return <a href={props.href} className='pageLink'>
{props.icon}
<span>{props.children}</span>
- </a>
+ </a>;
}
export function Footer() {
- return <div className="footer">
- <div className="header">
- <LogoDark/>
+ return <div className='footer'>
+ <div className='header'>
+ <LogoDark />
<h2>4 op een rij</h2>
</div>
- <div className="content">
- <div className="column">
- <PageLink icon={<Home/>} href="/" children="Home"/>
- <PageLink icon={<VideogameAssetIcon/>} href="/game" children="Spelen"/>
- <PageLink icon={<ExtensionIcon/>} href="/" children="Puzzels"/>
- <PageLink icon={<SearchIcon/>} href="/search" children="Zoeken"/>
+ <div className='content'>
+ <div className='column'>
+ <PageLink icon={<Home />} href='/' children='Home' />
+ <PageLink icon={<VideogameAssetIcon />} href='/game' children='Spelen' />
+ <PageLink icon={<ExtensionIcon />} href='/' children='Puzzels' />
+ <PageLink icon={<SearchIcon />} href='/search' children='Zoeken' />
</div>
- <div className="column">
- <PageLink icon={<LockIcon/>} href="/privacy" children="Privacy"/>
- <PageLink icon={<GitHubIcon/>} href="https://github.com/lonkaars/connect-4" children="Broncode"/>
+ <div className='column'>
+ <PageLink icon={<LockIcon />} href='/privacy' children='Privacy' />
+ <PageLink icon={<GitHubIcon />} href='https://github.com/lonkaars/connect-4' children='Broncode' />
</div>
- <div className="column">
- <PageLink icon={<SettingsIcon/>} href="/settings" children="Instellingen"/>
- <PageLink icon={<PersonIcon/>} href="/user" children="Profiel"/>
- <PageLink icon={<ExitToAppOutlinedIcon/>} href="/logout" children="Uitloggen"/>
+ <div className='column'>
+ <PageLink icon={<SettingsIcon />} href='/settings' children='Instellingen' />
+ <PageLink icon={<PersonIcon />} href='/user' children='Profiel' />
+ <PageLink icon={<ExitToAppOutlinedIcon />} href='/logout' children='Uitloggen' />
</div>
</div>
- </div>
+ </div>;
}
diff --git a/components/gameBar.tsx b/components/gameBar.tsx
index 67712e2..0d7d4d9 100644
--- a/components/gameBar.tsx
+++ b/components/gameBar.tsx
@@ -1,30 +1,35 @@
import { CSSProperties, ReactNode } from 'react';
-import { Vierkant, Bubble } from './ui';
+import { Bubble, Vierkant } from './ui';
-import SettingsRoundedIcon from '@material-ui/icons/SettingsRounded';
import ExitToAppRoundedIcon from '@material-ui/icons/ExitToAppRounded';
-import NavigateNextRoundedIcon from '@material-ui/icons/NavigateNextRounded';
import NavigateBeforeRoundedIcon from '@material-ui/icons/NavigateBeforeRounded';
+import NavigateNextRoundedIcon from '@material-ui/icons/NavigateNextRounded';
+import SettingsRoundedIcon from '@material-ui/icons/SettingsRounded';
function GameBarModule(props: {
children?: ReactNode;
onclick?: () => void;
}) {
- return <Vierkant style={{
- backgroundColor: "var(--background-alt)",
- padding: 12,
- borderRadius: 6,
- margin: 0,
- verticalAlign: "top",
- cursor: props.onclick ? "pointer" : "default"
- }} onclick={props.onclick}>{props.children}</Vierkant>
+ return <Vierkant
+ style={{
+ backgroundColor: 'var(--background-alt)',
+ padding: 12,
+ borderRadius: 6,
+ margin: 0,
+ verticalAlign: 'top',
+ cursor: props.onclick ? 'pointer' : 'default',
+ }}
+ onclick={props.onclick}
+ >
+ {props.children}
+ </Vierkant>;
}
-var GameBarSpacer = () => <div style={{ width: 8, display: "inline-block" }}></div>;
+var GameBarSpacer = () => <div style={{ width: 8, display: 'inline-block' }}></div>;
var GameBarAlignStyle: CSSProperties = {
- display: "inline-block"
-}
+ display: 'inline-block',
+};
export function GameBar(props: {
turn: boolean;
@@ -32,64 +37,85 @@ export function GameBar(props: {
active: boolean;
resignFunction: () => void;
}) {
- return <Vierkant className="gameBar" style={{
- padding: 8,
- width: "calc(100% - 12px)"
- }}>
- <div style={{ gridAutoColumns: "auto" }}>
- <div style={{ ...GameBarAlignStyle, float: "left" }}>
- <div style={{
- width: 32, height: 32,
- margin: 8,
- backgroundColor: props.turn ? "var(--disk-b)" : "var(--disk-a)",
- borderRadius: 16,
- display: "inline-block"
- }}/>
- <h2 style={{
- fontSize: 20,
- margin: 12,
- verticalAlign: "top",
- display: "inline-block"
- }}>{
- !props.active ? "Wachten op tegenstander..." :
- props.turn == props.player1 ?
- "Jouw beurt" : "Tegenstander"
- }</h2>
+ return <Vierkant
+ className='gameBar'
+ style={{
+ padding: 8,
+ width: 'calc(100% - 12px)',
+ }}
+ >
+ <div style={{ gridAutoColumns: 'auto' }}>
+ <div style={{ ...GameBarAlignStyle, float: 'left' }}>
+ <div
+ style={{
+ width: 32,
+ height: 32,
+ margin: 8,
+ backgroundColor: props.turn ? 'var(--disk-b)' : 'var(--disk-a)',
+ borderRadius: 16,
+ display: 'inline-block',
+ }}
+ />
+ <h2
+ style={{
+ fontSize: 20,
+ margin: 12,
+ verticalAlign: 'top',
+ display: 'inline-block',
+ }}
+ >
+ {!props.active
+ ? 'Wachten op tegenstander...'
+ : props.turn == props.player1
+ ? 'Jouw beurt'
+ : 'Tegenstander'}
+ </h2>
</div>
- <div style={{
- ...GameBarAlignStyle,
- position: "absolute",
- top: "50%", left: "50%",
- transform: "translate(-50%, -50%)"
- }}>
- <span style={{
- color: "var(--text)",
- fontSize: 20,
- opacity: .75 - .75
- }}>0-0</span>
+ <div
+ style={{
+ ...GameBarAlignStyle,
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ }}
+ >
+ <span
+ style={{
+ color: 'var(--text)',
+ fontSize: 20,
+ opacity: .75 - .75,
+ }}
+ >
+ 0-0
+ </span>
</div>
- <div style={{ ...GameBarAlignStyle, float: "right" }}>
+ <div style={{ ...GameBarAlignStyle, float: 'right' }}>
<GameBarModule>
- <SettingsRoundedIcon/>
+ <SettingsRoundedIcon />
</GameBarModule>
- <GameBarSpacer/>
+ <GameBarSpacer />
<GameBarModule>
- <span style={{
- margin: "0 4px",
- fontSize: 20
- }}>00:00</span>
+ <span
+ style={{
+ margin: '0 4px',
+ fontSize: 20,
+ }}
+ >
+ 00:00
+ </span>
</GameBarModule>
- <GameBarSpacer/>
+ <GameBarSpacer />
<GameBarModule onclick={props.resignFunction}>
- <ExitToAppRoundedIcon/>
+ <ExitToAppRoundedIcon />
</GameBarModule>
- <GameBarSpacer/>
+ <GameBarSpacer />
<GameBarModule>
- <NavigateBeforeRoundedIcon/>
+ <NavigateBeforeRoundedIcon />
</GameBarModule>
- <GameBarSpacer/>
+ <GameBarSpacer />
<GameBarModule>
- <NavigateNextRoundedIcon/>
+ <NavigateNextRoundedIcon />
</GameBarModule>
</div>
</div>
diff --git a/components/gameSettings.tsx b/components/gameSettings.tsx
index be45112..f562e5d 100644
--- a/components/gameSettings.tsx
+++ b/components/gameSettings.tsx
@@ -1,17 +1,16 @@
-import { ReactNode, Component, CSSProperties } from 'react';
import axios from 'axios';
+import { Component, CSSProperties, ReactNode } from 'react';
-import { Button, Vierkant, CheckBox, Input } from './ui';
-import { DialogBox } from './dialogBox';
import { ruleset, userPreferences } from '../api/api';
+import { DialogBox } from './dialogBox';
+import { Button, CheckBox, Input, Vierkant } from './ui';
import BuildOutlinedIcon from '@material-ui/icons/BuildOutlined';
-
type CurrentGameSettingsStateType = {
editGameRulesDialogVisible: boolean;
ruleset: ruleset;
-}
+};
export class CurrentGameSettings extends Component {
state: CurrentGameSettingsStateType = {
@@ -19,25 +18,25 @@ export class CurrentGameSettings extends Component {
ruleset: {
timelimit: {
enabled: false,
- shared: false
+ shared: false,
},
- ranked: false
- }
- }
+ ranked: false,
+ },
+ };
constructor(props: {}) {
super(props);
- if (typeof window === "undefined") return; // return if run on server
+ if (typeof window === 'undefined') return; // return if run on server
axios.request<userPreferences>({
- method: "get",
+ method: 'get',
url: `/api/user/preferences`,
- headers: {"content-type": "application/json"}
+ headers: { 'content-type': 'application/json' },
})
- //FIXME: this assumes the request ruleset has all properties of a ruleset
- .then(request => this.setState({ ruleset: request.data.ruleset || this.state.ruleset }))
- .catch(() => {});
+ // FIXME: this assumes the request ruleset has all properties of a ruleset
+ .then(request => this.setState({ ruleset: request.data.ruleset || this.state.ruleset }))
+ .catch(() => {});
}
showEditGameRules = () => this.setState({ editGameRulesDialogVisible: true });
@@ -45,51 +44,67 @@ export class CurrentGameSettings extends Component {
setGameRules = (newRules: ruleset) => this.setState({ ruleset: newRules });
render() {
- var timelimit_str = this.state.ruleset.timelimit.enabled ?
- `${this.state.ruleset.timelimit.minutes}m${this.state.ruleset.timelimit.seconds}s plus ${this.state.ruleset.timelimit.addmove}` :
- "Geen tijdslimiet"
- var ranked_str = this.state.ruleset.ranked ?
- "Gerangschikt" :
- "Niet gerangschikt"
- return <div style={{
- position: "relative",
- height: 80,
- overflow: "visible",
- zIndex: 1
- }}>
- <p style={{
- opacity: .75,
- fontStyle: "italic",
- userSelect: "none",
- position: "absolute",
- top: "50%",
- left: 0,
- transform: "translateY(-50%)"
- }}>
- {timelimit_str}<br/>
+ var timelimit_str = this.state.ruleset.timelimit.enabled
+ ? `${this.state.ruleset.timelimit.minutes}m${this.state.ruleset.timelimit.seconds}s plus ${this.state.ruleset.timelimit.addmove}`
+ : 'Geen tijdslimiet';
+ var ranked_str = this.state.ruleset.ranked
+ ? 'Gerangschikt'
+ : 'Niet gerangschikt';
+ return <div
+ style={{
+ position: 'relative',
+ height: 80,
+ overflow: 'visible',
+ zIndex: 1,
+ }}
+ >
+ <p
+ style={{
+ opacity: .75,
+ fontStyle: 'italic',
+ userSelect: 'none',
+ position: 'absolute',
+ top: '50%',
+ left: 0,
+ transform: 'translateY(-50%)',
+ }}
+ >
+ {timelimit_str}
+ <br />
{ranked_str}
</p>
- <Button style={{
- width: 150,
- position: "absolute",
- top: "50%",
- right: 0,
- transform: "translateY(-50%)"
- }} onclick={this.showEditGameRules}>
- <BuildOutlinedIcon style={{ fontSize: 48 }}/>
- <span style={{
- fontWeight: 600,
- position: "absolute",
- right: 24,
- top: "50%",
- width: 85,
- verticalAlign: "middle",
- textAlign: "center",
- transform: "translateY(-50%)",
- userSelect: "none"
- }}>Spelregels aanpassen</span>
+ <Button
+ style={{
+ width: 150,
+ position: 'absolute',
+ top: '50%',
+ right: 0,
+ transform: 'translateY(-50%)',
+ }}
+ onclick={this.showEditGameRules}
+ >
+ <BuildOutlinedIcon style={{ fontSize: 48 }} />
+ <span
+ style={{
+ fontWeight: 600,
+ position: 'absolute',
+ right: 24,
+ top: '50%',
+ width: 85,
+ verticalAlign: 'middle',
+ textAlign: 'center',
+ transform: 'translateY(-50%)',
+ userSelect: 'none',
+ }}
+ >
+ Spelregels aanpassen
+ </span>
</Button>
- <EditGameSettings parentState={this.state} hideEditGameRules={this.hideEditGameRules} setGameRules={this.setGameRules}/>
+ <EditGameSettings
+ parentState={this.state}
+ hideEditGameRules={this.hideEditGameRules}
+ setGameRules={this.setGameRules}
+ />
</div>;
}
}
@@ -101,25 +116,36 @@ function GameSettingsSection(props: {
noMarginBottom?: boolean;
id: string;
}) {
- return <Vierkant id={props.id} style={{
- backgroundColor: "var(--background-alt)",
- width: "100%",
- padding: 16,
- margin: 0,
- marginBottom: props.noMarginBottom ? 0 : 24
- }}>
- <span style={{
- verticalAlign: "top",
- fontSize: 14,
- fontWeight: 600
- }}>{props.title}</span>
- <CheckBox state={props.state} id={`${props.id}_enabled`} style={{
- verticalAlign: "top",
- float: "right",
- margin: -3
- }}/>
- <div>{props.children}</div>
- </Vierkant>
+ return <Vierkant
+ id={props.id}
+ style={{
+ backgroundColor: 'var(--background-alt)',
+ width: '100%',
+ padding: 16,
+ margin: 0,
+ marginBottom: props.noMarginBottom ? 0 : 24,
+ }}
+ >
+ <span
+ style={{
+ verticalAlign: 'top',
+ fontSize: 14,
+ fontWeight: 600,
+ }}
+ >
+ {props.title}
+ </span>
+ <CheckBox
+ state={props.state}
+ id={`${props.id}_enabled`}
+ style={{
+ verticalAlign: 'top',
+ float: 'right',
+ margin: -3,
+ }}
+ />
+ <div>{props.children}</div>
+ </Vierkant>;
}
function GameRule(props: {
@@ -127,15 +153,17 @@ function GameRule(props: {
description: string;
style?: CSSProperties;
}) {
- return <div style={{
- backgroundColor: "var(--page-background)",
- borderRadius: 8,
- padding: "16px 0",
- textAlign: "center",
- ...props.style
- }}>
- <h1 style={{ color: "var(--disk-a)", fontSize: 42 }}>{props.title}</h1>
- <p style={{ color: "var(--text-alt)", maxWidth: 250, margin: "0 auto" }}>{props.description}</p>
+ return <div
+ style={{
+ backgroundColor: 'var(--page-background)',
+ borderRadius: 8,
+ padding: '16px 0',
+ textAlign: 'center',
+ ...props.style,
+ }}
+ >
+ <h1 style={{ color: 'var(--disk-a)', fontSize: 42 }}>{props.title}</h1>
+ <p style={{ color: 'var(--text-alt)', maxWidth: 250, margin: '0 auto' }}>{props.description}</p>
</div>;
}
@@ -146,66 +174,114 @@ type editGameSettingsProps = {
};
export class EditGameSettings extends Component<editGameSettingsProps> {
- render () {
- return <DialogBox title="Spelregels aanpassen" style={{
- margin: 0,
- display: this.props.parentState.editGameRulesDialogVisible ? "block" : "none"
- }} onclick={this.props.hideEditGameRules}>
- <div style={{
- marginTop: 24,
- maxHeight: 500,
- overflowY: "scroll",
- borderRadius: 8,
- }}>
- <GameSettingsSection title="Tijdslimiet" state={this.props.parentState.ruleset.timelimit.enabled} id="timelimit">
- <div style={{
- display: "grid",
- gridTemplateColumns: "1fr 1fr 1fr",
- gridGap: 16,
- margin: "16px 0"
- }}>
- <Input id="timelimit_minutes" type="number" label="min" min={0} max={60} value={this.props.parentState.ruleset.timelimit.minutes}/>
- <Input id="timelimit_seconds" type="number" label="sec" min={0} max={60} value={this.props.parentState.ruleset.timelimit.seconds}/>
- <Input id="timelimit_addmove" type="number" label="plus" min={0} value={this.props.parentState.ruleset.timelimit.addmove}/>
+ render() {
+ return <DialogBox
+ title='Spelregels aanpassen'
+ style={{
+ margin: 0,
+ display: this.props.parentState.editGameRulesDialogVisible ? 'block' : 'none',
+ }}
+ onclick={this.props.hideEditGameRules}
+ >
+ <div
+ style={{
+ marginTop: 24,
+ maxHeight: 500,
+ overflowY: 'scroll',
+ borderRadius: 8,
+ }}
+ >
+ <GameSettingsSection
+ title='Tijdslimiet'
+ state={this.props.parentState.ruleset.timelimit.enabled}
+ id='timelimit'
+ >
+ <div
+ style={{
+ display: 'grid',
+ gridTemplateColumns: '1fr 1fr 1fr',
+ gridGap: 16,
+ margin: '16px 0',
+ }}
+ >
+ <Input
+ id='timelimit_minutes'
+ type='number'
+ label='min'
+ min={0}
+ max={60}
+ value={this.props.parentState.ruleset.timelimit.minutes}
+ />
+ <Input
+ id='timelimit_seconds'
+ type='number'
+ label='sec'
+ min={0}
+ max={60}
+ value={this.props.parentState.ruleset.timelimit.seconds}
+ />
+ <Input
+ id='timelimit_addmove'
+ type='number'
+ label='plus'
+ min={0}
+ value={this.props.parentState.ruleset.timelimit.addmove}
+ />
</div>
- <CheckBox id="timelimit_shared" state={this.props.parentState.ruleset.timelimit.shared}/>
- <span style={{
- verticalAlign: "super",
- marginLeft: 4
- }}>Timer gebruiken voor bijde spelers</span>
+ <CheckBox id='timelimit_shared' state={this.props.parentState.ruleset.timelimit.shared} />
+ <span
+ style={{
+ verticalAlign: 'super',
+ marginLeft: 4,
+ }}
+ >
+ Timer gebruiken voor bijde spelers
+ </span>
</GameSettingsSection>
- { false && <GameSettingsSection title="Regelset" state={false}>
- <div style={{
- display: "grid",
- gridTemplateColumns: "1fr 1fr",
- gridGap: 16,
- margin: "16px 0"
- }}>
- <GameRule title="+2" description="Extra kolommen"/>
- <GameRule title="+4" description="Extra kolommen"/>
+ {false && <GameSettingsSection title='Regelset' state={false}>
+ <div
+ style={{
+ display: 'grid',
+ gridTemplateColumns: '1fr 1fr',
+ gridGap: 16,
+ margin: '16px 0',
+ }}
+ >
+ <GameRule title='+2' description='Extra kolommen' />
+ <GameRule title='+4' description='Extra kolommen' />
</div>
- <GameRule style={{ marginBottom: 16 }} title="Gravity" description="De zwaartekracht draait soms"/>
- <GameRule title="Flashlight" description="Het veld wordt opgelicht door de vallende fiches"/>
- </GameSettingsSection> }
- <GameSettingsSection title="Gerangschikt spel" state={this.props.parentState.ruleset.ranked} id="ranked" noMarginBottom/>
+ <GameRule style={{ marginBottom: 16 }} title='Gravity' description='De zwaartekracht draait soms' />
+ <GameRule title='Flashlight' description='Het veld wordt opgelicht door de vallende fiches' />
+ </GameSettingsSection>}
+ <GameSettingsSection
+ title='Gerangschikt spel'
+ state={this.props.parentState.ruleset.ranked}
+ id='ranked'
+ noMarginBottom
+ />
</div>
- <Button style={{
- textAlign: "center",
- marginTop: 24
- }} onclick={() => {
- var rules: ruleset = {
- timelimit: {
- enabled: document.getElementById("timelimit_enabled").classList.contains("on"),
- minutes: Number((document.getElementById("timelimit_minutes") as HTMLInputElement).value),
- seconds: Number((document.getElementById("timelimit_seconds") as HTMLInputElement).value),
- addmove: Number((document.getElementById("timelimit_addmove") as HTMLInputElement).value),
- shared: document.getElementById("timelimit_shared").classList.contains("on"),
- },
- ranked: document.getElementById("ranked_enabled").classList.contains("on")
- }
- this.props.setGameRules(rules);
- this.props.hideEditGameRules();
- }}>Instellingen opslaan</Button>
+ <Button
+ style={{
+ textAlign: 'center',
+ marginTop: 24,
+ }}
+ onclick={() => {
+ var rules: ruleset = {
+ timelimit: {
+ enabled: document.getElementById('timelimit_enabled').classList.contains('on'),
+ minutes: Number((document.getElementById('timelimit_minutes') as HTMLInputElement).value),
+ seconds: Number((document.getElementById('timelimit_seconds') as HTMLInputElement).value),
+ addmove: Number((document.getElementById('timelimit_addmove') as HTMLInputElement).value),
+ shared: document.getElementById('timelimit_shared').classList.contains('on'),
+ },
+ ranked: document.getElementById('ranked_enabled').classList.contains('on'),
+ };
+ this.props.setGameRules(rules);
+ this.props.hideEditGameRules();
+ }}
+ >
+ Instellingen opslaan
+ </Button>
</DialogBox>;
}
}
diff --git a/components/globalState.tsx b/components/globalState.tsx
index 5b01947..0ec7838 100644
--- a/components/globalState.tsx
+++ b/components/globalState.tsx
@@ -6,9 +6,9 @@ type globalState = {
on: boolean;
time: number;
useForBoth: boolean;
- }
+ };
rankedGame: boolean;
- }
-}
+ };
+};
export var GlobalStateContext = React.createContext();
diff --git a/components/logo.tsx b/components/logo.tsx
index b0f358f..e43aa88 100644
--- a/components/logo.tsx
+++ b/components/logo.tsx
@@ -1,12 +1,12 @@
export function LogoDark() {
return (
- <div className="noclick">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <rect width="24" height="24" fill="var(--background)"/>
- <circle cx="6.5" cy="6.5" r="4.5" fill="var(--disk-b)"/>
- <circle cx="6.5" cy="17.5" r="4.5" fill="var(--disk-a)"/>
- <circle cx="17.5" cy="17.5" r="4.5" fill="var(--disk-b)"/>
- <circle cx="17.5" cy="6.5" r="3.5" stroke="var(--text)" strokeWidth="2"/>
+ <div className='noclick'>
+ <svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
+ <rect width='24' height='24' fill='var(--background)' />
+ <circle cx='6.5' cy='6.5' r='4.5' fill='var(--disk-b)' />
+ <circle cx='6.5' cy='17.5' r='4.5' fill='var(--disk-a)' />
+ <circle cx='17.5' cy='17.5' r='4.5' fill='var(--disk-b)' />
+ <circle cx='17.5' cy='6.5' r='3.5' stroke='var(--text)' strokeWidth='2' />
</svg>
</div>
);
@@ -14,15 +14,14 @@ export function LogoDark() {
export function LogoLight() {
return (
- <div className="noclick">
- <svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
- <rect width="24" height="24" fill="var(--page-background)"/>
- <circle cx="6.5" cy="6.5" r="4.5" fill="var(--disk-b)"/>
- <circle cx="6.5" cy="17.5" r="4.5" fill="var(--disk-a)"/>
- <circle cx="17.5" cy="17.5" r="4.5" fill="var(--disk-b)"/>
- <circle cx="17.5" cy="6.5" r="3.5" stroke="var(--background)" strokeWidth="2"/>
+ <div className='noclick'>
+ <svg width='24' height='24' viewBox='0 0 24 24' fill='none' xmlns='http://www.w3.org/2000/svg'>
+ <rect width='24' height='24' fill='var(--page-background)' />
+ <circle cx='6.5' cy='6.5' r='4.5' fill='var(--disk-b)' />
+ <circle cx='6.5' cy='17.5' r='4.5' fill='var(--disk-a)' />
+ <circle cx='17.5' cy='17.5' r='4.5' fill='var(--disk-b)' />
+ <circle cx='17.5' cy='6.5' r='3.5' stroke='var(--background)' strokeWidth='2' />
</svg>
</div>
);
}
-
diff --git a/components/navbar.tsx b/components/navbar.tsx
index d6775ee..70de574 100644
--- a/components/navbar.tsx
+++ b/components/navbar.tsx
@@ -1,121 +1,147 @@
-import { CSSProperties, useEffect, useState, useContext } from "react";
-import axios from "axios";
+import axios from 'axios';
+import { CSSProperties, useContext, useEffect, useState } from 'react';
-import { LogoDark } from "../components/logo";
-import { AccountAvatar } from "./account";
-import { userInfo } from "../api/api";
-import { NotificationsArea } from "./notificationsArea";
-import { SocketContext } from "./socketContext";
+import { userInfo } from '../api/api';
+import { LogoDark } from '../components/logo';
+import { AccountAvatar } from './account';
+import { NotificationsArea } from './notificationsArea';
+import { SocketContext } from './socketContext';
-import Home from '@material-ui/icons/Home';
-import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
import ExtensionIcon from '@material-ui/icons/Extension';
+import Home from '@material-ui/icons/Home';
+import NotificationsIcon from '@material-ui/icons/Notifications';
+import PersonIcon from '@material-ui/icons/Person';
import SearchIcon from '@material-ui/icons/Search';
import SettingsIcon from '@material-ui/icons/Settings';
-import PersonIcon from '@material-ui/icons/Person';
-import NotificationsIcon from '@material-ui/icons/Notifications';
+import VideogameAssetIcon from '@material-ui/icons/VideogameAsset';
var NavBarItemStyle: CSSProperties = {
margin: 12,
marginBottom: 16,
- display: "block"
-}
+ display: 'block',
+};
export function NavBar() {
- var [ loggedIn, setLoggedIn ] = useState(false);
- var [ gotData, setGotData ] = useState(false);
+ var [loggedIn, setLoggedIn] = useState(false);
+ var [gotData, setGotData] = useState(false);
- var [ friendRequests, setFriendRequests ] = useState<Array<userInfo>>(null);
+ var [friendRequests, setFriendRequests] = useState<Array<userInfo>>(null);
- var [ notificationsAreaVisible, setNotificationsAreaVisible ] = useState(false);
- var [ gotNotifications, setGotNotifications ] = useState(false);
+ var [notificationsAreaVisible, setNotificationsAreaVisible] = useState(false);
+ var [gotNotifications, setGotNotifications] = useState(false);
var { io } = useContext(SocketContext);
async function getNotifications() {
- var friendRequestsReq = await axios.request<{ requests: Array<userInfo> }>({
- method: "get",
- url: `/api/social/list/requests`
+ var friendRequestsReq = await axios.request<{ requests: Array<userInfo>; }>({
+ method: 'get',
+ url: `/api/social/list/requests`,
});
setFriendRequests(friendRequestsReq.data.requests);
setGotNotifications(friendRequestsReq.data.requests.length > 0);
}
- useEffect(() => {(async () => {
- if (gotData) return;
- if (typeof window === "undefined") return;
-
- var loggedIn = document.cookie.includes("token");
- setLoggedIn(loggedIn);
-
- if (loggedIn) {
- await getNotifications();
- io.on("incomingFriendRequest", getNotifications);
- io.on("changedRelation", getNotifications);
- }
+ useEffect(() => {
+ (async () => {
+ if (gotData) return;
+ if (typeof window === 'undefined') return;
- setGotData(true);
- })()}, []);
+ var loggedIn = document.cookie.includes('token');
+ setLoggedIn(loggedIn);
- return <div className="navbar" style={{
- width: 48,
- height: "100%",
+ if (loggedIn) {
+ await getNotifications();
+ io.on('incomingFriendRequest', getNotifications);
+ io.on('changedRelation', getNotifications);
+ }
- lineHeight: 0,
+ setGotData(true);
+ })();
+ }, []);
- backgroundColor: "var(--background)",
- display: "inline-block",
+ return <div
+ className='navbar'
+ style={{
+ width: 48,
+ height: '100%',
- position: "fixed",
- top: 0,
- left: 0,
+ lineHeight: 0,
- overflow: "visible",
- whiteSpace: "nowrap",
- zIndex: 2
- }}>
- <div style={NavBarItemStyle}><LogoDark/></div>
- <a href="/" style={NavBarItemStyle}><Home/></a>
- <a href="/game" style={NavBarItemStyle}><VideogameAssetIcon/></a>
- { false && <a href="/" style={NavBarItemStyle}><ExtensionIcon/></a> }
- <a href="/search" style={NavBarItemStyle}><SearchIcon/></a>
+ backgroundColor: 'var(--background)',
+ display: 'inline-block',
- <div style={{
- position: "absolute",
- bottom: -4,
+ position: 'fixed',
+ top: 0,
left: 0,
- backgroundColor: "var(--background)"
- }}>
- { loggedIn && <a style={{
- overflow: "visible",
- position: "relative",
- ...NavBarItemStyle
- }}>
- <div style={{ cursor: "pointer" }} onClick={() => setNotificationsAreaVisible(!notificationsAreaVisible)}>
- <NotificationsIcon/>
- { gotNotifications && <div style={{
- backgroundColor: "var(--disk-a)",
- width: 8, height: 8,
- borderRadius: 4,
- position: "absolute",
- top: 2, right: 2
- }}/> }
+
+ overflow: 'visible',
+ whiteSpace: 'nowrap',
+ zIndex: 2,
+ }}
+ >
+ <div style={NavBarItemStyle}>
+ <LogoDark />
+ </div>
+ <a href='/' style={NavBarItemStyle}>
+ <Home />
+ </a>
+ <a href='/game' style={NavBarItemStyle}>
+ <VideogameAssetIcon />
+ </a>
+ {false && <a href='/' style={NavBarItemStyle}>
+ <ExtensionIcon />
+ </a>}
+ <a href='/search' style={NavBarItemStyle}>
+ <SearchIcon />
+ </a>
+
+ <div
+ style={{
+ position: 'absolute',
+ bottom: -4,
+ left: 0,
+ backgroundColor: 'var(--background)',
+ }}
+ >
+ {loggedIn && <a
+ style={{
+ overflow: 'visible',
+ position: 'relative',
+ ...NavBarItemStyle,
+ }}
+ >
+ <div
+ style={{ cursor: 'pointer' }}
+ onClick={() => setNotificationsAreaVisible(!notificationsAreaVisible)}
+ >
+ <NotificationsIcon />
+ {gotNotifications && <div
+ style={{
+ backgroundColor: 'var(--disk-a)',
+ width: 8,
+ height: 8,
+ borderRadius: 4,
+ position: 'absolute',
+ top: 2,
+ right: 2,
+ }}
+ />}
</div>
<NotificationsArea
visible={notificationsAreaVisible}
friendRequests={friendRequests}
- rerender={getNotifications}/>
- </a> }
- <a href={loggedIn ? "/user" : "/login"} style={NavBarItemStyle}>
- {
- loggedIn ?
- <AccountAvatar size={24} round/> :
- <PersonIcon/>
- }
+ rerender={getNotifications}
+ />
+ </a>}
+ <a href={loggedIn ? '/user' : '/login'} style={NavBarItemStyle}>
+ {loggedIn
+ ? <AccountAvatar size={24} round />
+ : <PersonIcon />}
</a>
- { loggedIn && <a href="/settings" style={NavBarItemStyle}><SettingsIcon/></a> }
+ {loggedIn && <a href='/settings' style={NavBarItemStyle}>
+ <SettingsIcon />
+ </a>}
</div>
- </div>
+ </div>;
}
-
diff --git a/components/notificationsArea.tsx b/components/notificationsArea.tsx
index b427941..9573b72 100644
--- a/components/notificationsArea.tsx
+++ b/components/notificationsArea.tsx
@@ -1,13 +1,13 @@
-import { CSSProperties, ReactNode, useState, useContext, useEffect } from 'react';
import axios from 'axios';
+import { CSSProperties, ReactNode, useContext, useEffect, useState } from 'react';
-import { userInfo, gameInfo } from "../api/api";
-import { AccountAvatar } from "./account";
-import { Bubble, Vierkant, IconLabelButton } from './ui';
+import { gameInfo, userInfo } from '../api/api';
+import { AccountAvatar } from './account';
import { ToastContext } from './toast';
+import { Bubble, IconLabelButton, Vierkant } from './ui';
-import DoneIcon from '@material-ui/icons/Done';
import CloseIcon from '@material-ui/icons/Close';
+import DoneIcon from '@material-ui/icons/Done';
import NotificationsActiveOutlinedIcon from '@material-ui/icons/NotificationsActiveOutlined';
export function NotificationsArea(props: {
@@ -17,71 +17,82 @@ export function NotificationsArea(props: {
rerender: () => void;
}) {
var { toast } = useContext(ToastContext);
- var [ previousMessages, setPreviousMessages ] = useState(0);
+ var [previousMessages, setPreviousMessages] = useState(0);
var messages = (
- (props.friendRequests ? props.friendRequests.length : 0) +
- (props.gameInvites ? props.gameInvites.length : 0)
- )
+ (props.friendRequests ? props.friendRequests.length : 0)
+ + (props.gameInvites ? props.gameInvites.length : 0)
+ );
useEffect(() => {
- if(messages > previousMessages) {
- toast({ message: "Je hebt nieuwe meldingen!",
- type: "confirmation",
- icon: <NotificationsActiveOutlinedIcon/>});
+ if (messages > previousMessages) {
+ toast({
+ message: 'Je hebt nieuwe meldingen!',
+ type: 'confirmation',
+ icon: <NotificationsActiveOutlinedIcon />,
+ });
}
setPreviousMessages(messages);
- })
+ });
- return props.visible && <Bubble style={{
- left: 48 + 12,
- top: 92,
- transform: "translateY(-100%)",
- textAlign: "left",
- width: 400,
- height: 450
- }} tuitjeStyle={{
- left: 12,
- bottom: 86,
- transform: "translate(-100%, 100%) rotate(90deg)"
- }}>
+ return props.visible && <Bubble
+ style={{
+ left: 48 + 12,
+ top: 92,
+ transform: 'translateY(-100%)',
+ textAlign: 'left',
+ width: 400,
+ height: 450,
+ }}
+ tuitjeStyle={{
+ left: 12,
+ bottom: 86,
+ transform: 'translate(-100%, 100%) rotate(90deg)',
+ }}
+ >
<h2 style={{ marginBottom: 24 }}>Meldingen</h2>
- <div style={{
- overflowY: "scroll",
- whiteSpace: "normal",
- height: 450 - 24 * 4,
- borderRadius: 6
- }}>
- { props.gameInvites?.map(game => <GameInvite hide={props.rerender} game={game}/>) }
- { props.friendRequests?.map(user => <FriendRequest hide={props.rerender} user={user}/>) }
- {
- messages == 0 &&
- <div style={{
- position: "absolute",
- left: 0,
- right: 0,
- bottom: 0,
- top: 0
- }}>
- <h1 style={{
- position: "absolute",
- top: "50%",
- left: "50%",
- whiteSpace: "nowrap",
- transform: "translate(-50%, -50%)",
- opacity: .7
- }}>Geen meldingen</h1>
- </div>
- }
+ <div
+ style={{
+ overflowY: 'scroll',
+ whiteSpace: 'normal',
+ height: 450 - 24 * 4,
+ borderRadius: 6,
+ }}
+ >
+ {props.gameInvites?.map(game => <GameInvite hide={props.rerender} game={game} />)}
+ {props.friendRequests?.map(user => <FriendRequest hide={props.rerender} user={user} />)}
+ {messages == 0
+ && <div
+ style={{
+ position: 'absolute',
+ left: 0,
+ right: 0,
+ bottom: 0,
+ top: 0,
+ }}
+ >
+ <h1
+ style={{
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ whiteSpace: 'nowrap',
+ transform: 'translate(-50%, -50%)',
+ opacity: .7,
+ }}
+ >
+ Geen meldingen
+ </h1>
+ </div>}
</div>
- </Bubble>
+ </Bubble>;
}
var FriendRequestButtonStyle: CSSProperties = {
borderRadius: 6,
- display: "inline-block",
+ display: 'inline-block',
marginLeft: 0,
- textAlign: "center"
+ textAlign: 'center',
};
function Acceptable(props: {
@@ -89,78 +100,89 @@ function Acceptable(props: {
onAccept?: () => void;
onDeny?: () => void;
}) {
- return <Vierkant style={{
- borderRadius: 8,
- background: "var(--background-alt)",
- margin: 0,
- padding: 12,
- width: "100%",
- marginBottom: 12
- }}>
- <div style={{ position: "relative" }}>
+ return <Vierkant
+ style={{
+ borderRadius: 8,
+ background: 'var(--background-alt)',
+ margin: 0,
+ padding: 12,
+ width: '100%',
+ marginBottom: 12,
+ }}
+ >
+ <div style={{ position: 'relative' }}>
{props.children}
- <div style={{
- display: "grid",
- gridTemplateColumns: "1fr, 1fr",
- gridGap: 12,
- marginTop: 12,
- gridAutoFlow: "column",
- }}>
+ <div
+ style={{
+ display: 'grid',
+ gridTemplateColumns: '1fr, 1fr',
+ gridGap: 12,
+ marginTop: 12,
+ gridAutoFlow: 'column',
+ }}
+ >
<IconLabelButton
onclick={props.onAccept}
style={FriendRequestButtonStyle}
- icon={<DoneIcon/>}
- text="Accepteren"/>
+ icon={<DoneIcon />}
+ text='Accepteren'
+ />
<IconLabelButton
onclick={props.onDeny}
style={FriendRequestButtonStyle}
- icon={<CloseIcon/>}
- text="Verwijderen"/>
+ icon={<CloseIcon />}
+ text='Verwijderen'
+ />
</div>
</div>
- </Vierkant>
+ </Vierkant>;
}
function FriendRequest(props: {
user: userInfo;
hide: () => void;
}) {
- var [ gone, setGone ] = useState(false);
+ var [gone, setGone] = useState(false);
var hide = () => {
setGone(true);
props.hide();
- }
+ };
- return !gone && <Acceptable onAccept={() => {
- axios.request({
- method: "post",
- url: "/api/social/accept",
- headers: {"content-type": "application/json"},
- data: { "id": props.user?.id }
- })
- .then(hide);
- }} onDeny={() => {
- axios.request({
- method: "post",
- url: "/api/social/remove",
- headers: {"content-type": "application/json"},
- data: { "id": props.user?.id }
- })
- .then(hide);
- }}>
- <a href={"/user?id=" + props.user.id}>
- <AccountAvatar size={48} id={props.user.id}/>
- <div style={{
- display: "inline-block",
- verticalAlign: "top",
- marginLeft: 6
- }}>
- <i style={{ display: "block" }}>Vriendschapsverzoek</i>
+ return !gone && <Acceptable
+ onAccept={() => {
+ axios.request({
+ method: 'post',
+ url: '/api/social/accept',
+ headers: { 'content-type': 'application/json' },
+ data: { 'id': props.user?.id },
+ })
+ .then(hide);
+ }}
+ onDeny={() => {
+ axios.request({
+ method: 'post',
+ url: '/api/social/remove',
+ headers: { 'content-type': 'application/json' },
+ data: { 'id': props.user?.id },
+ })
+ .then(hide);
+ }}
+ >
+ <a href={'/user?id=' + props.user.id}>
+ <AccountAvatar size={48} id={props.user.id} />
+ <div
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ marginLeft: 6,
+ }}
+ >
+ <i style={{ display: 'block' }}>Vriendschapsverzoek</i>
<b>{props.user.username}</b>
</div>
</a>
- </Acceptable>
+ </Acceptable>;
}
function GameInvite(props: {
@@ -169,14 +191,20 @@ function GameInvite(props: {
}) {
return <Acceptable>
<a>
- <div style={{
- display: "inline-block",
- verticalAlign: "top",
- }}>
- <i style={{ display: "block" }}>Partijuitnodiging</i>
- <p><b><a href={"/user?id=" + props.game.opponent?.id}>{props.game.opponent?.username}</a></b> wil een potje 4 op een rij spelen!</p>
+ <div
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ }}
+ >
+ <i style={{ display: 'block' }}>Partijuitnodiging</i>
+ <p>
+ <b>
+ <a href={'/user?id=' + props.game.opponent?.id}>{props.game.opponent?.username}</a>
+ </b>{" "}
+ wil een potje 4 op een rij spelen!
+ </p>
</div>
</a>
- </Acceptable>
+ </Acceptable>;
}
-
diff --git a/components/page.tsx b/components/page.tsx
index d8a4a2b..506e2db 100644
--- a/components/page.tsx
+++ b/components/page.tsx
@@ -6,27 +6,39 @@ interface CenteredPageProps {
style?: CSSProperties;
}
-export function CenteredPage (props: CenteredPageProps) {
- return <div className="CenteredPageOuter" style={{
- maxWidth: props.width,
- margin: "0 auto"
- }}>
- <div className="CenteredPageInner" style={{
- margin: "0 6px",
- lineHeight: 0,
- ...props.style
- }}>{props.children}</div>
+export function CenteredPage(props: CenteredPageProps) {
+ return <div
+ className='CenteredPageOuter'
+ style={{
+ maxWidth: props.width,
+ margin: '0 auto',
+ }}
+ >
+ <div
+ className='CenteredPageInner'
+ style={{
+ margin: '0 6px',
+ lineHeight: 0,
+ ...props.style,
+ }}
+ >
+ {props.children}
+ </div>
</div>;
}
export class PageTitle extends Component {
- render () {
- return <h1 style={{
- color: "var(--text-alt)",
- marginLeft: 6,
- marginTop: 32,
- marginBottom: 64,
- fontSize: 25,
- }}>{this.props.children}</h1>;
+ render() {
+ return <h1
+ style={{
+ color: 'var(--text-alt)',
+ marginLeft: 6,
+ marginTop: 32,
+ marginBottom: 64,
+ fontSize: 25,
+ }}
+ >
+ {this.props.children}
+ </h1>;
}
}
diff --git a/components/preferencesContext.tsx b/components/preferencesContext.tsx
index 1b58a4f..a169be6 100644
--- a/components/preferencesContext.tsx
+++ b/components/preferencesContext.tsx
@@ -1,43 +1,48 @@
-import { useState, useEffect, createContext, ReactNode } from 'react';
import axios from 'axios';
+import { createContext, ReactNode, useEffect, useState } from 'react';
import { userPreferences } from '../api/api';
function applyPreferences(preferences: userPreferences) {
- if(typeof preferences === "undefined") return;
- if(typeof preferences.darkMode !== "undefined")
- document.getElementsByTagName("html")[0].classList[preferences.darkMode ? "add" : "remove"]("dark");
+ if (typeof preferences === 'undefined') return;
+ if (typeof preferences.darkMode !== 'undefined') {
+ document.getElementsByTagName('html')[0].classList[preferences.darkMode ? 'add' : 'remove']('dark');
+ }
}
-var PreferencesContext = createContext<{ preferences?: userPreferences; updatePreference?: (newPreference: userPreferences) => void }>({});
+var PreferencesContext = createContext<
+ { preferences?: userPreferences; updatePreference?: (newPreference: userPreferences) => void; }
+>({});
-export function PreferencesContextWrapper(props: { children?: ReactNode }) {
- var server = typeof window === "undefined";
- var loggedIn = !server && document.cookie.includes("token");
+export function PreferencesContextWrapper(props: { children?: ReactNode; }) {
+ var server = typeof window === 'undefined';
+ var loggedIn = !server && document.cookie.includes('token');
var [preferences, setPreferences] = useState<userPreferences>();
- useEffect(() => {(async() => {
- if (!loggedIn) return;
+ useEffect(() => {
+ (async () => {
+ if (!loggedIn) return;
- var local_prefs = window.localStorage.getItem("preferences");
- if (local_prefs) {
- var local_prefs_json = JSON.parse(local_prefs) as userPreferences;
- setPreferences(local_prefs_json);
- applyPreferences(local_prefs_json);
- }
+ var local_prefs = window.localStorage.getItem('preferences');
+ if (local_prefs) {
+ var local_prefs_json = JSON.parse(local_prefs) as userPreferences;
+ setPreferences(local_prefs_json);
+ applyPreferences(local_prefs_json);
+ }
- if (!preferences) {
- var preferencesReq = await axios.request<{ preferences: userPreferences; }>({
- method: "get",
- url: `/api/user/preferences`,
- headers: {"content-type": "application/json"}
- });
+ if (!preferences) {
+ var preferencesReq = await axios.request<{ preferences: userPreferences; }>({
+ method: 'get',
+ url: `/api/user/preferences`,
+ headers: { 'content-type': 'application/json' },
+ });
- window.localStorage.setItem("preferences", JSON.stringify(preferencesReq.data.preferences));
- setPreferences(preferencesReq.data.preferences);
- }
- })()}, []);
+ window.localStorage.setItem('preferences', JSON.stringify(preferencesReq.data.preferences));
+ setPreferences(preferencesReq.data.preferences);
+ }
+ })();
+ }, []);
useEffect(() => applyPreferences(preferences), [preferences]);
@@ -46,17 +51,16 @@ export function PreferencesContextWrapper(props: { children?: ReactNode }) {
setPreferences(prefs);
applyPreferences(prefs);
axios.request({
- method: "post",
+ method: 'post',
url: `/api/user/preferences`,
- headers: {"content-type": "application/json"},
- data: { "newPreferences": prefs }
+ headers: { 'content-type': 'application/json' },
+ data: { 'newPreferences': prefs },
});
}
return <PreferencesContext.Provider value={{ preferences, updatePreference }}>
{props.children}
- </PreferencesContext.Provider>
+ </PreferencesContext.Provider>;
}
export default PreferencesContext;
-
diff --git a/components/recentGames.tsx b/components/recentGames.tsx
index 150520c..988126f 100644
--- a/components/recentGames.tsx
+++ b/components/recentGames.tsx
@@ -1,79 +1,92 @@
-import { CSSProperties } from 'react';
import friendlyTime from 'friendly-time';
+import { CSSProperties } from 'react';
import { gameInfo } from '../api/api';
var LeftAlignedTableColumn: CSSProperties = {
- textAlign: "left",
- paddingLeft: 16
-}
+ textAlign: 'left',
+ paddingLeft: 16,
+};
var RightAlignedTableColumn: CSSProperties = {
- textAlign: "right",
- paddingRight: 16
-}
+ textAlign: 'right',
+ paddingRight: 16,
+};
-function GameOutcome(props: { game: gameInfo }) {
+function GameOutcome(props: { game: gameInfo; }) {
var gameStatus = (() => {
return {
- "resign": () => "Opgegeven",
- "wait_for_opponent": () => "Aan het wachten op een tegenstander",
- "in_progress": () => "Bezig",
- "finished": () => {
+ 'resign': () => 'Opgegeven',
+ 'wait_for_opponent': () => 'Aan het wachten op een tegenstander',
+ 'in_progress': () => 'Bezig',
+ 'finished': () => {
return {
- "w": "Gewonnen",
- "l": "Verloren",
- "d": "Gelijkspel"
- }[props.game.outcome]
+ 'w': 'Gewonnen',
+ 'l': 'Verloren',
+ 'd': 'Gelijkspel',
+ }[props.game.outcome];
},
}[props.game.status]();
})();
var outcome = props.game.outcome;
- return <td style={{
- color:
- outcome == "w" ? "var(--disk-b-text)" :
- outcome == "l" ? "var(--disk-a-text)" :
- "var(--text)",
- opacity: !["w", "l"].includes(outcome) ? 0.75 : 1.0
- }}>{gameStatus}</td>
+ return <td
+ style={{
+ color: outcome == 'w'
+ ? 'var(--disk-b-text)'
+ : outcome == 'l'
+ ? 'var(--disk-a-text)'
+ : 'var(--text)',
+ opacity: !['w', 'l'].includes(outcome) ? 0.75 : 1.0,
+ }}
+ >
+ {gameStatus}
+ </td>;
}
-export default function RecentGames(props: { games?: Array<gameInfo> }) {
+export default function RecentGames(props: { games?: Array<gameInfo>; }) {
return <div>
<h2>Recente partijen</h2>
- {
- props.games?.length > 0 ?
- <table width="100%" style={{ marginTop: "16px", textAlign: "center" }}>
+ {props.games?.length > 0
+ ? <table width='100%' style={{ marginTop: '16px', textAlign: 'center' }}>
<tbody>
<tr>
- <th style={{ width: "50%" }}>Tegenstander</th>
- <th style={{ width: "15%" }}>Uitkomst</th>
- <th style={{ width: "15%" }}>Zetten</th>
- <th style={{ width: "20%" }}>Datum</th>
+ <th style={{ width: '50%' }}>Tegenstander</th>
+ <th style={{ width: '15%' }}>Uitkomst</th>
+ <th style={{ width: '15%' }}>Zetten</th>
+ <th style={{ width: '20%' }}>Datum</th>
</tr>
- {
- props.games?.map(game => <tr key={game.id}>
- <td style={LeftAlignedTableColumn}>
- <a href={"/user?id=" + game.opponent?.id} style={{
- fontWeight: 500
- }}>{game.opponent?.username}</a>
- </td>
- <GameOutcome game={game}/>
- <td>{Math.max(0, game.moves.length -1)}</td>
- <td style={RightAlignedTableColumn}>{(() => {
- var timeCreated = new Date(game.created);
- return friendlyTime(timeCreated);
- })()}</td>
- </tr>)
- }
+ {props.games?.map(game =>
+ <tr key={game.id}>
+ <td style={LeftAlignedTableColumn}>
+ <a
+ href={'/user?id=' + game.opponent?.id}
+ style={{
+ fontWeight: 500,
+ }}
+ >
+ {game.opponent?.username}
+ </a>
+ </td>
+ <GameOutcome game={game} />
+ <td>{Math.max(0, game.moves.length - 1)}</td>
+ <td style={RightAlignedTableColumn}>
+ {(() => {
+ var timeCreated = new Date(game.created);
+ return friendlyTime(timeCreated);
+ })()}
+ </td>
+ </tr>
+ )}
</tbody>
- </table> :
- <h1 style={{
- textAlign: "center",
- opacity: .6,
- margin: "32px 64px"
- }}>Deze gebruiker heeft nog geen partijen gespeeld</h1>
- }
- </div>
+ </table>
+ : <h1
+ style={{
+ textAlign: 'center',
+ opacity: .6,
+ margin: '32px 64px',
+ }}
+ >
+ Deze gebruiker heeft nog geen partijen gespeeld
+ </h1>}
+ </div>;
}
-
diff --git a/components/socketContext.tsx b/components/socketContext.tsx
index f493d73..435f4a7 100644
--- a/components/socketContext.tsx
+++ b/components/socketContext.tsx
@@ -1,12 +1,11 @@
-import { ReactNode, createContext } from 'react';
+import { createContext, ReactNode } from 'react';
import { io as socket, Socket } from 'socket.io-client';
-export var SocketContext = createContext<{ io?: Socket }>({});
-export function SocketContextWrapper(props: { children?: ReactNode }) {
+export var SocketContext = createContext<{ io?: Socket; }>({});
+export function SocketContextWrapper(props: { children?: ReactNode; }) {
var io = socket();
return <SocketContext.Provider value={{ io }}>
- { props.children }
- </SocketContext.Provider>
+ {props.children}
+ </SocketContext.Provider>;
}
-
diff --git a/components/toast.tsx b/components/toast.tsx
index 91e67f7..97e17e6 100644
--- a/components/toast.tsx
+++ b/components/toast.tsx
@@ -1,114 +1,138 @@
-import { CSSProperties, ReactNode, useState, createContext } from "react";
+import { createContext, CSSProperties, ReactNode, useState } from 'react';
import CloseIcon from '@material-ui/icons/Close';
function ToastArea(props: {
- style?: CSSProperties
- children?: ReactNode
+ style?: CSSProperties;
+ children?: ReactNode;
rerender?: boolean;
}) {
- return <div id="ToastArea" style={{
- position: "fixed",
- whiteSpace: "nowrap",
- bottom: 12,
- left: "50%",
- transform: "translateX(-50%)",
- zIndex: 1,
- maxWidth: 600,
- width: "calc(100% - 48px - 48px)",
- margin: "0 24px",
- ...props.style
- }}>{props.children}</div>
+ return <div
+ id='ToastArea'
+ style={{
+ position: 'fixed',
+ whiteSpace: 'nowrap',
+ bottom: 12,
+ left: '50%',
+ transform: 'translateX(-50%)',
+ zIndex: 1,
+ maxWidth: 600,
+ width: 'calc(100% - 48px - 48px)',
+ margin: '0 24px',
+ ...props.style,
+ }}
+ >
+ {props.children}
+ </div>;
}
function Toast(props: {
- text?: string
- description?: string
- icon?: ReactNode
- children?: ReactNode
- type?: "normal"|"confirmation"|"error"
- style?: CSSProperties
+ text?: string;
+ description?: string;
+ icon?: ReactNode;
+ children?: ReactNode;
+ type?: 'normal' | 'confirmation' | 'error';
+ style?: CSSProperties;
}) {
var [visible, setVisibility] = useState(true);
setTimeout(() => setVisibility(false), 10e3);
- return visible && <div style={{
- padding: 0,
- marginBottom: 12,
- borderRadius: 6,
- boxShadow: "0 8px 12px -4px #00000033",
- color:
- props.type === "confirmation" ? "var(--background)" : "var(--text)",
- backgroundColor:
- props.type === "normal" ? "var(--background)" :
- props.type === "confirmation" ? "var(--disk-b)" :
- props.type === "error" ? "var(--disk-a)" : "var(--background)",
- ...props.style
- }}>
- {
- props.children ||
- <div style={{
- lineHeight: 0,
- padding: 12,
- minHeight: props.description ? 36 : 24,
- position: "relative"
- }}>
- <div style={{
- position: "absolute",
- left: 12,
- top: "50%",
- transform: "translateY(-50%)"
- }}>{props.icon}</div>
- <div style={{
- userSelect: "none",
- position: "absolute",
- left: props.icon ? 48 : 12,
- top: "50%",
- transform: "translateY(-50%)"
- }}>
+ return visible && <div
+ style={{
+ padding: 0,
+ marginBottom: 12,
+ borderRadius: 6,
+ boxShadow: '0 8px 12px -4px #00000033',
+ color: props.type === 'confirmation' ? 'var(--background)' : 'var(--text)',
+ backgroundColor: props.type === 'normal'
+ ? 'var(--background)'
+ : props.type === 'confirmation'
+ ? 'var(--disk-b)'
+ : props.type === 'error'
+ ? 'var(--disk-a)'
+ : 'var(--background)',
+ ...props.style,
+ }}
+ >
+ {props.children
+ || <div
+ style={{
+ lineHeight: 0,
+ padding: 12,
+ minHeight: props.description ? 36 : 24,
+ position: 'relative',
+ }}
+ >
+ <div
+ style={{
+ position: 'absolute',
+ left: 12,
+ top: '50%',
+ transform: 'translateY(-50%)',
+ }}
+ >
+ {props.icon}
+ </div>
+ <div
+ style={{
+ userSelect: 'none',
+ position: 'absolute',
+ left: props.icon ? 48 : 12,
+ top: '50%',
+ transform: 'translateY(-50%)',
+ }}
+ >
<h2 style={{ fontSize: 16 }}>{props.text}</h2>
<p>{props.description}</p>
</div>
- <div style={{
- cursor: "pointer",
- position: "absolute",
- right: 12,
- top: "50%",
- transform: "translateY(-50%)"
- }} onClick={() => setVisibility(false)}>
- <CloseIcon style={{ fontSize: 24 }}/>
+ <div
+ style={{
+ cursor: 'pointer',
+ position: 'absolute',
+ right: 12,
+ top: '50%',
+ transform: 'translateY(-50%)',
+ }}
+ onClick={() => setVisibility(false)}
+ >
+ <CloseIcon style={{ fontSize: 24 }} />
</div>
- </div>
- }
- </div>
+ </div>}
+ </div>;
}
export type toastSettings = {
- message: string,
- description?: string,
- type: "confirmation"|"normal"|"error",
- icon?: ReactNode
-}
+ message: string;
+ description?: string;
+ type: 'confirmation' | 'normal' | 'error';
+ icon?: ReactNode;
+};
export type toastType = (settings: toastSettings) => void;
-export var ToastContext = createContext<{ toast?: toastType }>({});
+export var ToastContext = createContext<{ toast?: toastType; }>({});
var toasts: Array<JSX.Element> = [];
-export function ToastContextWrapper(props: { children?: ReactNode }) {
+export function ToastContextWrapper(props: { children?: ReactNode; }) {
var [dummyState, rerender] = useState(false);
- return <ToastContext.Provider value={{ toast: options => {
- toasts.push(<Toast
- type={options.type}
- text={options.message}
- description={options.description}
- icon={options.icon}/>);
- rerender(!dummyState);
- } }}>
- { props.children }
+ return <ToastContext.Provider
+ value={{
+ toast: options => {
+ toasts.push(
+ <Toast
+ type={options.type}
+ text={options.message}
+ description={options.description}
+ icon={options.icon}
+ />,
+ );
+ rerender(!dummyState);
+ },
+ }}
+ >
+ {props.children}
<ToastArea rerender={dummyState}>
{toasts}
</ToastArea>
- </ToastContext.Provider>
+ </ToastContext.Provider>;
}
-
diff --git a/components/ui.tsx b/components/ui.tsx
index 9d532f8..c3f950b 100644
--- a/components/ui.tsx
+++ b/components/ui.tsx
@@ -1,9 +1,9 @@
-import { Component, CSSProperties, ReactNode, useState, useEffect } from "react";
+import { Component, CSSProperties, ReactNode, useEffect, useState } from 'react';
-import SearchIcon from '@material-ui/icons/Search';
-import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
import CheckBoxIcon from '@material-ui/icons/CheckBox';
+import CheckBoxOutlineBlankIcon from '@material-ui/icons/CheckBoxOutlineBlank';
import EditOutlinedIcon from '@material-ui/icons/EditOutlined';
+import SearchIcon from '@material-ui/icons/Search';
export function Vierkant(props: {
href?: string;
@@ -16,27 +16,31 @@ export function Vierkant(props: {
fullwidth?: boolean;
onclick?: () => void;
}) {
- return <a style={{
- padding: 24,
- backgroundColor: "var(--background)",
- borderRadius: 8,
- color: "var(--text)",
- margin: 6, // geen margin collapse = 12px marge
- display: "inline-block",
- position: "relative",
- boxSizing: "border-box",
- width:
- props.width ? props.width :
- props.fullwidth ? "calc(100% - 12px)" :
- undefined,
- height: props.height ? props.height : undefined,
- ...props.style
- }}
- href={props.href}
- className={props.className}
- id={props.id}
- onClick={props.onclick}
- >{props.children}</a>
+ return <a
+ style={{
+ padding: 24,
+ backgroundColor: 'var(--background)',
+ borderRadius: 8,
+ color: 'var(--text)',
+ margin: 6, // geen margin collapse = 12px marge
+ display: 'inline-block',
+ position: 'relative',
+ boxSizing: 'border-box',
+ width: props.width
+ ? props.width
+ : props.fullwidth
+ ? 'calc(100% - 12px)'
+ : undefined,
+ height: props.height ? props.height : undefined,
+ ...props.style,
+ }}
+ href={props.href}
+ className={props.className}
+ id={props.id}
+ onClick={props.onclick}
+ >
+ {props.children}
+ </a>;
}
export function Button(props: {
@@ -47,27 +51,34 @@ export function Button(props: {
onclick?: (() => void);
id?: string;
}) {
- return <a onClick={props.onclick} href={props.href} id={props.id} style={{
- padding: props.text ? 8 : 16,
- textAlign: props.text ? "center" : "left",
- borderRadius: 8,
- backgroundColor: "var(--disk-a)",
- cursor: "pointer",
- position: "relative",
- textDecoration: "none",
- display: "block",
- userSelect: "none",
- ...props.style
- }}>
- {
- props.text ?
- <span style={{
- fontWeight: 600,
- userSelect: "none"
- }}>{props.text}</span>
- : undefined
- }
- { props.children }
+ return <a
+ onClick={props.onclick}
+ href={props.href}
+ id={props.id}
+ style={{
+ padding: props.text ? 8 : 16,
+ textAlign: props.text ? 'center' : 'left',
+ borderRadius: 8,
+ backgroundColor: 'var(--disk-a)',
+ cursor: 'pointer',
+ position: 'relative',
+ textDecoration: 'none',
+ display: 'block',
+ userSelect: 'none',
+ ...props.style,
+ }}
+ >
+ {props.text
+ ? <span
+ style={{
+ fontWeight: 600,
+ userSelect: 'none',
+ }}
+ >
+ {props.text}
+ </span>
+ : undefined}
+ {props.children}
</a>;
}
@@ -78,23 +89,33 @@ export function IconLabelButton(props: {
style?: CSSProperties;
href?: string;
}) {
- return <Button onclick={props.onclick} href={props.href} style={{
- display: "inline-block",
- verticalAlign: "top",
- padding: 8,
- float: "right",
- marginLeft: 12,
- ...props.style
- }}>
+ return <Button
+ onclick={props.onclick}
+ href={props.href}
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ padding: 8,
+ float: 'right',
+ marginLeft: 12,
+ ...props.style,
+ }}
+ >
{props.icon}
- <span style={{
- display: "inline-block",
- verticalAlign: "top",
- fontWeight: 500,
- marginLeft: 8,
- marginTop: 3, marginBottom: 3, marginRight: 3
- }}>{props.text}</span>
- </Button>
+ <span
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ fontWeight: 500,
+ marginLeft: 8,
+ marginTop: 3,
+ marginBottom: 3,
+ marginRight: 3,
+ }}
+ >
+ {props.text}
+ </span>
+ </Button>;
}
export function Input(props: {
@@ -104,61 +125,73 @@ export function Input(props: {
id?: string;
min?: number;
max?: number;
- value?: string|number;
+ value?: string | number;
dark?: boolean;
autocomplete?: string;
autofocus?: boolean;
}) {
return <input
- id={props.id}
- type={props.type || "text"}
- min={props.min} max={props.max}
- placeholder={props.label}
- spellCheck={false}
- defaultValue={props.value ? String(props.value) : ""}
- className={props.dark ? "dark" : "light"}
- autoComplete={props.autocomplete}
- autoFocus={props.autofocus}
- style={{
- padding: 12,
- border: 0,
- width: "calc(100% - 24px)",
- fontSize: 14,
- backgroundColor: "var(--page-background)",
- color: "var(--text-alt)",
- borderRadius: 8,
- fontFamily: "Inter",
- ...props.style
- }}/>
+ id={props.id}
+ type={props.type || 'text'}
+ min={props.min}
+ max={props.max}
+ placeholder={props.label}
+ spellCheck={false}
+ defaultValue={props.value ? String(props.value) : ''}
+ className={props.dark ? 'dark' : 'light'}
+ autoComplete={props.autocomplete}
+ autoFocus={props.autofocus}
+ style={{
+ padding: 12,
+ border: 0,
+ width: 'calc(100% - 24px)',
+ fontSize: 14,
+ backgroundColor: 'var(--page-background)',
+ color: 'var(--text-alt)',
+ borderRadius: 8,
+ fontFamily: 'Inter',
+ ...props.style,
+ }}
+ />;
}
-export function SearchBar(props: { label?: string }) {
- return <div style={{
- marginTop: 24,
- borderRadius: 8,
- overflow: "hidden",
- width: "100%"
- }}>
- <Input label={props.label} style={{
- width: "calc(100% - 24px - 41px)",
- borderTopRightRadius: 0,
- borderBottomRightRadius: 0
- }}/>
- <div style={{
- width: 41,
- height: 41,
- backgroundColor: "var(--disk-a)",
- display: "inline-block",
- verticalAlign: "top",
- position: "relative"
- }}>
- <SearchIcon style={{
- position: "absolute",
- top: "50%", left: "50%",
- transform: "translate(-50%, -50%)"
- }}/>
+export function SearchBar(props: { label?: string; }) {
+ return <div
+ style={{
+ marginTop: 24,
+ borderRadius: 8,
+ overflow: 'hidden',
+ width: '100%',
+ }}
+ >
+ <Input
+ label={props.label}
+ style={{
+ width: 'calc(100% - 24px - 41px)',
+ borderTopRightRadius: 0,
+ borderBottomRightRadius: 0,
+ }}
+ />
+ <div
+ style={{
+ width: 41,
+ height: 41,
+ backgroundColor: 'var(--disk-a)',
+ display: 'inline-block',
+ verticalAlign: 'top',
+ position: 'relative',
+ }}
+ >
+ <SearchIcon
+ style={{
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ }}
+ />
</div>
- </div>
+ </div>;
}
export function CheckBox(props: {
@@ -173,25 +206,28 @@ export function CheckBox(props: {
useEffect(() => {
if (gotDefaultState) return;
setOn(props.state);
- if (typeof props.state !== "undefined") setGotDefaultState(true);
+ if (typeof props.state !== 'undefined') setGotDefaultState(true);
});
var toggle = () => {
setOn(!on);
props.onclick && props.onclick(!on);
- }
+ };
- return <div onClick={toggle} id={props.id} className={on ? "on" : "off"} style={{
- ...props.style,
- display: "inline-block",
- cursor: "pointer"
- }}>
- {
- on ?
- <CheckBoxIcon/> :
- <CheckBoxOutlineBlankIcon/>
- }
- </div>
+ return <div
+ onClick={toggle}
+ id={props.id}
+ className={on ? 'on' : 'off'}
+ style={{
+ ...props.style,
+ display: 'inline-block',
+ cursor: 'pointer',
+ }}
+ >
+ {on
+ ? <CheckBoxIcon />
+ : <CheckBoxOutlineBlankIcon />}
+ </div>;
}
export class ColorPicker extends Component<{
@@ -201,44 +237,53 @@ export class ColorPicker extends Component<{
color: string;
dark: boolean;
} = {
- color: "#012345",
- dark: true
- }
+ color: '#012345',
+ dark: true,
+ };
render() {
- return <Button style={{
- display: "inline-block",
- verticalAlign: "top",
- padding: 6,
- float: "right",
- marginLeft: 12,
- color: this.state.dark ? "var(--text)" : "var(--text-alt)",
- borderColor: this.state.dark ? "var(--text)" : "var(--text-alt)",
- borderWidth: 2,
- borderStyle: "solid",
- backgroundColor: this.state.color,
- ...this.props.style
- }}>
+ return <Button
+ style={{
+ display: 'inline-block',
+ verticalAlign: 'top',
+ padding: 6,
+ float: 'right',
+ marginLeft: 12,
+ color: this.state.dark ? 'var(--text)' : 'var(--text-alt)',
+ borderColor: this.state.dark ? 'var(--text)' : 'var(--text-alt)',
+ borderWidth: 2,
+ borderStyle: 'solid',
+ backgroundColor: this.state.color,
+ ...this.props.style,
+ }}
+ >
<div>
- <EditOutlinedIcon/>
- <div style={{
- width: 150,
- height: 24,
- display: "inline-block",
- textAlign: "center",
- verticalAlign: "top",
- position: "relative"
- }}>
- <span style={{
- position: "absolute",
- top: "50%", left: "50%",
- transform: "translate(-50%, -50%)",
- fontWeight: 600,
- fontFeatureSettings: '"tnum", "ss01"'
- }}>{this.state.color}</span>
+ <EditOutlinedIcon />
+ <div
+ style={{
+ width: 150,
+ height: 24,
+ display: 'inline-block',
+ textAlign: 'center',
+ verticalAlign: 'top',
+ position: 'relative',
+ }}
+ >
+ <span
+ style={{
+ position: 'absolute',
+ top: '50%',
+ left: '50%',
+ transform: 'translate(-50%, -50%)',
+ fontWeight: 600,
+ fontFeatureSettings: '"tnum", "ss01"',
+ }}
+ >
+ {this.state.color}
+ </span>
</div>
</div>
- </Button>
+ </Button>;
}
}
@@ -246,12 +291,21 @@ export function Tuitje(props: {
style?: CSSProperties;
rotation?: number;
}) {
- return <svg width="36" height="12" viewBox="0 0 36 12" fill="none" xmlns="http://www.w3.org/2000/svg" style={{
- ...props.style
- }}>
- <path d="M18 12C24 12 27 0 36 0L0 0C9 0 12 12 18 12Z"
- fill={ props.style?.background as string || "var(--background)" }/>
- </svg>
+ return <svg
+ width='36'
+ height='12'
+ viewBox='0 0 36 12'
+ fill='none'
+ xmlns='http://www.w3.org/2000/svg'
+ style={{
+ ...props.style,
+ }}
+ >
+ <path
+ d='M18 12C24 12 27 0 36 0L0 0C9 0 12 12 18 12Z'
+ fill={props.style?.background as string || 'var(--background)'}
+ />
+ </svg>;
}
export function Bubble(props: {
@@ -259,24 +313,27 @@ export function Bubble(props: {
style?: CSSProperties;
tuitjeStyle?: CSSProperties;
}) {
- return <Vierkant style={{
- position: "absolute",
- textAlign: "center",
- margin: 0,
- overflow: "visible",
- left: "50%",
- top: -24,
- transform: "translateY(-100%) translateX(-50%)",
- boxShadow: "0 8px 32px rgba(0, 0, 0, 0.3)",
- ...props.style
- }}>
+ return <Vierkant
+ style={{
+ position: 'absolute',
+ textAlign: 'center',
+ margin: 0,
+ overflow: 'visible',
+ left: '50%',
+ top: -24,
+ transform: 'translateY(-100%) translateX(-50%)',
+ boxShadow: '0 8px 32px rgba(0, 0, 0, 0.3)',
+ ...props.style,
+ }}
+ >
{props.children}
- <Tuitje style={{
- position: "absolute",
- bottom: -12,
- transform: "translate(-50%, 0%) rotate(0deg)",
- ...props.tuitjeStyle
- }}/>
- </Vierkant>
+ <Tuitje
+ style={{
+ position: 'absolute',
+ bottom: -12,
+ transform: 'translate(-50%, 0%) rotate(0deg)',
+ ...props.tuitjeStyle,
+ }}
+ />
+ </Vierkant>;
}
-
diff --git a/components/voerBord.tsx b/components/voerBord.tsx
index 946aa9c..93e350c 100644
--- a/components/voerBord.tsx
+++ b/components/voerBord.tsx
@@ -1,10 +1,16 @@
function Disc() {
- return <div className="disk" style={{
- position: "absolute",
- top: 0, left: 0, right: 0, bottom: 0,
- borderRadius: 999999,
- margin: 3
- }}/>
+ return <div
+ className='disk'
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
+ borderRadius: 999999,
+ margin: 3,
+ }}
+ />;
}
export function VoerBord(props: {
@@ -13,40 +19,53 @@ export function VoerBord(props: {
onMove: (move: number) => void;
active: boolean;
}) {
- return <table className="voerBord" style={{
- borderSpacing: 8,
- width: "100%"
- }}>
+ return <table
+ className='voerBord'
+ style={{
+ borderSpacing: 8,
+ width: '100%',
+ }}
+ >
<tbody>
- {
- [...Array(props.height).keys()].map((row) => (
- <tr key={`r-${row}`}>
- {[...Array(props.width).keys()].map((column) => (
- <td style={{
- position: "relative",
- width: "100%",
- padding: 0
- }} key={`c-${row}x${column}`}>
- <div style={{
- display: "block",
- marginTop: "100%"
- }}/>
- <Disc/>
- <div style={{
- position: "absolute",
- top: 0, left: 0, right: 0, bottom: 0,
+ {[...Array(props.height).keys()].map((row) => (
+ <tr key={`r-${row}`}>
+ {[...Array(props.width).keys()].map((column) => (
+ <td
+ style={{
+ position: 'relative',
+ width: '100%',
+ padding: 0,
+ }}
+ key={`c-${row}x${column}`}
+ >
+ <div
+ style={{
+ display: 'block',
+ marginTop: '100%',
+ }}
+ />
+ <Disc />
+ <div
+ style={{
+ position: 'absolute',
+ top: 0,
+ left: 0,
+ right: 0,
+ bottom: 0,
borderRadius: 6,
- border: "2px solid var(--background-alt)",
+ border: '2px solid var(--background-alt)',
opacity: .5,
- cursor: props.active ? "pointer" : "default"
- }} id={`pos-${(props.height - row - 1) * props.width + column}`} onClick={event => {
- props.onMove(Number((event.target as HTMLElement).id.split("-")[1]))
- }}/>
- </td>
- ))}
- </tr>
- ))
- }
+ cursor: props.active ? 'pointer' : 'default',
+ }}
+ id={`pos-${(props.height - row - 1) * props.width + column}`}
+ onClick={event => {
+ props.onMove(Number((event.target as HTMLElement).id.split('-')[1]));
+ }}
+ />
+ </td>
+ ))}
+ </tr>
+ ))}
</tbody>
- </table>
+ </table>;
}