Skip to content

Commit 564ff83

Browse files
committed
feat: custom word lists
1 parent 3734180 commit 564ff83

File tree

7 files changed

+275
-46
lines changed

7 files changed

+275
-46
lines changed

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Changelog
2+
3+
## 0.2.0 - 2025-11-21
4+
5+
### Added
6+
7+
* Support for LED Matrix Modules with the following firmware: [sigroot/FW_LED_Matrix_Firmware](https://github.com/sigroot/FW_LED_Matrix_Firmware/tree/main/rp2040_firmware)
8+
9+
## 0.3.0 - 2025-11-24
10+
11+
### Added
12+
13+
* New "Custom Word Lists" panel with controls for creating, editing and deleting custom collections of words.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "led-matrix-vocab",
3-
"version": "0.2.0",
3+
"version": "0.3.0",
44
"license": "SEE LICENSE FILE",
55
"packageManager": "[email protected]",
66
"scripts": {

public/index.html

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ <h6 class="m-1">
3030
<h1 class="text-center mb-4">LED Matrix Word Randomizer 🇯🇵</h1>
3131

3232
<div class="row">
33-
<div class="col-lg-6 mb-4">
34-
<div class="card shadow-sm border-0 bg-body-tertiary rounded-4 p-3">
33+
<div class="col-lg-6">
34+
<div class="card shadow-sm border-0 bg-body-tertiary rounded-4 p-3 mb-4">
3535
<h5 class="mb-3 fw-semibold text-body-secondary">
3636
LED Matrix Control
3737
</h5>
38-
<div class="d-flex justify-content-between align-items-center mb-3">
38+
<div class="d-flex justify-content-between align-items-center">
3939
<div>
4040
<h6>Left Module</h6>
4141
<div id="status-display-1" class="d-flex align-items-center">
@@ -50,7 +50,7 @@ <h6>Left Module</h6>
5050

5151
<hr />
5252

53-
<div class="d-flex justify-content-between align-items-center mb-3">
53+
<div class="d-flex justify-content-between align-items-center">
5454
<div>
5555
<h6>Right Module</h6>
5656
<div id="status-display-2" class="d-flex align-items-center">
@@ -69,10 +69,51 @@ <h6>Right Module</h6>
6969
</button>
7070
</div>
7171
</div>
72+
73+
<div class="card shadow-sm border-0 bg-body-tertiary rounded-4 p-3 mb-4">
74+
<h5 class="mb-3 fw-semibold text-body-secondary">
75+
Custom Word Lists
76+
</h5>
77+
78+
<div class="mb-3">
79+
<label for="custom-list-select" class="form-label">Select List</label>
80+
<select id="custom-list-select" class="form-select">
81+
<option value="no-list">No custom lists</option>
82+
</select>
83+
</div>
84+
85+
<div class="d-flex gap-2 justify-content-end mb-2">
86+
<button id="add-list-btn" class="btn btn-primary">
87+
<i class="bi bi-plus-lg"></i> New
88+
</button>
89+
<button id="edit-list-btn" class="btn btn-secondary">
90+
<i class="bi bi-pencil"></i> Edit
91+
</button>
92+
<button id="delete-list-btn" class="btn btn-danger">
93+
<i class="bi bi-trash"></i> Delete
94+
</button>
95+
</div>
96+
97+
<div class="mb-2" id="list-controls" style="display: none;">
98+
<label for="list-name-input" class="form-label">Name</label>
99+
<input type="text" id="list-name-input" class="form-control mb-3" />
100+
101+
<label for="custom-list-textarea" class="form-label">Words (one per line)</label>
102+
<textarea id="custom-list-textarea" class="form-control mb-3" rows="6" style="font-size: 0.85rem; resize: vertical;"></textarea>
103+
<div class="d-flex gap-2 justify-content-end">
104+
<button id="save-list-btn" class="btn btn-primary">
105+
<i class="bi bi-floppy"></i> Save
106+
</button>
107+
<button id="cancel-edit-btn" class="btn btn-secondary">
108+
Cancel
109+
</button>
110+
</div>
111+
</div>
112+
</div>
72113
</div>
73114

74-
<div class="col-lg-6 mb-4">
75-
<div class="card shadow-sm border-0 bg-body-tertiary rounded-4 p-3 pb-1">
115+
<div class="col-lg-6">
116+
<div class="card shadow-sm border-0 bg-body-tertiary rounded-4 p-3 mb-4">
76117
<h5 class="mb-3 fw-semibold text-body-secondary">
77118
Word Randomizer
78119
</h5>

public/js/CustomLists.js

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
export class CustomLists {
2+
constructor() {
3+
this.storageKey = 'custom-word-lists';
4+
this.lists = this.loadLists();
5+
}
6+
7+
loadLists() {
8+
const stored = localStorage.getItem(this.storageKey);
9+
return stored ? JSON.parse(stored) : {};
10+
}
11+
12+
saveLists() {
13+
localStorage.setItem(this.storageKey, JSON.stringify(this.lists));
14+
}
15+
16+
createList(name) {
17+
if (!name || name.trim() === '') {
18+
throw new Error('List name cannot be empty');
19+
}
20+
21+
const id = 'list-' + Date.now();
22+
this.lists[id] = {
23+
id,
24+
name: name.trim(),
25+
words: [],
26+
created: new Date().toISOString(),
27+
};
28+
29+
this.saveLists();
30+
return id;
31+
}
32+
33+
deleteList(id) {
34+
if (this.lists[id]) {
35+
delete this.lists[id];
36+
this.saveLists();
37+
}
38+
}
39+
40+
getList(id) {
41+
return this.lists[id];
42+
}
43+
44+
getListMetadata() {
45+
return Object.values(this.lists).map(list => ({
46+
id: list.id,
47+
name: list.name,
48+
wordCount: list.words.length,
49+
}));
50+
}
51+
52+
getRandomWord(id) {
53+
const list = this.lists[id];
54+
if (!list || list.words.length === 0) {
55+
throw new Error('List is empty or not found');
56+
}
57+
return list.words[Math.floor(Math.random() * list.words.length)];
58+
}
59+
}
Lines changed: 19 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class OnlineJlptSource {
7676
*/
7777
const WORD_SOURCES = [
7878
{
79-
title: 'JLPT (offline)',
79+
title: 'JLPT',
8080
value: 'jlpt1',
8181
fetch: async () => (await OfflineJlptSource.getEntry()).jp,
8282
},
@@ -85,13 +85,23 @@ const WORD_SOURCES = [
8585
value: 'wiki',
8686
fetch: async () => (await OfflineWikipediaSource.getEntry()).w,
8787
},
88-
{
89-
title: 'JLPT (online)',
90-
value: 'jlpt2',
91-
fetch: async () => (await OnlineJlptSource.getEntry()).word,
92-
},
93-
];
88+
];
89+
90+
export class DataSources {
91+
constructor(customLists) {
92+
this.#customLists = customLists;
93+
}
94+
95+
get() {
96+
return [
97+
...WORD_SOURCES,
98+
...this.#customLists.getListMetadata().map(list => ({
99+
title: `Custom List: ${list.name}`,
100+
value: `custom-${list.id}`,
101+
fetch: async () => this.#customLists.getRandomWord(list.id)
102+
}))
103+
];
104+
}
94105

95-
export function getSources() {
96-
return WORD_SOURCES;
106+
#customLists
97107
}

0 commit comments

Comments
 (0)