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

View file

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

View file

@ -22,8 +22,8 @@ export default function Scoreboard({}: {}) {
className='flex-grow'
columns={[
{field: 'name'},
{field: 'kills', visualize: p => p.scores.kills},
{field: 'deaths', visualize: p => p.scores.deaths},
{field: 'k/d', visualize: p => p.scores.kills / p.scores.deaths}
{field: 'kills', visualize: p => p.scores.kills.toString()},
{field: 'deaths', visualize: p => p.scores.deaths.toString()},
{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 './index.css';
import App from "./App.tsx";
import {setRandomTheme} from "./theme.ts";
setRandomTheme();
createRoot(document.getElementById('root')!).render(
<React.StrictMode>

View file

@ -1,44 +1,80 @@
function getRandomColor() {
const letters = '0123456789ABCDEF';
let color = '#';
for (let i = 0; i < 3; i++) {
color += letters[Math.floor(Math.random() * letters.length)];
}
return color;
}
import {useStoredObjectState} from "./useStoredState.ts";
export type Theme = {
primary?: string;
secondary?: string;
background?: string;
primary: HSL;
secondary: HSL;
background: HSL;
}
// @ts-ignore
const rootStyle = document.querySelector(':root')?.style;
export function setTheme(theme: Theme) {
if (!rootStyle)
return;
rootStyle.setProperty('--color-primary', theme.primary);
rootStyle.setProperty('--color-secondary', theme.secondary);
rootStyle.setProperty('--color-background', theme.background);
function getRandom(min: number, max: number) {
return min + Math.random() * (max - min);
}
export function getTheme(): Theme {
if (!rootStyle)
return {};
return {
primary: rootStyle.getPropertyValue('--color-primary'),
secondary: rootStyle.getPropertyValue('--color-secondary'),
background: rootStyle.getPropertyValue('--color-background')
type HSL = {
h: number;
s: number;
l: number;
}
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() {
setTheme({
primary: getRandomColor(),
secondary: getRandomColor(),
background: getRandomColor()
})
export function hslToString({h, s, l}: HSL) {
return `hsl(${h},${s}%,${l}%)`;
}
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];
}