this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

fix issue where custom sets don't show games

+156 -79
+1 -1
deno.json
··· 1 1 { 2 - "version": "4.1.0", 2 + "version": "4.1.1", 3 3 "workspace": ["./data"], 4 4 "compilerOptions": { 5 5 "lib": [
+1 -1
www/models/subjects.ts
··· 249 249 state.assignments = newAssignments 250 250 } 251 251 state.learnLimit = settings?.learnLimit ?? 5 252 - state.reviewLimit = settings?.reviewLimit ?? 20 252 + state.reviewLimit = settings?.reviewLimit ?? null 253 253 state.learnSessionSize = settings?.learnSessionSize ?? 10 254 254 state.reviewSessionSize = settings?.reviewSessionSize ?? 50 255 255 state.cardSortMethod = settings?.cardSortMethod ?? CardSortMethod.Paired
+67 -67
www/routes/settings/about.ts
··· 52 52 border="0" 53 53 alt="Buy Me a Coffee at ko-fi.com" 54 54 ></a> 55 - </section> 56 - <section> 57 - <h2>Acknowledgements</h2> 58 - <p>Word lists were compiled from:</p> 59 - <ul> 60 - <li> 61 - The Official <a href="https://www.chinesetest.cn/HSK">HSK</a> word list 62 - </li> 63 - <li> 64 - The Official <a href="https://coct.naer.edu.tw">TOCFL</a> word list 65 - </li> 66 - <li> 55 + </section> 56 + <section> 57 + <h2>Acknowledgements</h2> 58 + <p>Word lists were compiled from:</p> 59 + <ul> 60 + <li> 61 + The Official <a href="https://www.chinesetest.cn/HSK">HSK</a> word list 62 + </li> 63 + <li> 64 + The Official <a href="https://coct.naer.edu.tw">TOCFL</a> word list 65 + </li> 66 + <li> 67 + <a 68 + href="https://www.hackingchinese.com/what-important-words-are-missing-from-hsk/" 69 + > 70 + Hacking Chinese - What important words are missing from HSK? 71 + </a> 72 + </li> 73 + <li> 74 + <a 75 + href="https://www.hackingchinese.com/what-important-words-are-missing-from-tocfl/" 76 + > 77 + Hacking Chinese - What important words are missing from TOCFL? 78 + </a> 79 + </li> 80 + </ul> 81 + <p> 82 + Definitions and Translations were mostly sourced from 83 + <a href="https://cc-cedict.org">cc-cedict</a> 84 + </p> 85 + <p> 86 + Example sentences were mostly sourced from 87 + <a href="https://tatoeba.org">Tatoeba</a> 88 + </p> 89 + <p> 90 + Audio is generated by 67 91 <a 68 - href="https://www.hackingchinese.com/what-important-words-are-missing-from-hsk/" 92 + href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/text-to-speech" 69 93 > 70 - Hacking Chinese - What important words are missing from HSK? 94 + Azure Text-To-Speech 71 95 </a> 72 - </li> 73 - <li> 74 - <a 75 - href="https://www.hackingchinese.com/what-important-words-are-missing-from-tocfl/" 76 - > 77 - Hacking Chinese - What important words are missing from TOCFL? 78 - </a> 79 - </li> 80 - </ul> 81 - <p> 82 - Definitions and Translations were mostly sourced from 83 - <a href="https://cc-cedict.org">cc-cedict</a> 84 - </p> 85 - <p> 86 - Example sentences were mostly sourced from 87 - <a href="https://tatoeba.org">Tatoeba</a> 88 - </p> 89 - <p> 90 - Audio is generated by 91 - <a 92 - href="https://learn.microsoft.com/en-us/azure/ai-services/speech-service/text-to-speech" 93 - > 94 - Azure Text-To-Speech 95 - </a> 96 - </p> 97 - </section> 98 - <h2>Licenses</h2> 99 - ${licenses.map((license) => { 100 - const text = license.text 101 - const isHTML = /<\/?[a-z][\s\S]*>/i.test(text) 102 - const style = 103 - 'text-wrap: wrap; overflow: scroll; background-color: whitesmoke; max-height: 400px;' 104 - return html` 105 - <details name="licenses"> 106 - <summary>${license.name}</summary> 107 - <section> 108 - <button @click="${() => 109 - globalThis.open(license.href)}">open</button> 110 - ${!isHTML 111 - ? html` 112 - <pre style="${style}">${text}</pre> 113 - ` 114 - : ''} 115 - </section> 116 - </details> 117 - ` 118 - })} 119 - </article> 120 - ` 96 + </p> 97 + </section> 98 + <h2>Licenses</h2> 99 + ${licenses.map((license) => { 100 + const text = license.text 101 + const isHTML = /<\/?[a-z][\s\S]*>/i.test(text) 102 + const style = 103 + 'text-wrap: wrap; overflow: scroll; background-color: whitesmoke; max-height: 400px;' 104 + return html` 105 + <details name="licenses"> 106 + <summary>${license.name}</summary> 107 + <section> 108 + <button @click="${() => 109 + globalThis.open(license.href)}">open</button> 110 + ${!isHTML 111 + ? html` 112 + <pre style="${style}">${text}</pre> 113 + ` 114 + : ''} 115 + </section> 116 + </details> 117 + ` 118 + })} 119 + </article> 120 + ` 121 + } 121 122 } 122 - } 123 123 124 - if (!customElements.get('r-settings-about')) { 125 - customElements.define('r-settings-about', SettingsAboutRoute) 126 - } 124 + if (!customElements.get('r-settings-about')) { 125 + customElements.define('r-settings-about', SettingsAboutRoute) 126 + }
+58 -7
www/routes/settings/custom-sets.ts
··· 5 5 getCustomSets, 6 6 saveCustomSet, 7 7 toLocaleId, 8 + updateCustomSetLocale, 8 9 } from '$/utils/custom_sets.ts' 9 10 import { Locale } from '$/enums.ts' 11 + 12 + const LOCALE_OPTIONS = [ 13 + { value: '', label: 'None (detect from data)' }, 14 + { value: 'ja', label: 'Japanese (ja)' }, 15 + { value: 'zh_CN', label: 'Chinese Simplified (zh_CN)' }, 16 + { value: 'zh_TW', label: 'Chinese Traditional (zh_TW)' }, 17 + { value: 'zh_HK', label: 'Chinese Hong Kong (zh_HK)' }, 18 + ] 10 19 11 20 export class SettingsCustomSetsRoutes extends LitElement { 12 21 #name = '' ··· 30 39 app.removeEventListener(this.#onUpdate) 31 40 } 32 41 42 + #selectedLocale = '' 43 + 33 44 async #onSubmit() { 34 45 const name = this.#name.trim() 35 46 if (!name || !this.#file) { ··· 46 57 'File must contain a JSON array or an object with a subjects array.', 47 58 ) 48 59 } 49 - const locale = Array.isArray(parsed) ? undefined : parsed?.locale 60 + const locale = this.#selectedLocale || 61 + (Array.isArray(parsed) ? undefined : parsed?.locale) 50 62 const id = toLocaleId(name) 51 - await saveCustomSet({ id, name, locale }, subjects) 63 + await saveCustomSet({ id, name, locale: locale || undefined }, subjects) 52 64 this.#name = '' 53 65 this.#file = null 54 66 this.#error = null 67 + this.#selectedLocale = '' 55 68 // Dispatch a storage event so h-locale-select updates 56 69 globalThis.dispatchEvent(new Event('hanzi-custom-sets-changed')) 57 70 this.requestUpdate() ··· 70 83 this.requestUpdate() 71 84 } 72 85 86 + async #onUpdateLocale(id: string, locale: string) { 87 + await updateCustomSetLocale(id, locale || undefined) 88 + globalThis.dispatchEvent(new Event('hanzi-custom-sets-changed')) 89 + this.requestUpdate() 90 + } 91 + 73 92 override render() { 74 93 const customSets = getCustomSets() 75 94 ··· 85 104 (set) => 86 105 html` 87 106 <li class="item flex items-center justify-between"> 88 - <span><b>${set.name}</b> <small>(${set.id}${set.locale 89 - ? ` · ${set.locale}` 90 - : ''})</small></span> 107 + <span><b>${set.name}</b> <small>(${set.id})</small></span> 108 + <select 109 + class="blue-shadow" 110 + @change="${(e: Event) => { 111 + this.#onUpdateLocale( 112 + set.id, 113 + (e.target as HTMLSelectElement).value, 114 + ) 115 + }}" 116 + > 117 + ${LOCALE_OPTIONS.map((opt) => 118 + html` 119 + <option 120 + value="${opt.value}" 121 + ?selected="${set.locale === opt.value}" 122 + > 123 + ${opt.label} 124 + </option> 125 + ` 126 + )} 127 + </select> 91 128 <button 92 129 class="bg-light-red" 93 130 @click="${() => this.#onDelete(set.id)}" ··· 124 161 .value="${this.#name}" 125 162 @input="${(e: InputEvent) => { 126 163 this.#name = (e.target as HTMLInputElement).value 127 - console.log(this.#name) 128 164 }}" 129 165 /> 130 166 </div> 131 167 <div class="item"> 168 + <label for="customSetLocale">Locale (optional)</label> 169 + <select 170 + class="blue-shadow" 171 + id="customSetLocale" 172 + @change="${(e: Event) => { 173 + this.#selectedLocale = (e.target as HTMLSelectElement).value 174 + }}" 175 + > 176 + ${LOCALE_OPTIONS.map((opt) => 177 + html` 178 + <option value="${opt.value}">${opt.label}</option> 179 + ` 180 + )} 181 + </select> 182 + </div> 183 + <div class="item"> 132 184 <label for="customSetFile">File (.json)</label> 133 185 <input 134 186 id="customSetFile" ··· 136 188 accept=".json,application/json" 137 189 @change="${(e: Event) => { 138 190 this.#file = (e.target as HTMLInputElement).files?.[0] ?? null 139 - console.log(this.#file) 140 191 }}" 141 192 /> 142 193 </div>
+2 -2
www/routes/study.ts
··· 124 124 } 125 125 126 126 for (const assignment of Object.values(deck.state.assignments)) { 127 - await app.saveAssignment({ ...assignment, srsId: 1 } as Assignment) 127 + await app.saveAssignment(assignment as Assignment) 128 128 } 129 129 await updateStreak() 130 130 ··· 389 389 ) 390 390 ) { 391 391 await app.saveAssignment( 392 - { ...assignment, srsId: 1 } as Assignment, 392 + assignment as Assignment, 393 393 ) 394 394 } 395 395 this.requestUpdate()
+27 -1
www/utils/custom_sets.ts
··· 14 14 } 15 15 16 16 const storage = new IDBStorage<CustomSetData>({ 17 - dbName: 'bpev-hanzi-custom-sets' 17 + dbName: 'bpev-hanzi-custom-sets', 18 18 }) 19 19 const customSets = new Collection<CustomSetData>(storage, { name: 'sets' }) 20 20 ··· 55 55 await customSets.delete(id) 56 56 } 57 57 58 + export async function updateCustomSetLocale( 59 + id: string, 60 + locale: string | undefined, 61 + ): Promise<void> { 62 + const data = await customSets.get(id) 63 + if (!data) return 64 + await customSets.set(id, { ...data, locale }) 65 + } 66 + 58 67 export async function getCustomSetData(id: string): Promise<unknown[] | null> { 59 68 const data = await customSets.get(id) 60 69 return data?.subjects ?? null 61 70 } 62 71 63 72 export function resolveLocale(locale: string): string { 73 + if (locale.startsWith('custom-')) { 74 + const customSet = cachedSets.find((set) => set.id === locale) 75 + return customSet?.locale ?? locale 76 + } 77 + return locale 78 + } 79 + 80 + /** 81 + * Async version that waits for custom sets to load before resolving. 82 + * Use when you need to ensure the locale is resolved after app startup. 83 + */ 84 + export async function resolveLocaleAsync(locale: string): Promise<string> { 85 + if (locale.startsWith('custom-')) { 86 + await refreshCache() 87 + const customSet = cachedSets.find((set) => set.id === locale) 88 + return customSet?.locale ?? locale 89 + } 64 90 return locale 65 91 } 66 92