diff --git a/.eslintrc.cjs b/.eslintrc.cjs
index 7bde1ee..9b2ec72 100644
--- a/.eslintrc.cjs
+++ b/.eslintrc.cjs
@@ -21,6 +21,6 @@ module.exports = {
'@typescript-eslint/no-non-null-assertion': 'off',
},
settings: {
- jest: { version: 29 }
- }
+ jest: { version: 29 },
+ },
};
diff --git a/index.html b/index.html
index a3fb8c9..26083ab 100644
--- a/index.html
+++ b/index.html
@@ -1,22 +1,25 @@
-
-
-
-
- SecSan
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+ SecSan
+
+
+
+
+
+
+
+
+
+
diff --git a/package.json b/package.json
index f96529e..58f7c0c 100644
--- a/package.json
+++ b/package.json
@@ -1,32 +1,32 @@
{
- "name": "secsan",
- "private": true,
- "version": "0.0.0",
- "type": "module",
- "scripts": {
- "dev": "vite",
- "build": "tsc && vite build",
- "preview": "vite preview",
- "test": "vitest",
- "format": "prettier --write .",
- "format:check": "prettier --check .",
- "lint": "eslint . --max-warnings 0"
- },
- "dependencies": {
- "@preact/signals": "^1.1.2",
- "preact": "^10.11.2"
- },
- "devDependencies": {
- "@preact/preset-vite": "^2.4.0",
- "@types/node": "^18.11.9",
- "@typescript-eslint/eslint-plugin": "^5.42.1",
- "@typescript-eslint/parser": "^5.42.1",
- "eslint": "^8.27.0",
- "eslint-config-preact": "^1.3.0",
- "prettier": "^2.7.1",
- "typescript": "^4.6.4",
- "vite": "^3.2.3",
- "vite-plugin-pwa": "^0.13.3",
- "vitest": "^0.25.1"
- }
-}
\ No newline at end of file
+ "name": "secsan",
+ "private": true,
+ "version": "0.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "tsc && vite build",
+ "preview": "vite preview",
+ "test": "vitest",
+ "format": "prettier --write .",
+ "format:check": "prettier --check .",
+ "lint": "eslint . --max-warnings 0"
+ },
+ "dependencies": {
+ "@preact/signals": "^1.1.2",
+ "preact": "^10.11.2"
+ },
+ "devDependencies": {
+ "@preact/preset-vite": "^2.4.0",
+ "@types/node": "^18.11.9",
+ "@typescript-eslint/eslint-plugin": "^5.42.1",
+ "@typescript-eslint/parser": "^5.42.1",
+ "eslint": "^8.27.0",
+ "eslint-config-preact": "^1.3.0",
+ "prettier": "^2.7.1",
+ "typescript": "^4.6.4",
+ "vite": "^3.2.3",
+ "vite-plugin-pwa": "^0.13.3",
+ "vitest": "^0.25.1"
+ }
+}
diff --git a/src/app.css b/src/app.css
index 3d21a7b..e947409 100644
--- a/src/app.css
+++ b/src/app.css
@@ -1,188 +1,188 @@
:root {
- --background: #13092b;
- --background-transparent: #13092baa;
- --background-2: #090f2b;
- --foreground: #ddd;
- --foreground-bright: #fff;
- --foreground-dim: #aaa;
- --error: orange;
- --accent: #f9027a;
- --accent-dim: hsl(331, 50%, 49%);
- --primary: #8da1ee;
- --confirm: #82f1b1;
- --warning: #fb7185;
+ --background: #13092b;
+ --background-transparent: #13092baa;
+ --background-2: #090f2b;
+ --foreground: #ddd;
+ --foreground-bright: #fff;
+ --foreground-dim: #aaa;
+ --error: orange;
+ --accent: #f9027a;
+ --accent-dim: hsl(331, 50%, 49%);
+ --primary: #8da1ee;
+ --confirm: #82f1b1;
+ --warning: #fb7185;
}
body {
- font-family: 'Ubuntu', 'Noto Sans', sans-serif;
- font-size: 24px;
- background-color: var(--background);
- background-image: url('https://ashhhleyyy.dev/assets-gen/background.svg');
- color: var(--foreground);
- margin: 0;
- box-sizing: border-box;
- display: flex;
- align-items: center;
- flex-direction: column;
- min-height: 100vh;
- max-width: 100vw;
+ font-family: 'Ubuntu', 'Noto Sans', sans-serif;
+ font-size: 24px;
+ background-color: var(--background);
+ background-image: url('https://ashhhleyyy.dev/assets-gen/background.svg');
+ color: var(--foreground);
+ margin: 0;
+ box-sizing: border-box;
+ display: flex;
+ align-items: center;
+ flex-direction: column;
+ min-height: 100vh;
+ max-width: 100vw;
}
* {
- box-sizing: inherit;
+ box-sizing: inherit;
}
@media (prefers-reduced-motion: no-preference) {
- html {
- scroll-behavior: smooth;
- }
+ html {
+ scroll-behavior: smooth;
+ }
}
main {
- margin: 16px;
- padding: 8px;
- width: calc(100vw - 32px);
- max-width: 720px;
- flex: 1;
- background-color: var(--background-transparent);
+ margin: 16px;
+ padding: 8px;
+ width: calc(100vw - 32px);
+ max-width: 720px;
+ flex: 1;
+ background-color: var(--background-transparent);
}
a {
- color: inherit;
- font-family: inherit;
+ color: inherit;
+ font-family: inherit;
}
h1 {
- margin: 0;
- display: flex;
- flex-direction: row;
- align-items: center;
+ margin: 0;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
}
h1::before {
- content: '# ';
- margin-right: 16px;
- color: var(--foreground-dim);
- font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
+ content: '# ';
+ margin-right: 16px;
+ color: var(--foreground-dim);
+ font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
}
h2::before {
- content: '## ';
- color: var(--foreground-dim);
- font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
+ content: '## ';
+ color: var(--foreground-dim);
+ font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
}
h3::before {
- content: '### ';
- color: var(--foreground-dim);
- font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
+ content: '### ';
+ color: var(--foreground-dim);
+ font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
}
h4::before {
- content: '#### ';
- color: var(--foreground-dim);
- font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
+ content: '#### ';
+ color: var(--foreground-dim);
+ font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
}
h5::before {
- content: '##### ';
- color: var(--foreground-dim);
- font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
+ content: '##### ';
+ color: var(--foreground-dim);
+ font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
}
h6::before {
- content: '###### ';
- color: var(--foreground-dim);
- font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
+ content: '###### ';
+ color: var(--foreground-dim);
+ font-family: 'JetBrains Mono', 'Oxygen Mono', monospace;
}
button {
- border: none;
- padding: 8px;
- border-radius: 8px;
- cursor: pointer;
- text-decoration: none;
- font-size: initial;
- color: black;
- transition: filter 200ms ease;
- vertical-align: middle;
- font-family: inherit;
+ border: none;
+ padding: 8px;
+ border-radius: 8px;
+ cursor: pointer;
+ text-decoration: none;
+ font-size: initial;
+ color: black;
+ transition: filter 200ms ease;
+ vertical-align: middle;
+ font-family: inherit;
}
button:hover {
- filter: brightness(.8);
+ filter: brightness(0.8);
}
button.primary {
- background-color: var(--primary);
+ background-color: var(--primary);
}
button.confirm {
- background-color: var(--confirm);
+ background-color: var(--confirm);
}
button.warning {
- background-color: var(--warning);
+ background-color: var(--warning);
}
button:disabled {
- filter: saturate(25%) brightness(.85);
- cursor: not-allowed;
+ filter: saturate(25%) brightness(0.85);
+ cursor: not-allowed;
}
a.primary {
- color: var(--primary);
+ color: var(--primary);
}
a.confirm {
- color: var(--confirm);
+ color: var(--confirm);
}
a.warning {
- color: var(--warning);
+ color: var(--warning);
}
input {
- margin: 8px 0;
- background-color: rgba(128, 128, 128, .25);
- color: white;
- border: #888 2px solid;
- padding: 8px;
- border-radius: 8px;
- font-family: inherit;
- font-size: medium;
+ margin: 8px 0;
+ background-color: rgba(128, 128, 128, 0.25);
+ color: white;
+ border: #888 2px solid;
+ padding: 8px;
+ border-radius: 8px;
+ font-family: inherit;
+ font-size: medium;
}
-input[type="text"]:focus-visible,
-input[type="password"]:focus-visible {
- outline: none;
- border-color: var(--accent);
+input[type='text']:focus-visible,
+input[type='password']:focus-visible {
+ outline: none;
+ border-color: var(--accent);
}
.button-row {
- display: flex;
- width: 100%;
- flex-direction: row;
- flex-wrap: wrap;
- gap: 8px;
- margin: 8px 0;
+ display: flex;
+ width: 100%;
+ flex-direction: row;
+ flex-wrap: wrap;
+ gap: 8px;
+ margin: 8px 0;
}
.button-row button {
- flex: 1;
+ flex: 1;
}
.button-group {
- display: flex;
- width: 100%;
- flex-direction: column;
- gap: 8px;
- margin: 8px 0;
+ display: flex;
+ width: 100%;
+ flex-direction: column;
+ gap: 8px;
+ margin: 8px 0;
}
.drawn-name {
- font-size: 72px;
- width: 100%;
- display: grid;
- place-content: center;
+ font-size: 72px;
+ width: 100%;
+ display: grid;
+ place-content: center;
}
diff --git a/src/app.tsx b/src/app.tsx
index 747d25f..5d93f33 100644
--- a/src/app.tsx
+++ b/src/app.tsx
@@ -8,17 +8,21 @@ import { buildNameMap } from './shuffle';
const names = signal([]);
export function App() {
- const [shuffled, setShuffled] = useState<[string, string][] | null>(null);
+ const [shuffled, setShuffled] = useState<[string, string][] | null>(null);
- function start() {
- setShuffled(buildNameMap(names.value));
- }
+ function start() {
+ setShuffled(buildNameMap(names.value));
+ }
- return (
-
- Secret Santa
+ return (
+
+ Secret Santa
- {shuffled ? : }
-
- )
+ {shuffled ? (
+
+ ) : (
+
+ )}
+
+ );
}
diff --git a/src/main.tsx b/src/main.tsx
index e0ce3e9..c6e39ea 100644
--- a/src/main.tsx
+++ b/src/main.tsx
@@ -1,5 +1,5 @@
-import { render } from 'preact'
-import { App } from './app'
-import './index.css'
+import { render } from 'preact';
+import { App } from './app';
+import './index.css';
-render(, document.getElementById('app') as HTMLElement)
+render(, document.getElementById('app') as HTMLElement);
diff --git a/src/pages/DrawName.tsx b/src/pages/DrawName.tsx
index 673a246..c9bbf89 100644
--- a/src/pages/DrawName.tsx
+++ b/src/pages/DrawName.tsx
@@ -1,6 +1,6 @@
-import { FunctionComponent } from "preact";
-import { useState } from "preact/hooks";
-import { useWindowEvent } from "../util";
+import { FunctionComponent } from 'preact';
+import { useState } from 'preact/hooks';
+import { useWindowEvent } from '../util';
interface Props {
names: [string, string][];
@@ -14,34 +14,44 @@ const DrawName: FunctionComponent = ({ names }) => {
if (e.button !== 0) return;
if (currentPerson !== null) {
- setCompleted(completed => [...completed, currentPerson])
+ setCompleted((completed) => [...completed, currentPerson]);
setCurrentPerson(null);
}
});
if (currentPerson === null) {
if (completed.length === names.length) {
- return <>
-
- All done!
-
- >
+ return (
+ <>
+ All done!
+ >
+ );
}
- return <>
-
- {names.map(([name], i) => {
- if (completed.includes(i)) return null;
- return ;
- })}
-
- >
+ return (
+ <>
+
+ {names.map(([name], i) => {
+ if (completed.includes(i)) return null;
+ return (
+
+ );
+ })}
+
+ >
+ );
}
- return <>
-
- {names[currentPerson][1]}
-
- >
-}
+ return (
+ <>
+ {names[currentPerson][1]}
+ >
+ );
+};
export default DrawName;
diff --git a/src/pages/EnterNames.tsx b/src/pages/EnterNames.tsx
index 7c74921..7e08325 100644
--- a/src/pages/EnterNames.tsx
+++ b/src/pages/EnterNames.tsx
@@ -1,6 +1,6 @@
-import { Signal } from "@preact/signals";
-import { FunctionComponent } from "preact";
-import { useState } from "preact/hooks";
+import { Signal } from '@preact/signals';
+import { FunctionComponent } from 'preact';
+import { useState } from 'preact/hooks';
interface Props {
names: Signal;
@@ -20,33 +20,45 @@ const EnterNames: FunctionComponent = ({ names, start }) => {
setName('');
}
- return <>
-
- {names.value.map((name, i) => -
- {name}
-
)}
-
+ return (
+ <>
+
+ {names.value.map((name, i) => (
+ - {name}
+ ))}
+
-
-
setName((e.target! as HTMLInputElement).value)}
- onKeyPress={(e) => {
- if (e.key === 'Enter') {
- addName();
- e.preventDefault();
+
+
+ setName((e.target! as HTMLInputElement).value)
}
- }}
+ onKeyPress={(e) => {
+ if (e.key === 'Enter') {
+ addName();
+ e.preventDefault();
+ }
+ }}
/>
-
-
- >
-}
+ >
+ );
+};
export default EnterNames;
diff --git a/src/shuffle.ts b/src/shuffle.ts
index 23c35e2..ca6e2b2 100644
--- a/src/shuffle.ts
+++ b/src/shuffle.ts
@@ -1,6 +1,7 @@
// https://stackoverflow.com/a/2450976
function shuffle
(array: T[]) {
- let currentIndex = array.length, randomIndex;
+ let currentIndex = array.length,
+ randomIndex: number;
// While there remain elements to shuffle.
while (currentIndex != 0) {
@@ -9,7 +10,10 @@ function shuffle(array: T[]) {
currentIndex--;
// 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;
@@ -44,7 +48,9 @@ function nonEqualShuffle(input: T[]): T[] {
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 shuffled = nonEqualShuffle(names);
diff --git a/src/util.ts b/src/util.ts
index a53f71a..199ba9e 100644
--- a/src/util.ts
+++ b/src/util.ts
@@ -1,6 +1,10 @@
-import { useEffect } from "preact/hooks";
+import { useEffect } from 'preact/hooks';
-export function useWindowEvent(type: K, listener: (this: Window, ev: WindowEventMap[K]) => void, options?: boolean | AddEventListenerOptions): void {
+export function useWindowEvent(
+ type: K,
+ listener: (this: Window, ev: WindowEventMap[K]) => void,
+ options?: boolean | AddEventListenerOptions
+): void {
useEffect(() => {
window.addEventListener(type, listener, options);
return () => window.removeEventListener(type, listener);
diff --git a/test/shuffle.test.ts b/test/shuffle.test.ts
index 352c35b..8e4f092 100644
--- a/test/shuffle.test.ts
+++ b/test/shuffle.test.ts
@@ -2,7 +2,9 @@ import { expect, test } from 'vitest';
import { buildNameMap } from '../src/shuffle';
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++) {
const shuffled = buildNameMap(names);
for (const pair of shuffled) {
diff --git a/tsconfig.json b/tsconfig.json
index 9c1b1e0..12a2769 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -1,22 +1,22 @@
{
- "compilerOptions": {
- "target": "ESNext",
- "useDefineForClassFields": true,
- "lib": ["DOM", "DOM.Iterable", "ESNext"],
- "allowJs": false,
- "skipLibCheck": true,
- "esModuleInterop": false,
- "allowSyntheticDefaultImports": true,
- "strict": true,
- "forceConsistentCasingInFileNames": true,
- "module": "ESNext",
- "moduleResolution": "Node",
- "resolveJsonModule": true,
- "isolatedModules": true,
- "noEmit": true,
- "jsx": "react-jsx",
- "jsxImportSource": "preact"
- },
- "include": ["src"],
- "references": [{ "path": "./tsconfig.node.json" }]
+ "compilerOptions": {
+ "target": "ESNext",
+ "useDefineForClassFields": true,
+ "lib": ["DOM", "DOM.Iterable", "ESNext"],
+ "allowJs": false,
+ "skipLibCheck": true,
+ "esModuleInterop": false,
+ "allowSyntheticDefaultImports": true,
+ "strict": true,
+ "forceConsistentCasingInFileNames": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "noEmit": true,
+ "jsx": "react-jsx",
+ "jsxImportSource": "preact"
+ },
+ "include": ["src"],
+ "references": [{ "path": "./tsconfig.node.json" }]
}
diff --git a/tsconfig.node.json b/tsconfig.node.json
index 9d31e2a..13b35d0 100644
--- a/tsconfig.node.json
+++ b/tsconfig.node.json
@@ -1,9 +1,9 @@
{
- "compilerOptions": {
- "composite": true,
- "module": "ESNext",
- "moduleResolution": "Node",
- "allowSyntheticDefaultImports": true
- },
- "include": ["vite.config.ts"]
+ "compilerOptions": {
+ "composite": true,
+ "module": "ESNext",
+ "moduleResolution": "Node",
+ "allowSyntheticDefaultImports": true
+ },
+ "include": ["vite.config.ts"]
}
diff --git a/vite.config.ts b/vite.config.ts
index b587c9b..bb0cec8 100644
--- a/vite.config.ts
+++ b/vite.config.ts
@@ -4,34 +4,34 @@ import { VitePWA } from 'vite-plugin-pwa';
// https://vitejs.dev/config/
export default defineConfig({
- plugins: [
- preact(),
- VitePWA({
- registerType: 'autoUpdate',
- includeAssets: ['favicon.png', 'apple-touch-icon.png'],
- manifest: {
- name: 'SecSan',
- short_name: 'SecSan',
- description: 'Secret santa name drawing',
- theme_color: '#ffffff',
- icons: [
- {
- src: 'favicon.png',
- sizes: '256x256',
- type: 'image/png',
- },
- {
- src: 'favicon-192.png',
- sizes: '192x192',
- type: 'image/png',
- },
- ],
- },
- }),
- ],
- server: {
- hmr: {
- clientPort: parseInt(process.env.CLIENT_PORT || '5173', 10),
+ plugins: [
+ preact(),
+ VitePWA({
+ registerType: 'autoUpdate',
+ includeAssets: ['favicon.png', 'apple-touch-icon.png'],
+ manifest: {
+ name: 'SecSan',
+ short_name: 'SecSan',
+ description: 'Secret santa name drawing',
+ theme_color: '#ffffff',
+ icons: [
+ {
+ src: 'favicon.png',
+ sizes: '256x256',
+ type: 'image/png',
+ },
+ {
+ src: 'favicon-192.png',
+ sizes: '192x192',
+ type: 'image/png',
+ },
+ ],
+ },
+ }),
+ ],
+ server: {
+ hmr: {
+ clientPort: parseInt(process.env.CLIENT_PORT || '5173', 10),
+ },
},
- },
});