Skip to content

Commit 8e1f268

Browse files
author
Andrew Marcuse
committed
Haikunator tweaks
1 parent c724ecb commit 8e1f268

File tree

2 files changed

+171
-20
lines changed

2 files changed

+171
-20
lines changed

src/charcount.ts

Lines changed: 72 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,51 @@ type CountRunesResult = {
1212
decodeErrorCount: number
1313
}
1414

15-
type CharsetKey = 'utf8' | 'utf16' | 'latin1'
16-
17-
const charsetToEncoding: Record<CharsetKey, string> = {
18-
utf8: 'utf-8',
19-
utf16: 'utf-16le',
20-
latin1: 'iso-8859-1',
21-
}
15+
const charsetOptions = [
16+
{ value: 'utf-8', label: 'utf-8' },
17+
{ value: 'utf-16le', label: 'utf-16le' },
18+
{ value: 'utf-16be', label: 'utf-16be' },
19+
{ value: 'ibm866', label: 'ibm866' },
20+
{ value: 'iso-8859-1', label: 'iso-8859-1' },
21+
{ value: 'iso-8859-2', label: 'iso-8859-2' },
22+
{ value: 'iso-8859-3', label: 'iso-8859-3' },
23+
{ value: 'iso-8859-4', label: 'iso-8859-4' },
24+
{ value: 'iso-8859-5', label: 'iso-8859-5' },
25+
{ value: 'iso-8859-6', label: 'iso-8859-6' },
26+
{ value: 'iso-8859-7', label: 'iso-8859-7' },
27+
{ value: 'iso-8859-8', label: 'iso-8859-8' },
28+
{ value: 'iso-8859-8-i', label: 'iso-8859-8-i' },
29+
{ value: 'iso-8859-10', label: 'iso-8859-10' },
30+
{ value: 'iso-8859-13', label: 'iso-8859-13' },
31+
{ value: 'iso-8859-14', label: 'iso-8859-14' },
32+
{ value: 'iso-8859-15', label: 'iso-8859-15' },
33+
{ value: 'iso-8859-16', label: 'iso-8859-16' },
34+
{ value: 'koi8-r', label: 'koi8-r' },
35+
{ value: 'koi8-u', label: 'koi8-u' },
36+
{ value: 'macintosh', label: 'macintosh' },
37+
{ value: 'windows-874', label: 'windows-874' },
38+
{ value: 'windows-1250', label: 'windows-1250' },
39+
{ value: 'windows-1251', label: 'windows-1251' },
40+
{ value: 'windows-1252', label: 'windows-1252' },
41+
{ value: 'windows-1253', label: 'windows-1253' },
42+
{ value: 'windows-1254', label: 'windows-1254' },
43+
{ value: 'windows-1255', label: 'windows-1255' },
44+
{ value: 'windows-1256', label: 'windows-1256' },
45+
{ value: 'windows-1257', label: 'windows-1257' },
46+
{ value: 'windows-1258', label: 'windows-1258' },
47+
{ value: 'x-mac-cyrillic', label: 'x-mac-cyrillic' },
48+
{ value: 'gbk', label: 'gbk' },
49+
{ value: 'gb18030', label: 'gb18030' },
50+
{ value: 'big5', label: 'big5' },
51+
{ value: 'euc-jp', label: 'euc-jp' },
52+
{ value: 'iso-2022-jp', label: 'iso-2022-jp' },
53+
{ value: 'shift_jis', label: 'shift_jis' },
54+
{ value: 'euc-kr', label: 'euc-kr' },
55+
{ value: 'x-user-defined', label: 'x-user-defined' },
56+
] as const
57+
58+
type CharsetKey = (typeof charsetOptions)[number]['value']
59+
const charsetValues = new Set<CharsetKey>(charsetOptions.map((option) => option.value))
2260

2361
document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
2462
<main class="min-h-screen bg-base-200" data-theme="light">
@@ -41,18 +79,17 @@ document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
4179
<div class="label">
4280
<span class="label-text">Character set</span>
4381
</div>
44-
<select id="charset-select" name="charset" class="select select-bordered w-full">
45-
<option value="utf8" selected>UTF-8</option>
46-
<option value="utf16">UTF-16</option>
47-
<option value="latin1">Latin-1</option>
48-
</select>
82+
<select id="charset-select" name="charset" class="select select-bordered w-full"></select>
4983
</label>
5084
5185
<div id="form-error" class="alert alert-error hidden" role="alert" aria-live="polite"></div>
5286
</form>
5387
5488
<section id="results" class="mt-8 hidden">
55-
<h2 class="text-xl font-semibold">Character Counts</h2>
89+
<div class="flex items-center gap-3">
90+
<h2 class="text-xl font-semibold">Character Counts</h2>
91+
<button id="clear-results-button" type="button" class="btn btn-sm ml-auto">Clear</button>
92+
</div>
5693
<p id="results-summary" class="mt-2 text-base-content/70"></p>
5794
5895
<div class="mt-4 overflow-x-auto">
@@ -81,6 +118,7 @@ const formError = document.querySelector<HTMLDivElement>('#form-error')
81118
const resultsSection = document.querySelector<HTMLElement>('#results')
82119
const resultsSummary = document.querySelector<HTMLParagraphElement>('#results-summary')
83120
const resultsBody = document.querySelector<HTMLTableSectionElement>('#results-body')
121+
const clearResultsButton = document.querySelector<HTMLButtonElement>('#clear-results-button')
84122
let errorTimeoutId: number | undefined
85123

86124
const hideError = () => {
@@ -116,12 +154,18 @@ const showError = (message: string) => {
116154
document.addEventListener('click', hideError, { once: true })
117155
}
118156

119-
const isCharsetKey = (value: string): value is CharsetKey => value in charsetToEncoding
157+
const isCharsetKey = (value: string): value is CharsetKey => charsetValues.has(value as CharsetKey)
158+
159+
if (charsetSelect) {
160+
charsetSelect.innerHTML = charsetOptions
161+
.map((option) => `<option value="${option.value}"${option.value === 'utf-8' ? ' selected' : ''}>${option.label}</option>`)
162+
.join('')
163+
}
120164

121165
const countRunes = async (file: File, charset: CharsetKey): Promise<CountRunesResult> => {
122166
const runeCounts = new Map<number, RuneStats>()
123167
const fileBuffer = await file.arrayBuffer()
124-
const text = new TextDecoder(charsetToEncoding[charset]).decode(fileBuffer)
168+
const text = new TextDecoder(charset).decode(fileBuffer)
125169
let decodeErrorCount = 0
126170

127171
let runeOffset = 0
@@ -229,7 +273,7 @@ const renderRuneTable = (runeCounts: Map<number, RuneStats>, totalRunes: number,
229273

230274
const runCount = async () => {
231275
const selectedFile = fileInput?.files?.[0]
232-
const selectedCharset = charsetSelect?.value ?? 'utf8'
276+
const selectedCharset = charsetSelect?.value ?? 'utf-8'
233277

234278
if (!selectedFile) {
235279
resultsSection?.classList.add('hidden')
@@ -268,6 +312,18 @@ const runCount = async () => {
268312
}
269313
}
270314

315+
clearResultsButton?.addEventListener('click', () => {
316+
if (resultsBody) {
317+
resultsBody.innerHTML = ''
318+
}
319+
320+
if (resultsSummary) {
321+
resultsSummary.textContent = ''
322+
}
323+
324+
resultsSection?.classList.add('hidden')
325+
})
326+
271327
fileInput?.addEventListener('change', () => {
272328
void runCount()
273329
})

src/haikunator.ts

Lines changed: 99 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
3030
<div class="label">
3131
<span class="label-text">Token characters</span>
3232
</div>
33-
<input id="token-chars" type="text" value="0123456789" class="input input-bordered w-full" />
33+
<input id="token-chars" type="text" value="23456789" class="input input-bordered w-full" />
3434
</label>
3535
3636
<label class="form-control w-full">
@@ -41,8 +41,13 @@ document.querySelector<HTMLDivElement>('#app')!.innerHTML = `
4141
</label>
4242
4343
<label class="form-control w-full md:col-span-2">
44-
<div class="label">
44+
<div class="label w-full">
4545
<span class="label-text">Generated names</span>
46+
<button id="regenerate-button" type="button" class="btn btn-xs ml-auto" title="Regenerate names" aria-label="Regenerate names">
47+
<span class="block h-4 w-4">
48+
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M3 2v6h6"></path><path d="M3 8a9 9 0 0 1 14.12-3.12L21 8"></path><path d="M21 22v-6h-6"></path><path d="M21 16a9 9 0 0 1-14.12 3.12L3 16"></path></svg>
49+
</span>
50+
</button>
4651
</div>
4752
<div id="output-list" class="w-full rounded-box border border-base-300 bg-base-200 p-3"></div>
4853
</label>
@@ -62,8 +67,14 @@ const tokenCharsInput = document.querySelector<HTMLInputElement>('#token-chars')
6267
const nameCountInput = document.querySelector<HTMLInputElement>('#name-count')
6368
const outputList = document.querySelector<HTMLDivElement>('#output-list')
6469
const copyAllButton = document.querySelector<HTMLButtonElement>('#copy-all-button')
70+
const regenerateButton = document.querySelector<HTMLButtonElement>('#regenerate-button')
6571
let generatedNames: string[] = []
6672

73+
const defaultDelimiter = '-'
74+
const defaultTokenLength = 4
75+
const defaultTokenChars = '23456789'
76+
const defaultNameCount = 10
77+
6778
const copyIconSvg = '<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="8" height="4" x="8" y="2" rx="1" ry="1"></rect><path d="M8 4H6a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-2"></path><path d="M16 4h2a2 2 0 0 1 2 2v4"></path><path d="M21 14H11"></path><path d="m15 10-4 4 4 4"></path></svg>'
6879

6980
const escapeHtml = (value: string): string =>
@@ -82,6 +93,81 @@ const clampInteger = (value: number, min: number): number => {
8293
return Math.max(min, Math.floor(value))
8394
}
8495

96+
const loadFromQuery = () => {
97+
if (!delimiterInput || !tokenLengthInput || !tokenCharsInput || !nameCountInput) {
98+
return
99+
}
100+
101+
const params = new URLSearchParams(window.location.search)
102+
103+
const delimiter = params.get('delimiter')
104+
if (delimiter !== null) {
105+
delimiterInput.value = delimiter
106+
}
107+
108+
const tokenLength = params.get('tokenLength')
109+
if (tokenLength !== null) {
110+
const parsed = Number(tokenLength)
111+
if (Number.isFinite(parsed)) {
112+
tokenLengthInput.value = String(clampInteger(parsed, 0))
113+
}
114+
}
115+
116+
const tokenChars = params.get('tokenChars')
117+
if (tokenChars !== null) {
118+
tokenCharsInput.value = tokenChars
119+
}
120+
121+
const nameCount = params.get('nameCount')
122+
if (nameCount !== null) {
123+
const parsed = Number(nameCount)
124+
if (Number.isFinite(parsed)) {
125+
nameCountInput.value = String(clampInteger(parsed, 1))
126+
}
127+
}
128+
}
129+
130+
const updateQueryFromInputs = () => {
131+
if (!delimiterInput || !tokenLengthInput || !tokenCharsInput || !nameCountInput) {
132+
return
133+
}
134+
135+
const params = new URLSearchParams(window.location.search)
136+
137+
const delimiter = delimiterInput.value
138+
const tokenLength = String(clampInteger(Number(tokenLengthInput.value), 0))
139+
const tokenChars = tokenCharsInput.value || defaultTokenChars
140+
const nameCount = String(clampInteger(Number(nameCountInput.value), 1))
141+
142+
if (delimiter === defaultDelimiter) {
143+
params.delete('delimiter')
144+
} else {
145+
params.set('delimiter', delimiter)
146+
}
147+
148+
if (tokenLength === String(defaultTokenLength)) {
149+
params.delete('tokenLength')
150+
} else {
151+
params.set('tokenLength', tokenLength)
152+
}
153+
154+
if (tokenChars === defaultTokenChars) {
155+
params.delete('tokenChars')
156+
} else {
157+
params.set('tokenChars', tokenChars)
158+
}
159+
160+
if (nameCount === String(defaultNameCount)) {
161+
params.delete('nameCount')
162+
} else {
163+
params.set('nameCount', nameCount)
164+
}
165+
166+
const query = params.toString()
167+
const nextUrl = query ? `${window.location.pathname}?${query}${window.location.hash}` : `${window.location.pathname}${window.location.hash}`
168+
window.history.replaceState(null, '', nextUrl)
169+
}
170+
85171
const renderNames = () => {
86172
if (!outputList) {
87173
return
@@ -103,7 +189,7 @@ const generateNames = () => {
103189
const delimiter = delimiterInput.value
104190
const tokenLength = clampInteger(Number(tokenLengthInput.value), 0)
105191
const nameCount = clampInteger(Number(nameCountInput.value), 1)
106-
const tokenChars = tokenCharsInput.value || '0123456789'
192+
const tokenChars = tokenCharsInput.value || defaultTokenChars
107193

108194
const haikunator = new Haikunator()
109195
const names: string[] = []
@@ -124,7 +210,10 @@ const generateNames = () => {
124210

125211
const inputs = [delimiterInput, tokenLengthInput, tokenCharsInput, nameCountInput]
126212
for (const input of inputs) {
127-
input?.addEventListener('input', generateNames)
213+
input?.addEventListener('input', () => {
214+
updateQueryFromInputs()
215+
generateNames()
216+
})
128217
}
129218

130219
outputList?.addEventListener('click', async (event) => {
@@ -174,4 +263,10 @@ copyAllButton?.addEventListener('click', async () => {
174263
}, 1500)
175264
})
176265

266+
regenerateButton?.addEventListener('click', () => {
267+
generateNames()
268+
})
269+
270+
loadFromQuery()
271+
updateQueryFromInputs()
177272
generateNames()

0 commit comments

Comments
 (0)