unified ThemeProvider
This commit is contained in:
parent
e8238c6ea7
commit
40bb1ec28a
|
@ -9,39 +9,37 @@ import Row from './components/Row.tsx';
|
||||||
import Scoreboard from './Scoreboard.tsx';
|
import Scoreboard from './Scoreboard.tsx';
|
||||||
import Button from './components/Button.tsx';
|
import Button from './components/Button.tsx';
|
||||||
import MapChooser from './MapChooser.tsx';
|
import MapChooser from './MapChooser.tsx';
|
||||||
import {getRandomTheme, RgbaThemeProvider, ThemeContext, useStoredTheme} from './theme.tsx';
|
import {ThemeProvider} from './theme.tsx';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import ThemeChooser from './ThemeChooser.tsx';
|
||||||
|
|
||||||
export default function App() {
|
export default function App() {
|
||||||
const [theme, setTheme] = useStoredTheme();
|
|
||||||
const [name, setName] = useState<string | null>(null);
|
const [name, setName] = useState<string | null>(null);
|
||||||
|
|
||||||
return <ThemeContext.Provider value={theme}>
|
return <ThemeProvider>
|
||||||
<RgbaThemeProvider>
|
<Column className="flex-grow">
|
||||||
<Column className="flex-grow">
|
|
||||||
|
|
||||||
<ClientScreen player={name}/>
|
<ClientScreen player={name}/>
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<h1 className="flex-grow">CCCB-Tanks!</h1>
|
<h1 className="flex-grow">CCCB-Tanks!</h1>
|
||||||
<MapChooser/>
|
<MapChooser/>
|
||||||
<Button text="☼ change colors" onClick={() => setTheme(_ => getRandomTheme())}/>
|
<ThemeChooser/>
|
||||||
<Button
|
<Button
|
||||||
onClick={() => window.open('https://github.com/kaesaecracker/cccb-tanks-cs', '_blank')?.focus()}
|
onClick={() => window.open('https://github.com/kaesaecracker/cccb-tanks-cs', '_blank')?.focus()}
|
||||||
text="⌂ source"/>
|
text="⌂ source"/>
|
||||||
{name !== '' &&
|
{name !== '' &&
|
||||||
<Button onClick={() => setName(_ => '')} text="∩ logout"/>}
|
<Button onClick={() => setName(_ => '')} text="∩ logout"/>}
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
{name || <JoinForm onDone={name => setName(_ => name)}/>}
|
{name || <JoinForm onDone={name => setName(_ => name)}/>}
|
||||||
|
|
||||||
<Row className="GadgetRows">
|
<Row className="GadgetRows">
|
||||||
{name && <Controls player={name}/>}
|
{name && <Controls player={name}/>}
|
||||||
{name && <PlayerInfo player={name}/>}
|
{name && <PlayerInfo player={name}/>}
|
||||||
<Scoreboard/>
|
<Scoreboard/>
|
||||||
</Row>
|
</Row>
|
||||||
|
|
||||||
</Column>
|
</Column>
|
||||||
</RgbaThemeProvider>
|
</ThemeProvider>;
|
||||||
</ThemeContext.Provider>;
|
|
||||||
}
|
}
|
||||||
|
|
11
tank-frontend/src/ThemeChooser.tsx
Normal file
11
tank-frontend/src/ThemeChooser.tsx
Normal file
|
@ -0,0 +1,11 @@
|
||||||
|
import Button from './components/Button.tsx';
|
||||||
|
import {getRandomTheme, useHslTheme} from './theme.tsx';
|
||||||
|
|
||||||
|
export default function ThemeChooser({}: {}) {
|
||||||
|
const {setHslTheme} = useHslTheme();
|
||||||
|
return <>
|
||||||
|
<Button
|
||||||
|
text="☼ change colors"
|
||||||
|
onClick={() => setHslTheme(_ => getRandomTheme())}/>
|
||||||
|
</>;
|
||||||
|
}
|
|
@ -1,6 +1,6 @@
|
||||||
import {useEffect, useRef} from 'react';
|
import {useEffect, useRef} from 'react';
|
||||||
import './PixelGridCanvas.css';
|
import './PixelGridCanvas.css';
|
||||||
import {useRgbaThemeContext} from '../theme.tsx';
|
import {useRgbaTheme} from '../theme.tsx';
|
||||||
|
|
||||||
const pixelsPerRow = 352;
|
const pixelsPerRow = 352;
|
||||||
const pixelsPerCol = 160;
|
const pixelsPerCol = 160;
|
||||||
|
@ -101,7 +101,7 @@ function drawPixelsToCanvas(
|
||||||
export default function PixelGridCanvas({pixels}: {
|
export default function PixelGridCanvas({pixels}: {
|
||||||
readonly pixels: Uint8ClampedArray;
|
readonly pixels: Uint8ClampedArray;
|
||||||
}) {
|
}) {
|
||||||
const theme = useRgbaThemeContext();
|
const theme = useRgbaTheme();
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import {useStoredObjectState} from './useStoredState.ts';
|
import {useStoredObjectState} from './useStoredState.ts';
|
||||||
import {createContext, ReactNode, useContext, useEffect, useRef, useState} from 'react';
|
import {createContext, ReactNode, useContext, useEffect, useRef, useState} from 'react';
|
||||||
|
|
||||||
export type HSL = {
|
type HSL = {
|
||||||
h: number;
|
h: number;
|
||||||
s: number;
|
s: number;
|
||||||
l: number;
|
l: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Theme = {
|
export type HslTheme = {
|
||||||
primary: HSL;
|
primary: HSL;
|
||||||
secondary: HSL;
|
secondary: HSL;
|
||||||
background: HSL;
|
background: HSL;
|
||||||
|
@ -44,7 +44,7 @@ function getRandomHsl(params: {
|
||||||
return {h, s, l};
|
return {h, s, l};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hslToString({h, s, l}: HSL) {
|
function hslToString({h, s, l}: HSL) {
|
||||||
return `hsl(${h},${s}%,${l}%)`;
|
return `hsl(${h},${s}%,${l}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -52,7 +52,7 @@ function angle(a: number) {
|
||||||
return ((a % 360.0) + 360) % 360;
|
return ((a % 360.0) + 360) % 360;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getRandomTheme(): Theme {
|
export function getRandomTheme(): HslTheme {
|
||||||
const goldenAngle = 180 * (3 - Math.sqrt(5));
|
const goldenAngle = 180 * (3 - Math.sqrt(5));
|
||||||
|
|
||||||
const background = getRandomHsl({maxSaturation: 50, minLightness: 10, maxLightness: 30});
|
const background = getRandomHsl({maxSaturation: 50, minLightness: 10, maxLightness: 30});
|
||||||
|
@ -76,22 +76,13 @@ export function getRandomTheme(): Theme {
|
||||||
return {background, primary, secondary, tertiary};
|
return {background, primary, secondary, tertiary};
|
||||||
}
|
}
|
||||||
|
|
||||||
function applyTheme(theme: Theme) {
|
function applyTheme(theme: HslTheme) {
|
||||||
console.log('apply theme', theme);
|
console.log('apply theme', theme);
|
||||||
rootStyle.setProperty('--color-primary', hslToString(theme.primary));
|
rootStyle.setProperty('--color-primary', hslToString(theme.primary));
|
||||||
rootStyle.setProperty('--color-secondary', hslToString(theme.secondary));
|
rootStyle.setProperty('--color-secondary', hslToString(theme.secondary));
|
||||||
rootStyle.setProperty('--color-background', hslToString(theme.background));
|
rootStyle.setProperty('--color-background', hslToString(theme.background));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useStoredTheme() {
|
|
||||||
return useStoredObjectState<Theme>('theme', getRandomTheme, {
|
|
||||||
load: applyTheme,
|
|
||||||
save: applyTheme
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ThemeContext = createContext<Theme>(getRandomTheme());
|
|
||||||
|
|
||||||
type Rgba = Uint8ClampedArray;
|
type Rgba = Uint8ClampedArray;
|
||||||
|
|
||||||
export type RgbaTheme = {
|
export type RgbaTheme = {
|
||||||
|
@ -108,20 +99,40 @@ const dummyRgbaTheme: RgbaTheme = {
|
||||||
tertiary: new Uint8ClampedArray([0, 0, 0, 0])
|
tertiary: new Uint8ClampedArray([0, 0, 0, 0])
|
||||||
};
|
};
|
||||||
|
|
||||||
const RgbaThemeContext = createContext<RgbaTheme>(dummyRgbaTheme);
|
function hslToRgba(context: CanvasRenderingContext2D, color: HSL) {
|
||||||
|
|
||||||
function normalizeColor(context: CanvasRenderingContext2D, color: HSL) {
|
|
||||||
context.fillStyle = hslToString(color);
|
context.fillStyle = hslToString(color);
|
||||||
context.fillRect(0, 0, 1, 1);
|
context.fillRect(0, 0, 1, 1);
|
||||||
return context.getImageData(0, 0, 1, 1).data;
|
return context.getImageData(0, 0, 1, 1).data;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useRgbaThemeContext() {
|
|
||||||
|
const HslThemeContext = createContext<null | {
|
||||||
|
hslTheme: HslTheme,
|
||||||
|
setHslTheme: (mutator: (oldState: HslTheme) => HslTheme) => void
|
||||||
|
}>(null);
|
||||||
|
const RgbaThemeContext = createContext<RgbaTheme>(dummyRgbaTheme);
|
||||||
|
|
||||||
|
export function useRgbaTheme() {
|
||||||
return useContext(RgbaThemeContext);
|
return useContext(RgbaThemeContext);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function RgbaThemeProvider({children}: { children: ReactNode }) {
|
export function useHslTheme() {
|
||||||
const hslTheme = useContext(ThemeContext);
|
const context = useContext(HslThemeContext);
|
||||||
|
if (context === null) {
|
||||||
|
throw new Error();
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function ThemeProvider({children}: {
|
||||||
|
children?: ReactNode;
|
||||||
|
}) {
|
||||||
|
const [hslTheme, setHslTheme] = useStoredObjectState<HslTheme>('theme', getRandomTheme, {
|
||||||
|
load: applyTheme,
|
||||||
|
save: applyTheme
|
||||||
|
});
|
||||||
|
|
||||||
const [rgbaTheme, setRgbaTheme] = useState<RgbaTheme | null>(null);
|
const [rgbaTheme, setRgbaTheme] = useState<RgbaTheme | null>(null);
|
||||||
const canvasRef = useRef<HTMLCanvasElement>(null);
|
const canvasRef = useRef<HTMLCanvasElement>(null);
|
||||||
|
|
||||||
|
@ -139,15 +150,17 @@ export function RgbaThemeProvider({children}: { children: ReactNode }) {
|
||||||
throw new Error('could not get draw context');
|
throw new Error('could not get draw context');
|
||||||
|
|
||||||
setRgbaTheme({
|
setRgbaTheme({
|
||||||
background: normalizeColor(drawContext, hslTheme.background),
|
background: hslToRgba(drawContext, hslTheme.background),
|
||||||
primary: normalizeColor(drawContext, hslTheme.primary),
|
primary: hslToRgba(drawContext, hslTheme.primary),
|
||||||
secondary: normalizeColor(drawContext, hslTheme.secondary),
|
secondary: hslToRgba(drawContext, hslTheme.secondary),
|
||||||
tertiary: normalizeColor(drawContext, hslTheme.tertiary),
|
tertiary: hslToRgba(drawContext, hslTheme.tertiary),
|
||||||
});
|
});
|
||||||
}, [hslTheme, canvasRef.current]);
|
}, [hslTheme, canvasRef.current]);
|
||||||
|
|
||||||
return <RgbaThemeContext.Provider value={rgbaTheme || dummyRgbaTheme}>
|
return <HslThemeContext.Provider value={{hslTheme, setHslTheme}}>
|
||||||
<canvas hidden={true} ref={canvasRef}/>
|
<RgbaThemeContext.Provider value={rgbaTheme || dummyRgbaTheme}>
|
||||||
{children}
|
<canvas hidden={true} ref={canvasRef}/>
|
||||||
</RgbaThemeContext.Provider>;
|
{children}
|
||||||
|
</RgbaThemeContext.Provider>;
|
||||||
|
</HslThemeContext.Provider>;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue