store theme, improved random theme

This commit is contained in:
Vinzenz Schroeter 2024-04-14 14:55:01 +02:00
parent 52e09ae5ef
commit 16d3cd1545
5 changed files with 81 additions and 47 deletions

View file

@ -10,6 +10,7 @@ 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 './App.css'; import './App.css';
import {getRandomTheme, useStoredTheme} from "./theme.ts";
const getNewNameId = () => ({ const getNewNameId = () => ({
id: crypto.randomUUID(), id: crypto.randomUUID(),
@ -17,6 +18,7 @@ const getNewNameId = () => ({
}); });
export default function App() { export default function App() {
const [theme, setTheme] = useStoredTheme();
const [nameId, setNameId] = useStoredObjectState<NameId>('access', getNewNameId); const [nameId, setNameId] = useStoredObjectState<NameId>('access', getNewNameId);
const [isLoggedIn, setLoggedIn] = useState<boolean>(false); const [isLoggedIn, setLoggedIn] = useState<boolean>(false);
@ -32,13 +34,14 @@ export default function App() {
return <Column className='flex-grow'> return <Column className='flex-grow'>
<Row> <Row>
<h1 className='flex-grow'>CCCB-Tanks!</h1> <h1 className='flex-grow'>CCCB-Tanks!</h1>
<Button text='change colors' onClick={() => setTheme(getRandomTheme())} />
<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='GitHub'/> text='GitHub'/>
{nameId.name !== '' && {nameId.name !== '' &&
<Button onClick={() => setNameId(getNewNameId)} text='logout'/>} <Button onClick={() => setNameId(getNewNameId)} text='logout'/>}
</Row> </Row>
<ClientScreen logout={logout}/> <ClientScreen logout={logout} theme={theme}/>
{nameId.name === '' && <JoinForm setNameId={setNameId} clientId={nameId.id}/>} {nameId.name === '' && <JoinForm setNameId={setNameId} clientId={nameId.id}/>}
{isLoggedIn && <Row className='GadgetRows'> {isLoggedIn && <Row className='GadgetRows'>
<Controls playerId={nameId.id} logout={logout}/> <Controls playerId={nameId.id} logout={logout}/>

View file

@ -1,7 +1,7 @@
import useWebSocket from 'react-use-websocket'; import useWebSocket from 'react-use-websocket';
import {useEffect, useRef} from 'react'; import {useEffect, useRef} from 'react';
import './ClientScreen.css'; import './ClientScreen.css';
import {getTheme} from "./theme.ts"; import {hslToString, Theme} from "./theme.ts";
const pixelsPerRow = 352; const pixelsPerRow = 352;
const pixelsPerCol = 160; const pixelsPerCol = 160;
@ -19,14 +19,13 @@ function normalizeColor(context: CanvasRenderingContext2D, color: string) {
return context.getImageData(0, 0, 1, 1).data; return context.getImageData(0, 0, 1, 1).data;
} }
function drawPixelsToCanvas(pixels: Uint8Array, canvas: HTMLCanvasElement) { function drawPixelsToCanvas(pixels: Uint8Array, canvas: HTMLCanvasElement, theme: Theme) {
const drawContext = canvas.getContext('2d'); const drawContext = canvas.getContext('2d');
if (!drawContext) if (!drawContext)
throw new Error('could not get draw context'); throw new Error('could not get draw context');
const theme = getTheme(); const colorPrimary = normalizeColor(drawContext, hslToString(theme.primary));
const colorPrimary = normalizeColor(drawContext, theme.primary); const colorBackground = normalizeColor(drawContext, hslToString(theme.background));
const colorBackground = normalizeColor(drawContext, theme.background);
const imageData = drawContext.getImageData(0, 0, canvas.width, canvas.height, {colorSpace: 'srgb'}); const imageData = drawContext.getImageData(0, 0, canvas.width, canvas.height, {colorSpace: 'srgb'});
const data = imageData.data; const data = imageData.data;
@ -48,7 +47,7 @@ function drawPixelsToCanvas(pixels: Uint8Array, canvas: HTMLCanvasElement) {
drawContext.putImageData(imageData, 0, 0); drawContext.putImageData(imageData, 0, 0);
} }
export default function ClientScreen({logout}: { logout: () => void }) { export default function ClientScreen({logout, theme}: { logout: () => void, theme: Theme }) {
const canvasRef = useRef<HTMLCanvasElement>(null); const canvasRef = useRef<HTMLCanvasElement>(null);
const { const {
@ -69,9 +68,9 @@ export default function ClientScreen({logout}: { logout: () => void }) {
if (canvasRef.current === null) if (canvasRef.current === null)
throw new Error('canvas null'); throw new Error('canvas null');
drawPixelsToCanvas(new Uint8Array(lastMessage.data), canvasRef.current); drawPixelsToCanvas(new Uint8Array(lastMessage.data), canvasRef.current, theme);
sendMessage(''); sendMessage('');
}, [lastMessage, canvasRef.current]); }, [lastMessage, canvasRef.current, theme]);
return <canvas ref={canvasRef} id="screen" width={pixelsPerRow} height={pixelsPerCol}/>; return <canvas ref={canvasRef} id="screen" width={pixelsPerRow} height={pixelsPerCol}/>;
} }

View file

@ -22,8 +22,8 @@ export default function Scoreboard({}: {}) {
className='flex-grow' className='flex-grow'
columns={[ columns={[
{field: 'name'}, {field: 'name'},
{field: 'kills', visualize: p => p.scores.kills}, {field: 'kills', visualize: p => p.scores.kills.toString()},
{field: 'deaths', visualize: p => p.scores.deaths}, {field: 'deaths', visualize: p => p.scores.deaths.toString()},
{field: 'k/d', visualize: p => p.scores.kills / p.scores.deaths} {field: 'k/d', visualize: p => (p.scores.kills / p.scores.deaths).toString()}
]}/> ]}/>
} }

View file

@ -2,10 +2,6 @@ import React from 'react';
import {createRoot} from 'react-dom/client'; import {createRoot} from 'react-dom/client';
import './index.css'; import './index.css';
import App from "./App.tsx"; import App from "./App.tsx";
import {setRandomTheme} from "./theme.ts";
setRandomTheme();
createRoot(document.getElementById('root')!).render( createRoot(document.getElementById('root')!).render(
<React.StrictMode> <React.StrictMode>

View file

@ -1,44 +1,80 @@
function getRandomColor() { import {useStoredObjectState} from "./useStoredState.ts";
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 3; i++) {
color += letters[Math.floor(Math.random() * letters.length)];
}
return color;
}
export type Theme = { export type Theme = {
primary?: string; primary: HSL;
secondary?: string; secondary: HSL;
background?: string; background: HSL;
} }
// @ts-ignore // @ts-ignore
const rootStyle = document.querySelector(':root')?.style; const rootStyle = document.querySelector(':root')?.style;
export function setTheme(theme: Theme) { function getRandom(min: number, max: number) {
if (!rootStyle) return min + Math.random() * (max - min);
return;
rootStyle.setProperty('--color-primary', theme.primary);
rootStyle.setProperty('--color-secondary', theme.secondary);
rootStyle.setProperty('--color-background', theme.background);
} }
export function getTheme(): Theme { type HSL = {
if (!rootStyle) h: number;
return {}; s: number;
return { l: number;
primary: rootStyle.getPropertyValue('--color-primary'), }
secondary: rootStyle.getPropertyValue('--color-secondary'),
background: rootStyle.getPropertyValue('--color-background') function getRandomHsl(params: {
minHue?: number,
maxHue?: number,
minSaturation?: number,
maxSaturation?: number,
minLightness?: number,
maxLightness?: number,
}): HSL {
const values = {
minHue: 0,
maxHue: 360,
minSaturation: 0,
maxSaturation: 100,
minLightness: 0,
maxLightness: 100,
...params
}; };
const h = getRandom(values.minHue, values.maxHue);
const s = getRandom(values.minSaturation, values.maxSaturation);
const l = getRandom(values.minLightness, values.maxLightness);
return {h, s, l};
} }
export function setRandomTheme() { export function hslToString({h, s, l}: HSL) {
setTheme({ return `hsl(${h},${s}%,${l}%)`;
primary: getRandomColor(), }
secondary: getRandomColor(),
background: getRandomColor() function angle(a: number) {
}) return ((a % 360.0) + 360) % 360;
}
const goldenAngle = 180 * (3 - Math.sqrt(5));
export function getRandomTheme(): Theme {
const background = getRandomHsl({maxSaturation: 50, minLightness: 10, maxLightness: 40});
const primary = getRandomHsl({minSaturation: background.s * 1.2, minLightness: background.l + 20});
primary.h = angle(-Math.floor(1 + Math.random() * 2) * goldenAngle + primary.h);
const secondary = getRandomHsl({minSaturation: background.s * 1.2, minLightness: background.l + 20});
primary.h = angle(+Math.floor(1 + Math.random() * 2) * goldenAngle + primary.h);
return {background, primary, secondary};
}
export function useStoredTheme(): [Theme, (theme: Theme) => void] {
const [theme, setSavedTheme] = useStoredObjectState<Theme>('theme', getRandomTheme);
function setTheme(theme: Theme) {
console.log('set theme', theme);
rootStyle.setProperty('--color-primary', hslToString(theme.primary));
rootStyle.setProperty('--color-secondary', hslToString(theme.secondary));
rootStyle.setProperty('--color-background', hslToString(theme.background));
setSavedTheme(_ => theme);
}
return [theme, setTheme];
} }