@willwade/aac-processors 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/browser/processors/gridset/password.js +12 -6
- package/dist/browser/processors/gridset/symbols.js +62 -19
- package/dist/browser/processors/gridsetProcessor.js +10 -3
- package/dist/browser/processors/obfProcessor.js +6 -4
- package/dist/browser/validation/gridsetValidator.js +3 -2
- package/dist/processors/gridset/password.js +12 -9
- package/dist/processors/gridset/symbols.js +63 -46
- package/dist/processors/gridsetProcessor.js +10 -3
- package/dist/processors/obfProcessor.js +6 -4
- package/dist/validation/gridsetValidator.js +3 -2
- package/docs/PAGESET_GETTING_STARTED.md +185 -0
- package/examples/vitedemo/QUICKSTART.md +1 -0
- package/examples/vitedemo/index.html +160 -5
- package/examples/vitedemo/src/main.ts +367 -15
- package/examples/vitedemo/vite.config.ts +5 -1
- package/package.json +5 -3
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# AAC Pageset Quickstart (Node + Browser)
|
|
2
|
+
|
|
3
|
+
This guide shows two simple ways to generate or convert AAC pagesets using `aac-processors`:
|
|
4
|
+
|
|
5
|
+
- Node.js: full conversion between formats (read + write)
|
|
6
|
+
- Browser: generate or export to OBF/OBZ in-memory (downloadable)
|
|
7
|
+
|
|
8
|
+
If you need lossless conversion in the browser, use a Node/worker service for the save step (file I/O is required for most formats).
|
|
9
|
+
|
|
10
|
+
## Node.js: Convert and Generate Pagesets
|
|
11
|
+
|
|
12
|
+
### Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install aac-processors
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Convert a Gridset to OBF
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { getProcessor, ObfProcessor } from 'aac-processors';
|
|
22
|
+
|
|
23
|
+
async function convertGridsetToObf() {
|
|
24
|
+
const sourcePath = './input/example.gridset';
|
|
25
|
+
const targetPath = './output/example.obf';
|
|
26
|
+
|
|
27
|
+
const sourceProcessor = getProcessor(sourcePath); // GridsetProcessor
|
|
28
|
+
const tree = await sourceProcessor.loadIntoTree(sourcePath);
|
|
29
|
+
|
|
30
|
+
const obf = new ObfProcessor();
|
|
31
|
+
await obf.saveFromTree(tree, targetPath);
|
|
32
|
+
|
|
33
|
+
console.log('Saved:', targetPath);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
convertGridsetToObf().catch(console.error);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Generate a Simple Pageset and Save as OBZ
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { AACTree, AACPage, AACButton, ObfProcessor } from 'aac-processors';
|
|
43
|
+
|
|
44
|
+
async function generateObz() {
|
|
45
|
+
const tree = new AACTree();
|
|
46
|
+
tree.metadata = { name: 'Starter Demo', locale: 'en' };
|
|
47
|
+
|
|
48
|
+
const hello = new AACButton({ id: 'hello', label: 'Hello', message: 'Hello' });
|
|
49
|
+
const thanks = new AACButton({ id: 'thanks', label: 'Thanks', message: 'Thank you' });
|
|
50
|
+
|
|
51
|
+
const home = new AACPage({
|
|
52
|
+
id: 'home',
|
|
53
|
+
name: 'Home',
|
|
54
|
+
buttons: [hello, thanks],
|
|
55
|
+
grid: [[hello, thanks]],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
tree.addPage(home);
|
|
59
|
+
tree.rootId = 'home';
|
|
60
|
+
|
|
61
|
+
const obf = new ObfProcessor();
|
|
62
|
+
await obf.saveFromTree(tree, './output/starter.obz');
|
|
63
|
+
|
|
64
|
+
console.log('Saved: ./output/starter.obz');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
generateObz().catch(console.error);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Browser: Generate or Convert to OBF/OBZ
|
|
71
|
+
|
|
72
|
+
In the browser you can still parse files and build an `AACTree`, but most processors cannot write to disk (no `fs`).
|
|
73
|
+
The example below generates an `AACTree`, converts it to OBF JSON in-memory, and downloads it.
|
|
74
|
+
|
|
75
|
+
If you need full conversions in a browser app, do the save step in Node (server or worker).
|
|
76
|
+
|
|
77
|
+
### Generate a Pageset and Download as OBF
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { AACTree, AACPage, AACButton, ObfProcessor } from 'aac-processors';
|
|
81
|
+
|
|
82
|
+
function downloadBlob(data: BlobPart, filename: string, type: string) {
|
|
83
|
+
const blob = new Blob([data], { type });
|
|
84
|
+
const url = URL.createObjectURL(blob);
|
|
85
|
+
const a = document.createElement('a');
|
|
86
|
+
a.href = url;
|
|
87
|
+
a.download = filename;
|
|
88
|
+
a.click();
|
|
89
|
+
URL.revokeObjectURL(url);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildSampleTree() {
|
|
93
|
+
const tree = new AACTree();
|
|
94
|
+
tree.metadata = { name: 'Browser Demo', locale: 'en' };
|
|
95
|
+
|
|
96
|
+
const hello = new AACButton({ id: 'hello', label: 'Hello', message: 'Hello' });
|
|
97
|
+
const yes = new AACButton({ id: 'yes', label: 'Yes', message: 'Yes' });
|
|
98
|
+
|
|
99
|
+
const home = new AACPage({
|
|
100
|
+
id: 'home',
|
|
101
|
+
name: 'Home',
|
|
102
|
+
buttons: [hello, yes],
|
|
103
|
+
grid: [[hello, yes]],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
tree.addPage(home);
|
|
107
|
+
tree.rootId = 'home';
|
|
108
|
+
return tree;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function exportObf(tree: AACTree) {
|
|
112
|
+
// This mirrors the browser demo approach: create OBF JSON and download.
|
|
113
|
+
// ObfProcessor.saveFromTree writes to disk, so we build a board in-memory.
|
|
114
|
+
const obf = new ObfProcessor() as ObfProcessor & {
|
|
115
|
+
createObfBoardFromPage?: (page: AACPage, fallbackName: string, metadata?: AACTree['metadata']) => any;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const rootPage = tree.rootId ? tree.getPage(tree.rootId) : Object.values(tree.pages)[0];
|
|
119
|
+
const board = obf.createObfBoardFromPage
|
|
120
|
+
? obf.createObfBoardFromPage(rootPage!, 'Board', tree.metadata)
|
|
121
|
+
: {
|
|
122
|
+
format: 'open-board-0.1',
|
|
123
|
+
id: rootPage?.id ?? 'board',
|
|
124
|
+
name: rootPage?.name ?? 'Board',
|
|
125
|
+
locale: tree.metadata?.locale || 'en',
|
|
126
|
+
grid: { rows: 1, columns: rootPage?.buttons.length ?? 0, order: [] },
|
|
127
|
+
buttons: (rootPage?.buttons || []).map((button) => ({
|
|
128
|
+
id: button.id,
|
|
129
|
+
label: button.label,
|
|
130
|
+
vocalization: button.message || button.label,
|
|
131
|
+
})),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const json = JSON.stringify(board, null, 2);
|
|
135
|
+
downloadBlob(json, 'browser-demo.obf', 'application/json');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const tree = buildSampleTree();
|
|
139
|
+
exportObf(tree);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Convert an Uploaded Pageset to OBZ (Browser)
|
|
143
|
+
|
|
144
|
+
This uses the same idea as the Vite demo: parse any supported file into a tree,
|
|
145
|
+
then export OBF/OBZ in-memory and download.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { getProcessor, ObfProcessor, type AACTree, type AACPage } from 'aac-processors';
|
|
149
|
+
import JSZip from 'jszip';
|
|
150
|
+
|
|
151
|
+
async function convertToObz(file: File) {
|
|
152
|
+
const extension = '.' + file.name.split('.').pop();
|
|
153
|
+
const processor = getProcessor(extension);
|
|
154
|
+
const buffer = await file.arrayBuffer();
|
|
155
|
+
const tree = await processor.loadIntoTree(buffer);
|
|
156
|
+
|
|
157
|
+
const obf = new ObfProcessor() as ObfProcessor & {
|
|
158
|
+
createObfBoardFromPage?: (page: AACPage, fallbackName: string, metadata?: AACTree['metadata']) => any;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const zip = new JSZip();
|
|
162
|
+
Object.values(tree.pages).forEach((page) => {
|
|
163
|
+
const board = obf.createObfBoardFromPage
|
|
164
|
+
? obf.createObfBoardFromPage(page, 'Board', tree.metadata)
|
|
165
|
+
: { format: 'open-board-0.1', id: page.id, name: page.name, grid: { rows: 0, columns: 0, order: [] }, buttons: [] };
|
|
166
|
+
zip.file(`${page.id}.obf`, JSON.stringify(board, null, 2));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const data = await zip.generateAsync({ type: 'uint8array' });
|
|
170
|
+
const blob = new Blob([data], { type: 'application/zip' });
|
|
171
|
+
const url = URL.createObjectURL(blob);
|
|
172
|
+
const a = document.createElement('a');
|
|
173
|
+
a.href = url;
|
|
174
|
+
a.download = 'converted.obz';
|
|
175
|
+
a.click();
|
|
176
|
+
URL.revokeObjectURL(url);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Tips
|
|
181
|
+
|
|
182
|
+
- Use `loadIntoTree()` to normalize different AAC formats into one structure.
|
|
183
|
+
- In Node, `saveFromTree()` lets you write to OBF, OBZ, Gridset, etc.
|
|
184
|
+
- In the browser, build the output in memory and offer it for download.
|
|
185
|
+
- For full-fidelity conversions in the browser, use a server-side endpoint to save files.
|
|
@@ -40,6 +40,7 @@ The `test-files/` folder contains example AAC files you can use:
|
|
|
40
40
|
- **Navigation**: Click NAVIGATE buttons to jump between pages
|
|
41
41
|
- **Stats**: See page/button/text counts and load time
|
|
42
42
|
- **Logs**: Watch the processing log in real-time
|
|
43
|
+
- **Pageset Lab**: Open the "Create & Convert" tab to generate a sample pageset or convert an upload to OBF/OBZ
|
|
43
44
|
|
|
44
45
|
## 🛠️ Development
|
|
45
46
|
|
|
@@ -63,6 +63,39 @@
|
|
|
63
63
|
border-bottom: 2px solid #667eea;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
.tab-header {
|
|
67
|
+
display: flex;
|
|
68
|
+
gap: 8px;
|
|
69
|
+
margin-bottom: 15px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.tab-btn {
|
|
73
|
+
flex: 1;
|
|
74
|
+
border: 1px solid #d6daf5;
|
|
75
|
+
background: #f7f8ff;
|
|
76
|
+
color: #3d4bb8;
|
|
77
|
+
padding: 8px 12px;
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
font-size: 13px;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
transition: all 0.2s;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.tab-btn.active {
|
|
86
|
+
background: #667eea;
|
|
87
|
+
color: #fff;
|
|
88
|
+
border-color: #667eea;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.tab-content {
|
|
92
|
+
display: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.tab-content.active {
|
|
96
|
+
display: block;
|
|
97
|
+
}
|
|
98
|
+
|
|
66
99
|
.upload-area {
|
|
67
100
|
border: 2px dashed #ccc;
|
|
68
101
|
border-radius: 8px;
|
|
@@ -290,6 +323,83 @@
|
|
|
290
323
|
font-size: 13px;
|
|
291
324
|
}
|
|
292
325
|
|
|
326
|
+
.demo-section {
|
|
327
|
+
background: #f8f9ff;
|
|
328
|
+
border-radius: 10px;
|
|
329
|
+
padding: 15px;
|
|
330
|
+
border: 1px solid #e4e7ff;
|
|
331
|
+
margin-bottom: 15px;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.section-title {
|
|
335
|
+
font-size: 14px;
|
|
336
|
+
font-weight: 700;
|
|
337
|
+
color: #3942a3;
|
|
338
|
+
margin-bottom: 10px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.field {
|
|
342
|
+
display: flex;
|
|
343
|
+
flex-direction: column;
|
|
344
|
+
gap: 6px;
|
|
345
|
+
margin-bottom: 10px;
|
|
346
|
+
font-size: 12px;
|
|
347
|
+
color: #4d4d4d;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.field select,
|
|
351
|
+
.field input {
|
|
352
|
+
border: 1px solid #d7d9f5;
|
|
353
|
+
border-radius: 6px;
|
|
354
|
+
padding: 8px 10px;
|
|
355
|
+
font-size: 13px;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.action-row {
|
|
359
|
+
display: flex;
|
|
360
|
+
gap: 10px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.action-row .btn {
|
|
364
|
+
margin-bottom: 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.hint {
|
|
368
|
+
font-size: 12px;
|
|
369
|
+
color: #667;
|
|
370
|
+
margin-top: 8px;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.status-pill {
|
|
374
|
+
margin-top: 10px;
|
|
375
|
+
font-size: 12px;
|
|
376
|
+
padding: 8px 10px;
|
|
377
|
+
border-radius: 8px;
|
|
378
|
+
background: #eef0ff;
|
|
379
|
+
color: #4a4a8a;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.status-pill.success {
|
|
383
|
+
background: #e0f4e7;
|
|
384
|
+
color: #256d3b;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.status-pill.warn {
|
|
388
|
+
background: #fff4d6;
|
|
389
|
+
color: #7a5a00;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.code-preview {
|
|
393
|
+
background: #0f172a;
|
|
394
|
+
color: #d7e3ff;
|
|
395
|
+
padding: 12px;
|
|
396
|
+
border-radius: 8px;
|
|
397
|
+
font-size: 12px;
|
|
398
|
+
max-height: 220px;
|
|
399
|
+
overflow: auto;
|
|
400
|
+
white-space: pre-wrap;
|
|
401
|
+
}
|
|
402
|
+
|
|
293
403
|
@media (max-width: 1024px) {
|
|
294
404
|
.main-grid {
|
|
295
405
|
grid-template-columns: 1fr;
|
|
@@ -361,11 +471,56 @@
|
|
|
361
471
|
|
|
362
472
|
<!-- Right Panel: Results -->
|
|
363
473
|
<div class="panel results-panel">
|
|
364
|
-
<div class="panel-title">📊
|
|
365
|
-
<div
|
|
366
|
-
<
|
|
367
|
-
|
|
368
|
-
|
|
474
|
+
<div class="panel-title">📊 AAC Pageset Lab</div>
|
|
475
|
+
<div class="tab-header">
|
|
476
|
+
<button class="tab-btn active" data-tab="inspectTab">Inspect</button>
|
|
477
|
+
<button class="tab-btn" data-tab="pagesetTab">Create & Convert</button>
|
|
478
|
+
</div>
|
|
479
|
+
<div class="tab-content active" id="inspectTab">
|
|
480
|
+
<div id="results">
|
|
481
|
+
<p style="color: #999; text-align: center; padding: 40px;">
|
|
482
|
+
Load a file to see its contents here
|
|
483
|
+
</p>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
<div class="tab-content" id="pagesetTab">
|
|
487
|
+
<div class="demo-section">
|
|
488
|
+
<div class="section-title">✨ Create a Sample Pageset</div>
|
|
489
|
+
<div class="field">
|
|
490
|
+
<label for="templateSelect">Template</label>
|
|
491
|
+
<select id="templateSelect">
|
|
492
|
+
<option value="starter">Starter 2x2 + Feelings</option>
|
|
493
|
+
<option value="home">Home & Core 3x3</option>
|
|
494
|
+
</select>
|
|
495
|
+
</div>
|
|
496
|
+
<div class="field">
|
|
497
|
+
<label for="formatSelect">Output format</label>
|
|
498
|
+
<select id="formatSelect">
|
|
499
|
+
<option value="obf">OBF (.obf)</option>
|
|
500
|
+
<option value="obz">OBZ (.obz)</option>
|
|
501
|
+
</select>
|
|
502
|
+
</div>
|
|
503
|
+
<div class="action-row">
|
|
504
|
+
<button class="btn" id="createPagesetBtn">Generate & Download</button>
|
|
505
|
+
<button class="btn btn-secondary" id="previewPagesetBtn">Preview in Viewer</button>
|
|
506
|
+
</div>
|
|
507
|
+
<div class="hint">Creates a demo AACTree, then exports to OBF/OBZ.</div>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
<div class="demo-section">
|
|
511
|
+
<div class="section-title">🔁 Convert Loaded Pageset</div>
|
|
512
|
+
<div class="action-row">
|
|
513
|
+
<button class="btn" id="convertToObfBtn" disabled>Download .obf</button>
|
|
514
|
+
<button class="btn btn-secondary" id="convertToObzBtn" disabled>Download .obz</button>
|
|
515
|
+
</div>
|
|
516
|
+
<div class="hint">Upload and process a file first, then export it in another format.</div>
|
|
517
|
+
<div class="status-pill" id="conversionStatus">No pageset loaded yet.</div>
|
|
518
|
+
</div>
|
|
519
|
+
|
|
520
|
+
<div class="demo-section">
|
|
521
|
+
<div class="section-title">📄 Export Preview</div>
|
|
522
|
+
<pre class="code-preview" id="pagesetOutput">Generate or convert a pageset to preview the output JSON.</pre>
|
|
523
|
+
</div>
|
|
369
524
|
</div>
|
|
370
525
|
</div>
|
|
371
526
|
</div>
|