aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorlonkaars <l.leblansch@gmail.com>2021-04-22 12:35:53 +0200
committerlonkaars <l.leblansch@gmail.com>2021-04-22 12:35:53 +0200
commitfce651a618ca6d0d64fbcea757c3e0f582e1b437 (patch)
treeb49ff4a2e8b237b54fdc22f3c51e67af13d05c7f
parentb0d6721ba9c3cb0a74c376791d41e446a2f57d14 (diff)
beginnings of theme settings
-rw-r--r--api/api.ts2
-rw-r--r--api/user/preferences.py4
-rw-r--r--components/preferencesContext.tsx22
-rw-r--r--components/themes.tsx61
-rw-r--r--components/ui.tsx20
-rw-r--r--pages/_app.tsx1
-rw-r--r--pages/settings.tsx12
-rw-r--r--public/themes/themes.json34
-rw-r--r--styles/notifications.css3
-rw-r--r--styles/themepicker.css50
10 files changed, 170 insertions, 39 deletions
diff --git a/api/api.ts b/api/api.ts
index 95c63ba..d0dee96 100644
--- a/api/api.ts
+++ b/api/api.ts
@@ -24,13 +24,13 @@ export type ruleset = {
export type userColors = {
diskA: string;
diskB: string;
- background: string;
};
export interface userPreferences {
darkMode?: boolean;
ruleset?: ruleset;
userColors?: userColors;
+ theme?: string;
}
export interface userGameTotals {
diff --git a/api/user/preferences.py b/api/user/preferences.py
index 8779eaf..d903fd8 100644
--- a/api/user/preferences.py
+++ b/api/user/preferences.py
@@ -15,8 +15,8 @@ def format_preferences(prefs):
"userColors": {
"diskA": prefs.get("userColors", {}).get("diskA") or "",
"diskB": prefs.get("userColors", {}).get("diskB") or "",
- "background": prefs.get("userColors", {}).get("background") or ""
- }
+ },
+ "theme": prefs.get("theme") or "default",
}
diff --git a/components/preferencesContext.tsx b/components/preferencesContext.tsx
index a169be6..965b185 100644
--- a/components/preferencesContext.tsx
+++ b/components/preferencesContext.tsx
@@ -20,6 +20,8 @@ export function PreferencesContextWrapper(props: { children?: ReactNode; }) {
var [preferences, setPreferences] = useState<userPreferences>();
+ var [dummy, setDummy] = useState(false); //FIXME: janky fix to cause rerender
+
useEffect(() => {
(async () => {
if (!loggedIn) return;
@@ -31,31 +33,29 @@ export function PreferencesContextWrapper(props: { children?: ReactNode; }) {
applyPreferences(local_prefs_json);
}
- if (!preferences) {
- var preferencesReq = await axios.request<{ preferences: userPreferences; }>({
- method: 'get',
- url: `/api/user/preferences`,
- headers: { 'content-type': 'application/json' },
- });
+ 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]);
+ applyPreferences(preferences);
function updatePreference(newPreference: userPreferences) {
var prefs: userPreferences = Object.assign(preferences, newPreference);
setPreferences(prefs);
- applyPreferences(prefs);
axios.request({
method: 'post',
url: `/api/user/preferences`,
headers: { 'content-type': 'application/json' },
data: { 'newPreferences': prefs },
});
+ setDummy(!dummy);
}
return <PreferencesContext.Provider value={{ preferences, updatePreference }}>
diff --git a/components/themes.tsx b/components/themes.tsx
new file mode 100644
index 0000000..be98685
--- /dev/null
+++ b/components/themes.tsx
@@ -0,0 +1,61 @@
+import { useEffect, useState, CSSProperties } from 'react';
+import axios from 'axios';
+
+import { userPreferences } from '../api/api';
+import { Button } from './ui';
+
+type previewThemeColors = {
+ bg: string;
+ fg: string;
+ a: string;
+ b: string;
+}
+
+export type themeInfo = {
+ name: string;
+ url: string;
+ dark: previewThemeColors;
+ light: previewThemeColors;
+}
+
+export type themeJSON = themeInfo[];
+
+export default function ThemePicker(props: { preferences?: userPreferences }) {
+ var [ themes, setThemes ] = useState<themeJSON>([]);
+
+ useEffect(() => {
+ axios.request<themeJSON>({
+ method: 'get',
+ url: '/themes/themes.json',
+ }).then(response => {
+ setThemes(response.data);
+ }).catch(err => {
+ console.error(err)
+ })
+ }, []);
+
+ return <>
+ {themes.map(theme => <ThemeCard theme={theme} dark={props.preferences?.darkMode}/>)}
+ </>;
+}
+
+export function ThemeCard(props: { theme: themeInfo; dark?: boolean }) {
+ var mode = props.dark ? "dark" : "light";
+
+ return <div className="dispinbl themeCardWrapper" style={{
+ "--bg": props.theme[mode].bg,
+ "--fg": props.theme[mode].fg,
+ "--a": props.theme[mode].a,
+ "--b": props.theme[mode].b,
+ } as CSSProperties}>
+ <Button className="themeCard dispinbl drop-1" onclick={() => {
+ document.getElementById("theme").setAttribute("href", "/themes/" + props.theme.url)
+ }}>
+ <span className="name">{props.theme.name}</span>
+ <div className="posabs r0 v0 disks">
+ <div className="disk posabs a"/>
+ <div className="disk posabs b"/>
+ </div>
+ </Button>
+ </div>
+}
diff --git a/components/ui.tsx b/components/ui.tsx
index 1e9997a..c92ebfe 100644
--- a/components/ui.tsx
+++ b/components/ui.tsx
@@ -122,26 +122,12 @@ export function CheckBox(props: {
id?: string;
onclick?: (state: boolean) => void;
}) {
- var [gotDefaultState, setGotDefaultState] = useState(false);
- var [on, setOn] = useState(props.state);
-
- useEffect(() => {
- if (gotDefaultState) return;
- setOn(props.state);
- if (typeof props.state !== 'undefined') setGotDefaultState(true);
- });
-
- var toggle = () => {
- setOn(!on);
- props.onclick && props.onclick(!on);
- };
-
return <div
- onClick={toggle}
+ onClick={() => props.onclick && props.onclick(!props.state)}
id={props.id}
- className={'checkbox dispinbl ' + (on ? 'on' : 'off')}
+ className={'checkbox dispinbl ' + (props.state ? 'on' : 'off')}
>
- {on
+ {props.state
? <CheckBoxIcon />
: <CheckBoxOutlineBlankIcon />}
</div>;
diff --git a/pages/_app.tsx b/pages/_app.tsx
index f53a6ff..6cbd476 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -12,6 +12,7 @@ import '../styles/recentGames.css';
import '../styles/toast.css';
import '../styles/ui.css';
import '../styles/utility.css';
+import '../styles/themepicker.css';
import '../styles/game.css';
import '../styles/gameSettings.css';
diff --git a/pages/settings.tsx b/pages/settings.tsx
index 0ca2c30..d2b23eb 100644
--- a/pages/settings.tsx
+++ b/pages/settings.tsx
@@ -1,6 +1,6 @@
import axios from 'axios';
import reduce from 'image-blob-reduce';
-import { useContext } from 'react';
+import { useContext, useEffect } from 'react';
import { AccountAvatar } from '../components/account';
import { Footer } from '../components/footer';
@@ -9,6 +9,7 @@ import { NavBar } from '../components/navbar';
import { CenteredPage, PageTitle } from '../components/page';
import PreferencesContext from '../components/preferencesContext';
import { CheckBox, ColorPicker, IconLabelButton, Vierkant } from '../components/ui';
+import ThemePicker from '../components/themes';
import EditOutlinedIcon from '@material-ui/icons/EditOutlined';
import ExitToAppOutlinedIcon from '@material-ui/icons/ExitToAppOutlined';
@@ -102,12 +103,6 @@ export default function SettingsPage() {
</div>
</div>
<div className='subsection'>
- <ColorPicker />
- <div className='dispbl'>
- <h3>Achtergrond</h3>
- </div>
- </div>
- <div className='subsection'>
<div className='floatr'>
<CheckBox
state={preferences?.darkMode}
@@ -116,6 +111,9 @@ export default function SettingsPage() {
</div>
<h3>Donkere modus</h3>
</div>
+ <div className="subsection">
+ <ThemePicker preferences={preferences}/>
+ </div>
</Vierkant>
<Vierkant className='section gamerules w100m2m pad-l bg-800'>
<h2>Standaard spelregels</h2>
diff --git a/public/themes/themes.json b/public/themes/themes.json
new file mode 100644
index 0000000..0b5eb08
--- /dev/null
+++ b/public/themes/themes.json
@@ -0,0 +1,34 @@
+[
+ {
+ "name": "default",
+ "url": "default.css",
+ "dark": {
+ "bg": "#141619",
+ "fg": "#FFFFF3",
+ "a": "#FF4365",
+ "b": "#00D9C0"
+ },
+ "light": {
+ "bg": "#E3E6EE",
+ "fg": "#141619",
+ "a": "#A63A4D",
+ "b": "#3AA699"
+ }
+ },
+ {
+ "name": "classic",
+ "url": "classic.css",
+ "dark": {
+ "bg": "#222d33",
+ "fg": "#fcfffd",
+ "a": "#E16D82",
+ "b": "#71D9CC"
+ },
+ "light": {
+ "bg": "#5d737e",
+ "fg": "#fcfffd",
+ "a": "#E16D82",
+ "b": "#71D9CC"
+ }
+ }
+]
diff --git a/styles/notifications.css b/styles/notifications.css
index 02170ae..3ab92e7 100644
--- a/styles/notifications.css
+++ b/styles/notifications.css
@@ -15,8 +15,9 @@ a.notificationsArea .tuitje {
left: var(--spacing-medium);
bottom: 86px;
transform: translate(-100%, 100%) rotate(90deg);
- fill: var(--gray-700);
+ fill: var(--gray-800);
}
+html.dark a.notificationsArea .tuitje { fill: var(--gray-700); }
.notificationsArea .title {
margin-bottom: var(--spacing-large);
diff --git a/styles/themepicker.css b/styles/themepicker.css
new file mode 100644
index 0000000..4bac49d
--- /dev/null
+++ b/styles/themepicker.css
@@ -0,0 +1,50 @@
+.themeCardWrapper {
+ margin: var(--spacing-medium);
+}
+
+.themeCard {
+ background-color: var(--bg);
+ border-width: 2px;
+ border-style: solid;
+ border-color: var(--fg);
+ text-align: left;
+
+ transition-duration: .1s;
+ transition-property: transform, box-shadow;
+
+ height: 20px;
+ width: 170px;
+}
+
+.themeCard:active {
+ box-shadow: 0;
+ transform: translateY(2px);
+}
+
+.themeCard .name {
+ color: var(--fg);
+ margin-left: var(--spacing-small);
+}
+
+.themeCard .disks {
+ background-color: var(--fg);
+ width: 64px;
+
+}
+
+.themeCard .disks .disk {
+ border-radius: 9999999px;
+ width: 24px;
+
+ top: 4px;
+ bottom: 4px;
+}
+
+.themeCard .disks .disk.a {
+ background-color: var(--a);
+ right: 4px;
+}
+.themeCard .disks .disk.b {
+ background-color: var(--b);
+ left: 6px;
+}