diff options
author | lonkaars <l.leblansch@gmail.com> | 2021-04-22 12:35:53 +0200 |
---|---|---|
committer | lonkaars <l.leblansch@gmail.com> | 2021-04-22 12:35:53 +0200 |
commit | fce651a618ca6d0d64fbcea757c3e0f582e1b437 (patch) | |
tree | b49ff4a2e8b237b54fdc22f3c51e67af13d05c7f | |
parent | b0d6721ba9c3cb0a74c376791d41e446a2f57d14 (diff) |
beginnings of theme settings
-rw-r--r-- | api/api.ts | 2 | ||||
-rw-r--r-- | api/user/preferences.py | 4 | ||||
-rw-r--r-- | components/preferencesContext.tsx | 22 | ||||
-rw-r--r-- | components/themes.tsx | 61 | ||||
-rw-r--r-- | components/ui.tsx | 20 | ||||
-rw-r--r-- | pages/_app.tsx | 1 | ||||
-rw-r--r-- | pages/settings.tsx | 12 | ||||
-rw-r--r-- | public/themes/themes.json | 34 | ||||
-rw-r--r-- | styles/notifications.css | 3 | ||||
-rw-r--r-- | styles/themepicker.css | 50 |
10 files changed, 170 insertions, 39 deletions
@@ -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; +} |