store theme, improved random theme
This commit is contained in:
parent
52e09ae5ef
commit
16d3cd1545
|
@ -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}/>
|
||||||
|
|
|
@ -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}/>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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()}
|
||||||
]}/>
|
]}/>
|
||||||
}
|
}
|
||||||
|
|
|
@ -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>
|
||||||
|
|
|
@ -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];
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue