basic theme editor
This commit is contained in:
parent
4960df370c
commit
bacd1777be
|
@ -3,7 +3,7 @@ import {useMutation} from '@tanstack/react-query';
|
||||||
|
|
||||||
import {makeApiUrl} from './serverCalls';
|
import {makeApiUrl} from './serverCalls';
|
||||||
import Button from './components/Button.tsx';
|
import Button from './components/Button.tsx';
|
||||||
import TextInput from './components/TextInput.tsx';
|
import {TextInput} from './components/Input.tsx';
|
||||||
import Dialog from './components/Dialog.tsx';
|
import Dialog from './components/Dialog.tsx';
|
||||||
import './JoinForm.css';
|
import './JoinForm.css';
|
||||||
|
|
||||||
|
@ -30,8 +30,7 @@ export default function JoinForm({onDone}: {
|
||||||
|
|
||||||
const confirm = () => postPlayer.mutate({name});
|
const confirm = () => postPlayer.mutate({name});
|
||||||
|
|
||||||
return <Dialog className="JoinForm">
|
return <Dialog className="JoinForm" title="Enter your name to play">
|
||||||
<h3> Enter your name to play </h3>
|
|
||||||
<TextInput
|
<TextInput
|
||||||
value={name}
|
value={name}
|
||||||
placeholder="player name"
|
placeholder="player name"
|
||||||
|
|
|
@ -57,8 +57,7 @@ function MapChooserDialog({mapNames, onClose, onConfirm}: {
|
||||||
readonly onClose: () => void;
|
readonly onClose: () => void;
|
||||||
}) {
|
}) {
|
||||||
const [chosenMap, setChosenMap] = useState<string>();
|
const [chosenMap, setChosenMap] = useState<string>();
|
||||||
return <Dialog>
|
return <Dialog title='Choose a map' onClose={onClose}>
|
||||||
<h3>Choose a map</h3>
|
|
||||||
<Row className="MapChooser-Row overflow-scroll">
|
<Row className="MapChooser-Row overflow-scroll">
|
||||||
{mapNames.map(name => <MapPreview
|
{mapNames.map(name => <MapPreview
|
||||||
key={name}
|
key={name}
|
||||||
|
|
|
@ -1,11 +1,79 @@
|
||||||
import Button from './components/Button.tsx';
|
import Button from './components/Button.tsx';
|
||||||
import {getRandomTheme, useHslTheme} from './theme.tsx';
|
import {getRandomTheme, HSL, hslToString, useHslTheme} from './theme.tsx';
|
||||||
|
import Dialog from './components/Dialog.tsx';
|
||||||
|
import {useState} from 'react';
|
||||||
|
import {NumberInput} from './components/Input.tsx';
|
||||||
|
import Row from './components/Row.tsx';
|
||||||
|
import Column from './components/Column.tsx';
|
||||||
|
|
||||||
|
function HslEditor({name, value, setValue}: {
|
||||||
|
name: string;
|
||||||
|
value: HSL;
|
||||||
|
setValue: (value: HSL) => void
|
||||||
|
}) {
|
||||||
|
return <Column>
|
||||||
|
<Row>
|
||||||
|
<div className="" style={{background: hslToString(value), border: '1px solid white', aspectRatio: '1'}}/>
|
||||||
|
<p>{name}</p>
|
||||||
|
</Row>
|
||||||
|
<div style={{
|
||||||
|
display: 'grid',
|
||||||
|
columnGap: 'var(--padding-normal)',
|
||||||
|
gridTemplateColumns: 'auto auto',
|
||||||
|
gridTemplateRows: 'auto'
|
||||||
|
}}>
|
||||||
|
<p>Hue</p>
|
||||||
|
<NumberInput value={Math.round(value.h)} placeholder="Hue" onChange={event => {
|
||||||
|
setValue({...value, h: parseInt(event.target.value)});
|
||||||
|
}}/>
|
||||||
|
|
||||||
|
<p>Saturation</p>
|
||||||
|
<NumberInput value={Math.round(value.s)} placeholder="Saturation" onChange={event => {
|
||||||
|
setValue({...value, s: parseInt(event.target.value)});
|
||||||
|
}}/>
|
||||||
|
|
||||||
|
<p>Lightness</p>
|
||||||
|
<NumberInput value={Math.round(value.l)} placeholder="Lightness" onChange={event => {
|
||||||
|
setValue({...value, l: parseInt(event.target.value)});
|
||||||
|
}}/>
|
||||||
|
</div>
|
||||||
|
</Column>;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ThemeChooserDialog({onClose}: {
|
||||||
|
onClose: () => void;
|
||||||
|
}) {
|
||||||
|
const {hslTheme, setHslTheme} = useHslTheme();
|
||||||
|
return <Dialog title="Theme editor" onClose={onClose}>
|
||||||
|
<Button
|
||||||
|
text="surprise me"
|
||||||
|
onClick={() => setHslTheme(_ => getRandomTheme())}/>
|
||||||
|
<HslEditor
|
||||||
|
name="background"
|
||||||
|
value={hslTheme.background}
|
||||||
|
setValue={value => setHslTheme(old => ({...old, background: value}))}/>
|
||||||
|
<HslEditor
|
||||||
|
name="primary"
|
||||||
|
value={hslTheme.primary}
|
||||||
|
setValue={value => setHslTheme(old => ({...old, primary: value}))}/>
|
||||||
|
<HslEditor
|
||||||
|
name="secondary"
|
||||||
|
value={hslTheme.secondary}
|
||||||
|
setValue={value => setHslTheme(old => ({...old, secondary: value}))}/>
|
||||||
|
<HslEditor
|
||||||
|
name="background"
|
||||||
|
value={hslTheme.tertiary}
|
||||||
|
setValue={value => setHslTheme(old => ({...old, tertiary: value}))}/>
|
||||||
|
</Dialog>;
|
||||||
|
}
|
||||||
|
|
||||||
export default function ThemeChooser({}: {}) {
|
export default function ThemeChooser({}: {}) {
|
||||||
const {setHslTheme} = useHslTheme();
|
const [open, setOpen] = useState(false);
|
||||||
|
|
||||||
return <>
|
return <>
|
||||||
<Button
|
<Button
|
||||||
text="☼ change colors"
|
text="☼ change colors"
|
||||||
onClick={() => setHslTheme(_ => getRandomTheme())}/>
|
onClick={() => setOpen(true)}/>
|
||||||
|
{open && <ThemeChooserDialog onClose={() => setOpen(false)}/>}
|
||||||
</>;
|
</>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,12 +1,20 @@
|
||||||
import {ReactNode} from 'react';
|
import {ReactNode} from 'react';
|
||||||
import Column from './Column.tsx';
|
import Column from './Column.tsx';
|
||||||
|
import Row from './Row.tsx';
|
||||||
|
import Button from './Button.tsx';
|
||||||
import './Dialog.css';
|
import './Dialog.css';
|
||||||
|
|
||||||
export default function Dialog({children, className}: {
|
export default function Dialog({children, className, title, onClose}: {
|
||||||
children: ReactNode;
|
title?: string;
|
||||||
|
children?: ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
|
onClose?: () => void;
|
||||||
}) {
|
}) {
|
||||||
return <Column className={'Dialog overflow-scroll ' + (className ?? '')}>
|
return <Column className={'Dialog overflow-scroll ' + (className ?? '')}>
|
||||||
|
<Row>
|
||||||
|
<h3 className='flex-grow'>{title}</h3>
|
||||||
|
{onClose && <Button text='x' onClick={onClose} />}
|
||||||
|
</Row>
|
||||||
{children}
|
{children}
|
||||||
</Column>
|
</Column>
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
.TextInput {
|
.Input {
|
||||||
padding: var(--padding-normal);
|
padding: var(--padding-normal);
|
||||||
}
|
}
|
45
tank-frontend/src/components/Input.tsx
Normal file
45
tank-frontend/src/components/Input.tsx
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
import {ChangeEventHandler} from "react";
|
||||||
|
import './Input.css';
|
||||||
|
|
||||||
|
export function TextInput( {onChange, className, value, placeholder, onEnter }: {
|
||||||
|
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
|
||||||
|
className?: string;
|
||||||
|
value: string;
|
||||||
|
placeholder: string;
|
||||||
|
onEnter?: () => void;
|
||||||
|
}) {
|
||||||
|
return <input
|
||||||
|
type="text"
|
||||||
|
className={'Input ' + (className?? '')}
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyUp={event => {
|
||||||
|
if (onEnter && event.key === 'Enter')
|
||||||
|
onEnter();
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function NumberInput( {onChange, className, value, placeholder, onEnter }: {
|
||||||
|
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
|
||||||
|
className?: string;
|
||||||
|
value: number;
|
||||||
|
placeholder: string;
|
||||||
|
onEnter?: () => void;
|
||||||
|
}) {
|
||||||
|
return <input
|
||||||
|
type="number"
|
||||||
|
className={'Input ' + (className?? '')}
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
onChange={onChange}
|
||||||
|
onKeyUp={event => {
|
||||||
|
if (onEnter && event.key === 'Enter')
|
||||||
|
onEnter();
|
||||||
|
}}
|
||||||
|
/>;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
import {ReactNode} from "react";
|
import {ReactNode} from 'react';
|
||||||
|
|
||||||
import './Row.css';
|
import './Row.css';
|
||||||
|
|
||||||
export default function Row({children, className}: { children: ReactNode, className?: string }) {
|
export default function Row({children, className}: {
|
||||||
|
children?: ReactNode;
|
||||||
|
className?: string;
|
||||||
|
}) {
|
||||||
return <div className={'Row flex-row ' + (className ?? '')}>
|
return <div className={'Row flex-row ' + (className ?? '')}>
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
import {ChangeEventHandler} from "react";
|
|
||||||
import './TextInput.css';
|
|
||||||
|
|
||||||
export default function TextInput( {onChange, className, value, placeholder, onEnter }: {
|
|
||||||
onChange?: ChangeEventHandler<HTMLInputElement> | undefined;
|
|
||||||
className?: string;
|
|
||||||
value: string;
|
|
||||||
placeholder: string;
|
|
||||||
onEnter?: () => void;
|
|
||||||
}) {
|
|
||||||
return <input
|
|
||||||
type="text"
|
|
||||||
className={'TextInput ' + (className?? '')}
|
|
||||||
value={value}
|
|
||||||
placeholder={placeholder}
|
|
||||||
onChange={onChange}
|
|
||||||
onKeyUp={event => {
|
|
||||||
if (onEnter && event.key === 'Enter')
|
|
||||||
onEnter();
|
|
||||||
}}
|
|
||||||
/>;
|
|
||||||
}
|
|
|
@ -1,7 +1,7 @@
|
||||||
import {useStoredObjectState} from './useStoredState.ts';
|
import {useStoredObjectState} from './useStoredState.ts';
|
||||||
import {createContext, ReactNode, useContext, useMemo, useRef, useState} from 'react';
|
import {createContext, ReactNode, useContext, useMemo, useRef, useState} from 'react';
|
||||||
|
|
||||||
type HSL = {
|
export type HSL = {
|
||||||
h: number;
|
h: number;
|
||||||
s: number;
|
s: number;
|
||||||
l: number;
|
l: number;
|
||||||
|
@ -53,7 +53,7 @@ function getRandomHsl(params: {
|
||||||
return {h, s, l};
|
return {h, s, l};
|
||||||
}
|
}
|
||||||
|
|
||||||
function hslToString({h, s, l}: HSL) {
|
export function hslToString({h, s, l}: HSL) {
|
||||||
return `hsl(${h},${s}%,${l}%)`;
|
return `hsl(${h},${s}%,${l}%)`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue