chore: format with prettier
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Ashhhleyyy 2022-11-10 14:58:38 +00:00
parent 3e4434c0bc
commit b2d88bd1c2
Signed by: ash
GPG key ID: 83B789081A0878FB
14 changed files with 324 additions and 283 deletions

View file

@ -21,6 +21,6 @@ module.exports = {
'@typescript-eslint/no-non-null-assertion': 'off', '@typescript-eslint/no-non-null-assertion': 'off',
}, },
settings: { settings: {
jest: { version: 29 } jest: { version: 29 },
} },
}; };

View file

@ -1,22 +1,25 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta name="description" content="Secret santa name drawing" /> <meta name="description" content="Secret santa name drawing" />
<title>SecSan</title> <title>SecSan</title>
<link rel="icon" href="/favicon.png" /> <link rel="icon" href="/favicon.png" />
<link <link
rel="apple-touch-icon" rel="apple-touch-icon"
href="/apple-touch-icon.png" href="/apple-touch-icon.png"
sizes="180x180" sizes="180x180"
/> />
<meta name="theme-color" content="#ffffff" /> <meta name="theme-color" content="#ffffff" />
<link rel="preconnect" href="https://fonts.bunny.net"> <link rel="preconnect" href="https://fonts.bunny.net" />
<link href="https://fonts.bunny.net/css?family=ubuntu:500" rel="stylesheet" /> <link
</head> href="https://fonts.bunny.net/css?family=ubuntu:500"
<body> rel="stylesheet"
<div id="app"></div> />
<script type="module" src="/src/main.tsx"></script> </head>
</body> <body>
<div id="app"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html> </html>

View file

@ -1,32 +1,32 @@
{ {
"name": "secsan", "name": "secsan",
"private": true, "private": true,
"version": "0.0.0", "version": "0.0.0",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "vite", "dev": "vite",
"build": "tsc && vite build", "build": "tsc && vite build",
"preview": "vite preview", "preview": "vite preview",
"test": "vitest", "test": "vitest",
"format": "prettier --write .", "format": "prettier --write .",
"format:check": "prettier --check .", "format:check": "prettier --check .",
"lint": "eslint . --max-warnings 0" "lint": "eslint . --max-warnings 0"
}, },
"dependencies": { "dependencies": {
"@preact/signals": "^1.1.2", "@preact/signals": "^1.1.2",
"preact": "^10.11.2" "preact": "^10.11.2"
}, },
"devDependencies": { "devDependencies": {
"@preact/preset-vite": "^2.4.0", "@preact/preset-vite": "^2.4.0",
"@types/node": "^18.11.9", "@types/node": "^18.11.9",
"@typescript-eslint/eslint-plugin": "^5.42.1", "@typescript-eslint/eslint-plugin": "^5.42.1",
"@typescript-eslint/parser": "^5.42.1", "@typescript-eslint/parser": "^5.42.1",
"eslint": "^8.27.0", "eslint": "^8.27.0",
"eslint-config-preact": "^1.3.0", "eslint-config-preact": "^1.3.0",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"typescript": "^4.6.4", "typescript": "^4.6.4",
"vite": "^3.2.3", "vite": "^3.2.3",
"vite-plugin-pwa": "^0.13.3", "vite-plugin-pwa": "^0.13.3",
"vitest": "^0.25.1" "vitest": "^0.25.1"
} }
} }

View file

@ -1,188 +1,188 @@
:root { :root {
--background: #13092b; --background: #13092b;
--background-transparent: #13092baa; --background-transparent: #13092baa;
--background-2: #090f2b; --background-2: #090f2b;
--foreground: #ddd; --foreground: #ddd;
--foreground-bright: #fff; --foreground-bright: #fff;
--foreground-dim: #aaa; --foreground-dim: #aaa;
--error: orange; --error: orange;
--accent: #f9027a; --accent: #f9027a;
--accent-dim: hsl(331, 50%, 49%); --accent-dim: hsl(331, 50%, 49%);
--primary: #8da1ee; --primary: #8da1ee;
--confirm: #82f1b1; --confirm: #82f1b1;
--warning: #fb7185; --warning: #fb7185;
} }
body { body {
font-family: 'Ubuntu', 'Noto Sans', sans-serif; font-family: 'Ubuntu', 'Noto Sans', sans-serif;
font-size: 24px; font-size: 24px;
background-color: var(--background); background-color: var(--background);
background-image: url('https://ashhhleyyy.dev/assets-gen/background.svg'); background-image: url('https://ashhhleyyy.dev/assets-gen/background.svg');
color: var(--foreground); color: var(--foreground);
margin: 0; margin: 0;
box-sizing: border-box; box-sizing: border-box;
display: flex; display: flex;
align-items: center; align-items: center;
flex-direction: column; flex-direction: column;
min-height: 100vh; min-height: 100vh;
max-width: 100vw; max-width: 100vw;
} }
* { * {
box-sizing: inherit; box-sizing: inherit;
} }
@media (prefers-reduced-motion: no-preference) { @media (prefers-reduced-motion: no-preference) {
html { html {
scroll-behavior: smooth; scroll-behavior: smooth;
} }
} }
main { main {
margin: 16px; margin: 16px;
padding: 8px; padding: 8px;
width: calc(100vw - 32px); width: calc(100vw - 32px);
max-width: 720px; max-width: 720px;
flex: 1; flex: 1;
background-color: var(--background-transparent); background-color: var(--background-transparent);
} }
a { a {
color: inherit; color: inherit;
font-family: inherit; font-family: inherit;
} }
h1 { h1 {
margin: 0; margin: 0;
display: flex; display: flex;
flex-direction: row; flex-direction: row;
align-items: center; align-items: center;
} }
h1::before { h1::before {
content: '# '; content: '# ';
margin-right: 16px; margin-right: 16px;
color: var(--foreground-dim); color: var(--foreground-dim);
font-family: 'JetBrains Mono', 'Oxygen Mono', monospace; font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
} }
h2::before { h2::before {
content: '## '; content: '## ';
color: var(--foreground-dim); color: var(--foreground-dim);
font-family: 'JetBrains Mono', 'Oxygen Mono', monospace; font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
} }
h3::before { h3::before {
content: '### '; content: '### ';
color: var(--foreground-dim); color: var(--foreground-dim);
font-family: 'JetBrains Mono', 'Oxygen Mono', monospace; font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
} }
h4::before { h4::before {
content: '#### '; content: '#### ';
color: var(--foreground-dim); color: var(--foreground-dim);
font-family: 'JetBrains Mono', 'Oxygen Mono', monospace; font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
} }
h5::before { h5::before {
content: '##### '; content: '##### ';
color: var(--foreground-dim); color: var(--foreground-dim);
font-family: 'JetBrains Mono', 'Oxygen Mono', monospace; font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
} }
h6::before { h6::before {
content: '###### '; content: '###### ';
color: var(--foreground-dim); color: var(--foreground-dim);
font-family: 'JetBrains Mono', 'Oxygen Mono', monospace; font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
} }
button { button {
border: none; border: none;
padding: 8px; padding: 8px;
border-radius: 8px; border-radius: 8px;
cursor: pointer; cursor: pointer;
text-decoration: none; text-decoration: none;
font-size: initial; font-size: initial;
color: black; color: black;
transition: filter 200ms ease; transition: filter 200ms ease;
vertical-align: middle; vertical-align: middle;
font-family: inherit; font-family: inherit;
} }
button:hover { button:hover {
filter: brightness(.8); filter: brightness(0.8);
} }
button.primary { button.primary {
background-color: var(--primary); background-color: var(--primary);
} }
button.confirm { button.confirm {
background-color: var(--confirm); background-color: var(--confirm);
} }
button.warning { button.warning {
background-color: var(--warning); background-color: var(--warning);
} }
button:disabled { button:disabled {
filter: saturate(25%) brightness(.85); filter: saturate(25%) brightness(0.85);
cursor: not-allowed; cursor: not-allowed;
} }
a.primary { a.primary {
color: var(--primary); color: var(--primary);
} }
a.confirm { a.confirm {
color: var(--confirm); color: var(--confirm);
} }
a.warning { a.warning {
color: var(--warning); color: var(--warning);
} }
input { input {
margin: 8px 0; margin: 8px 0;
background-color: rgba(128, 128, 128, .25); background-color: rgba(128, 128, 128, 0.25);
color: white; color: white;
border: #888 2px solid; border: #888 2px solid;
padding: 8px; padding: 8px;
border-radius: 8px; border-radius: 8px;
font-family: inherit; font-family: inherit;
font-size: medium; font-size: medium;
} }
input[type="text"]:focus-visible, input[type='text']:focus-visible,
input[type="password"]:focus-visible { input[type='password']:focus-visible {
outline: none; outline: none;
border-color: var(--accent); border-color: var(--accent);
} }
.button-row { .button-row {
display: flex; display: flex;
width: 100%; width: 100%;
flex-direction: row; flex-direction: row;
flex-wrap: wrap; flex-wrap: wrap;
gap: 8px; gap: 8px;
margin: 8px 0; margin: 8px 0;
} }
.button-row button { .button-row button {
flex: 1; flex: 1;
} }
.button-group { .button-group {
display: flex; display: flex;
width: 100%; width: 100%;
flex-direction: column; flex-direction: column;
gap: 8px; gap: 8px;
margin: 8px 0; margin: 8px 0;
} }
.drawn-name { .drawn-name {
font-size: 72px; font-size: 72px;
width: 100%; width: 100%;
display: grid; display: grid;
place-content: center; place-content: center;
} }

View file

@ -8,17 +8,21 @@ import { buildNameMap } from './shuffle';
const names = signal<string[]>([]); const names = signal<string[]>([]);
export function App() { export function App() {
const [shuffled, setShuffled] = useState<[string, string][] | null>(null); const [shuffled, setShuffled] = useState<[string, string][] | null>(null);
function start() { function start() {
setShuffled(buildNameMap(names.value)); setShuffled(buildNameMap(names.value));
} }
return ( return (
<main> <main>
<h1>Secret Santa</h1> <h1>Secret Santa</h1>
{shuffled ? <DrawName names={shuffled} /> : <EnterNames start={start} names={names} />} {shuffled ? (
</main> <DrawName names={shuffled} />
) ) : (
<EnterNames start={start} names={names} />
)}
</main>
);
} }

View file

@ -1,5 +1,5 @@
import { render } from 'preact' import { render } from 'preact';
import { App } from './app' import { App } from './app';
import './index.css' import './index.css';
render(<App />, document.getElementById('app') as HTMLElement) render(<App />, document.getElementById('app') as HTMLElement);

View file

@ -1,6 +1,6 @@
import { FunctionComponent } from "preact"; import { FunctionComponent } from 'preact';
import { useState } from "preact/hooks"; import { useState } from 'preact/hooks';
import { useWindowEvent } from "../util"; import { useWindowEvent } from '../util';
interface Props { interface Props {
names: [string, string][]; names: [string, string][];
@ -14,34 +14,44 @@ const DrawName: FunctionComponent<Props> = ({ names }) => {
if (e.button !== 0) return; if (e.button !== 0) return;
if (currentPerson !== null) { if (currentPerson !== null) {
setCompleted(completed => [...completed, currentPerson]) setCompleted((completed) => [...completed, currentPerson]);
setCurrentPerson(null); setCurrentPerson(null);
} }
}); });
if (currentPerson === null) { if (currentPerson === null) {
if (completed.length === names.length) { if (completed.length === names.length) {
return <> return (
<h2> <>
All done! <h2>All done!</h2>
</h2> </>
</> );
} }
return <> return (
<div class="button-group"> <>
{names.map(([name], i) => { <div class='button-group'>
if (completed.includes(i)) return null; {names.map(([name], i) => {
return <button key={`${i}-${name}`} class="primary" onClick={() => setCurrentPerson(i)}>{name}</button>; if (completed.includes(i)) return null;
})} return (
</div> <button
</> key={`${i}-${name}`}
class='primary'
onClick={() => setCurrentPerson(i)}
>
{name}
</button>
);
})}
</div>
</>
);
} }
return <> return (
<div class="drawn-name"> <>
{names[currentPerson][1]} <div class='drawn-name'>{names[currentPerson][1]}</div>
</div> </>
</> );
} };
export default DrawName; export default DrawName;

View file

@ -1,6 +1,6 @@
import { Signal } from "@preact/signals"; import { Signal } from '@preact/signals';
import { FunctionComponent } from "preact"; import { FunctionComponent } from 'preact';
import { useState } from "preact/hooks"; import { useState } from 'preact/hooks';
interface Props { interface Props {
names: Signal<string[]>; names: Signal<string[]>;
@ -20,33 +20,45 @@ const EnterNames: FunctionComponent<Props> = ({ names, start }) => {
setName(''); setName('');
} }
return <> return (
<ul> <>
{names.value.map((name, i) => <li key={`${i}-${name}`}> <ul>
{name} {names.value.map((name, i) => (
</li>)} <li key={`${i}-${name}`}>{name}</li>
</ul> ))}
</ul>
<div> <div>
<input <input
type="text" type='text'
placeholder="Name..." placeholder='Name...'
value={name} value={name}
onInput={(e) => setName((e.target! as HTMLInputElement).value)} onInput={(e) =>
onKeyPress={(e) => { setName((e.target! as HTMLInputElement).value)
if (e.key === 'Enter') {
addName();
e.preventDefault();
} }
}} onKeyPress={(e) => {
if (e.key === 'Enter') {
addName();
e.preventDefault();
}
}}
/> />
<div class="button-row"> <div class='button-row'>
<button class="primary" onClick={addName}>Add name</button> <button class='primary' onClick={addName}>
<button class='confirm' disabled={names.value.length <= 1} onClick={start}>Start drawing</button> Add name
</button>
<button
class='confirm'
disabled={names.value.length <= 1}
onClick={start}
>
Start drawing
</button>
</div>
</div> </div>
</div> </>
</> );
} };
export default EnterNames; export default EnterNames;

View file

@ -1,6 +1,7 @@
// https://stackoverflow.com/a/2450976 // https://stackoverflow.com/a/2450976
function shuffle<T>(array: T[]) { function shuffle<T>(array: T[]) {
let currentIndex = array.length, randomIndex; let currentIndex = array.length,
randomIndex: number;
// While there remain elements to shuffle. // While there remain elements to shuffle.
while (currentIndex != 0) { while (currentIndex != 0) {
@ -9,7 +10,10 @@ function shuffle<T>(array: T[]) {
currentIndex--; currentIndex--;
// And swap it with the current element. // And swap it with the current element.
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[currentIndex]]; [array[currentIndex], array[randomIndex]] = [
array[randomIndex],
array[currentIndex],
];
} }
return array; return array;
@ -44,7 +48,9 @@ function nonEqualShuffle<T>(input: T[]): T[] {
return nw; return nw;
} }
export function buildNameMap(names: string[]): [person: string, target: string][] { export function buildNameMap(
names: string[]
): [person: string, target: string][] {
const ret: [string, string][] = []; const ret: [string, string][] = [];
const shuffled = nonEqualShuffle(names); const shuffled = nonEqualShuffle(names);

View file

@ -1,6 +1,10 @@
import { useEffect } from "preact/hooks"; import { useEffect } from 'preact/hooks';
export function useWindowEvent<K extends keyof WindowEventMap>(type: K, listener: (this: Window, ev: WindowEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void { export function useWindowEvent<K extends keyof WindowEventMap>(
type: K,
listener: (this: Window, ev: WindowEventMap[K]) => void,
options?: boolean | AddEventListenerOptions
): void {
useEffect(() => { useEffect(() => {
window.addEventListener(type, listener, options); window.addEventListener(type, listener, options);
return () => window.removeEventListener(type, listener); return () => window.removeEventListener(type, listener);

View file

@ -2,7 +2,9 @@ import { expect, test } from 'vitest';
import { buildNameMap } from '../src/shuffle'; import { buildNameMap } from '../src/shuffle';
test('shuffle does not include matching pairs', () => { test('shuffle does not include matching pairs', () => {
const names = Array(10).fill(null).map((_, i) => `Test ${i}`); const names = Array(10)
.fill(null)
.map((_, i) => `Test ${i}`);
for (let i = 0; i < 50000; i++) { for (let i = 0; i < 50000; i++) {
const shuffled = buildNameMap(names); const shuffled = buildNameMap(names);
for (const pair of shuffled) { for (const pair of shuffled) {

View file

@ -1,22 +1,22 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"useDefineForClassFields": true, "useDefineForClassFields": true,
"lib": ["DOM", "DOM.Iterable", "ESNext"], "lib": ["DOM", "DOM.Iterable", "ESNext"],
"allowJs": false, "allowJs": false,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": false, "esModuleInterop": false,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node",
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"noEmit": true, "noEmit": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "preact" "jsxImportSource": "preact"
}, },
"include": ["src"], "include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]
} }

View file

@ -1,9 +1,9 @@
{ {
"compilerOptions": { "compilerOptions": {
"composite": true, "composite": true,
"module": "ESNext", "module": "ESNext",
"moduleResolution": "Node", "moduleResolution": "Node",
"allowSyntheticDefaultImports": true "allowSyntheticDefaultImports": true
}, },
"include": ["vite.config.ts"] "include": ["vite.config.ts"]
} }

View file

@ -4,34 +4,34 @@ import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/ // https://vitejs.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [ plugins: [
preact(), preact(),
VitePWA({ VitePWA({
registerType: 'autoUpdate', registerType: 'autoUpdate',
includeAssets: ['favicon.png', 'apple-touch-icon.png'], includeAssets: ['favicon.png', 'apple-touch-icon.png'],
manifest: { manifest: {
name: 'SecSan', name: 'SecSan',
short_name: 'SecSan', short_name: 'SecSan',
description: 'Secret santa name drawing', description: 'Secret santa name drawing',
theme_color: '#ffffff', theme_color: '#ffffff',
icons: [ icons: [
{ {
src: 'favicon.png', src: 'favicon.png',
sizes: '256x256', sizes: '256x256',
type: 'image/png', type: 'image/png',
}, },
{ {
src: 'favicon-192.png', src: 'favicon-192.png',
sizes: '192x192', sizes: '192x192',
type: 'image/png', type: 'image/png',
}, },
], ],
}, },
}), }),
], ],
server: { server: {
hmr: { hmr: {
clientPort: parseInt(process.env.CLIENT_PORT || '5173', 10), clientPort: parseInt(process.env.CLIENT_PORT || '5173', 10),
},
}, },
},
}); });