@willwade/aac-processors 0.1.11 → 0.1.13
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/resolver.js +10 -0
- package/dist/browser/processors/gridsetProcessor.js +155 -9
- package/dist/processors/gridset/resolver.js +10 -0
- package/dist/processors/gridsetProcessor.js +155 -9
- package/package.json +1 -3
- package/examples/.coverage +0 -0
- package/examples/.keep +0 -1
- package/examples/README.md +0 -55
- package/examples/browser-test.html +0 -331
- package/examples/communikate.dot +0 -2637
- package/examples/demo.js +0 -143
- package/examples/example-images/FileMap.xml +0 -51
- package/examples/example-images/Grids/Start/0-1-0-text-0.png +0 -0
- package/examples/example-images/Grids/Start/0-2-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/0-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/0-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/0-5-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/1-1-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/1-2-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/1-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/1-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/1-5-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/10-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/10-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/11-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/11-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/2-1-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/2-2-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/2-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/2-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/2-5-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/3-1-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/3-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/3-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/3-5-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/4-1-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/4-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/4-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/4-5-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/5-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/5-5-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/6-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/6-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/7-1-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/7-2-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/7-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/7-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/8-1-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/8-2-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/8-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/8-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/9-1.png +0 -0
- package/examples/example-images/Grids/Start/9-2.png +0 -0
- package/examples/example-images/Grids/Start/9-3-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/9-4-0-text-0.jpeg +0 -0
- package/examples/example-images/Grids/Start/grid.xml +0 -1325
- package/examples/example-images/Settings0/Styles/styles.xml +0 -39
- package/examples/example-images/Settings0/WebBrowser/webbrowserextensions.xml +0 -3
- package/examples/example-images/Settings0/settings.xml +0 -16
- package/examples/example-images.gridset +0 -0
- package/examples/example-images.zip +0 -0
- package/examples/example.ce +0 -0
- package/examples/example.dot +0 -14
- package/examples/example.grd +0 -1
- package/examples/example.gridset +0 -0
- package/examples/example.obf +0 -27
- package/examples/example.obz +0 -0
- package/examples/example.opml +0 -18
- package/examples/example.spb +0 -0
- package/examples/example.sps +0 -0
- package/examples/example2.grd +0 -1
- package/examples/obf/aboutme.json +0 -376
- package/examples/obf/array.json +0 -6
- package/examples/obf/hash.json +0 -4
- package/examples/obf/links.obz +0 -0
- package/examples/obf/simple.obf +0 -53
- package/examples/package-lock.json +0 -1326
- package/examples/package.json +0 -10
- package/examples/styled-output/converted-snap-to-touchchat.ce +0 -0
- package/examples/styled-output/styled-example.ce +0 -0
- package/examples/styled-output/styled-example.gridset +0 -0
- package/examples/styled-output/styled-example.obf +0 -37
- package/examples/styled-output/styled-example.spb +0 -0
- package/examples/styling-example.ts +0 -316
- package/examples/translate.js +0 -39
- package/examples/translate_demo.js +0 -254
- package/examples/typescript-demo.ts +0 -251
- package/examples/vitedemo/README.md +0 -164
- package/examples/vitedemo/index.html +0 -580
- package/examples/vitedemo/package-lock.json +0 -1751
- package/examples/vitedemo/package.json +0 -24
- package/examples/vitedemo/src/main.ts +0 -1001
- package/examples/vitedemo/src/vite-env.d.ts +0 -1
- package/examples/vitedemo/test-files/example.dot +0 -14
- package/examples/vitedemo/test-files/example.grd +0 -1
- package/examples/vitedemo/test-files/example.gridset +0 -0
- package/examples/vitedemo/test-files/example.obz +0 -0
- package/examples/vitedemo/test-files/example.opml +0 -18
- package/examples/vitedemo/test-files/simple.obf +0 -53
- package/examples/vitedemo/tsconfig.json +0 -24
- package/examples/vitedemo/vite.config.ts +0 -57
|
@@ -1,1001 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* AAC Processors Browser Demo
|
|
3
|
-
*
|
|
4
|
-
* This demo uses Vite to bundle AACProcessors for browser use.
|
|
5
|
-
* It tests all browser-compatible processors with real file uploads.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
// Polyfill Buffer for browser environment
|
|
9
|
-
if (typeof (window as any).Buffer === 'undefined') {
|
|
10
|
-
// Create a proper Buffer wrapper class that extends Uint8Array
|
|
11
|
-
class BufferWrapper extends Uint8Array {
|
|
12
|
-
constructor(data: any, byteOffset?: number, length?: number) {
|
|
13
|
-
if (typeof data === 'number') {
|
|
14
|
-
// Alloc case: data is the size
|
|
15
|
-
super(data);
|
|
16
|
-
} else if (Array.isArray(data)) {
|
|
17
|
-
super(data);
|
|
18
|
-
} else if (data instanceof ArrayBuffer) {
|
|
19
|
-
super(data, byteOffset || 0, length);
|
|
20
|
-
} else if (data instanceof Uint8Array) {
|
|
21
|
-
super(data.buffer, data.byteOffset, data.length);
|
|
22
|
-
} else if (typeof data === 'string') {
|
|
23
|
-
const encoder = new TextEncoder();
|
|
24
|
-
super(encoder.encode(data));
|
|
25
|
-
} else {
|
|
26
|
-
super(0);
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
toString(encoding: string = 'utf8'): string {
|
|
31
|
-
if (encoding === 'utf8' || encoding === 'utf-8') {
|
|
32
|
-
const decoder = new TextDecoder('utf-8');
|
|
33
|
-
return decoder.decode(this);
|
|
34
|
-
}
|
|
35
|
-
throw new Error(`Buffer.toString: encoding ${encoding} not supported`);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
static from(data: any, encoding?: string): BufferWrapper {
|
|
39
|
-
return new BufferWrapper(data);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
static alloc(size: number): BufferWrapper {
|
|
43
|
-
return new BufferWrapper(size);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
static allocUnsafe(size: number): BufferWrapper {
|
|
47
|
-
return new BufferWrapper(size);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
static concat(list: Uint8Array[], totalLength?: number): BufferWrapper {
|
|
51
|
-
const result = new Uint8Array(totalLength || list.reduce((sum, arr) => sum + arr.length, 0));
|
|
52
|
-
let offset = 0;
|
|
53
|
-
for (const arr of list) {
|
|
54
|
-
result.set(arr, offset);
|
|
55
|
-
offset += arr.length;
|
|
56
|
-
}
|
|
57
|
-
return new BufferWrapper(result.buffer, result.byteOffset, result.length);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
static isBuffer(obj: any): boolean {
|
|
61
|
-
return obj instanceof BufferWrapper;
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
(window as any).Buffer = BufferWrapper as any;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
import {
|
|
69
|
-
configureSqlJs,
|
|
70
|
-
getProcessor,
|
|
71
|
-
getSupportedExtensions,
|
|
72
|
-
DotProcessor,
|
|
73
|
-
OpmlProcessor,
|
|
74
|
-
ObfProcessor,
|
|
75
|
-
GridsetProcessor,
|
|
76
|
-
SnapProcessor,
|
|
77
|
-
TouchChatProcessor,
|
|
78
|
-
ApplePanelsProcessor,
|
|
79
|
-
AstericsGridProcessor,
|
|
80
|
-
AACTree,
|
|
81
|
-
AACPage,
|
|
82
|
-
AACButton
|
|
83
|
-
} from 'aac-processors';
|
|
84
|
-
import { validateFileOrBuffer, type ValidationResult } from 'aac-processors/validation';
|
|
85
|
-
|
|
86
|
-
import sqlWasmUrl from 'sql.js/dist/sql-wasm.wasm?url';
|
|
87
|
-
|
|
88
|
-
configureSqlJs({
|
|
89
|
-
locateFile: () => sqlWasmUrl
|
|
90
|
-
});
|
|
91
|
-
|
|
92
|
-
// UI Elements
|
|
93
|
-
const dropArea = document.getElementById('dropArea') as HTMLElement;
|
|
94
|
-
const fileInput = document.getElementById('fileInput') as HTMLInputElement;
|
|
95
|
-
const processBtn = document.getElementById('processBtn') as HTMLButtonElement;
|
|
96
|
-
const validateBtn = document.getElementById('validateBtn') as HTMLButtonElement;
|
|
97
|
-
const runTestsBtn = document.getElementById('runTestsBtn') as HTMLButtonElement;
|
|
98
|
-
const clearBtn = document.getElementById('clearBtn') as HTMLButtonElement;
|
|
99
|
-
const fileInfo = document.getElementById('fileInfo') as HTMLElement;
|
|
100
|
-
const processorName = document.getElementById('processorName') as HTMLElement;
|
|
101
|
-
const fileDetails = document.getElementById('fileDetails') as HTMLElement;
|
|
102
|
-
const stats = document.getElementById('stats') as HTMLElement;
|
|
103
|
-
const results = document.getElementById('results') as HTMLElement;
|
|
104
|
-
const logPanel = document.getElementById('logPanel') as HTMLElement;
|
|
105
|
-
const testResults = document.getElementById('testResults') as HTMLElement;
|
|
106
|
-
const testList = document.getElementById('testList') as HTMLElement;
|
|
107
|
-
const validationPanel = document.getElementById('validationPanel') as HTMLElement;
|
|
108
|
-
const validationSummary = document.getElementById('validationSummary') as HTMLElement;
|
|
109
|
-
const validationList = document.getElementById('validationList') as HTMLElement;
|
|
110
|
-
const tabButtons = document.querySelectorAll('.tab-btn') as NodeListOf<HTMLButtonElement>;
|
|
111
|
-
const inspectTab = document.getElementById('inspectTab') as HTMLElement;
|
|
112
|
-
const pagesetTab = document.getElementById('pagesetTab') as HTMLElement;
|
|
113
|
-
const templateSelect = document.getElementById('templateSelect') as HTMLSelectElement;
|
|
114
|
-
const formatSelect = document.getElementById('formatSelect') as HTMLSelectElement;
|
|
115
|
-
const createPagesetBtn = document.getElementById('createPagesetBtn') as HTMLButtonElement;
|
|
116
|
-
const previewPagesetBtn = document.getElementById('previewPagesetBtn') as HTMLButtonElement;
|
|
117
|
-
const convertToObfBtn = document.getElementById('convertToObfBtn') as HTMLButtonElement;
|
|
118
|
-
const convertToObzBtn = document.getElementById('convertToObzBtn') as HTMLButtonElement;
|
|
119
|
-
const conversionStatus = document.getElementById('conversionStatus') as HTMLElement;
|
|
120
|
-
const pagesetOutput = document.getElementById('pagesetOutput') as HTMLElement;
|
|
121
|
-
|
|
122
|
-
// State
|
|
123
|
-
let currentFile: File | null = null;
|
|
124
|
-
let currentProcessor: any = null;
|
|
125
|
-
let currentTree: AACTree | null = null;
|
|
126
|
-
let currentSourceLabel = 'pageset';
|
|
127
|
-
|
|
128
|
-
// Tabs
|
|
129
|
-
function setActiveTab(tabId: string) {
|
|
130
|
-
tabButtons.forEach((btn) => {
|
|
131
|
-
btn.classList.toggle('active', btn.dataset.tab === tabId);
|
|
132
|
-
});
|
|
133
|
-
inspectTab.classList.toggle('active', tabId === 'inspectTab');
|
|
134
|
-
pagesetTab.classList.toggle('active', tabId === 'pagesetTab');
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
tabButtons.forEach((btn) => {
|
|
138
|
-
btn.addEventListener('click', () => {
|
|
139
|
-
setActiveTab(btn.dataset.tab || 'inspectTab');
|
|
140
|
-
});
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
// Logging
|
|
144
|
-
function log(message: string, type: 'info' | 'success' | 'error' | 'warn' = 'info') {
|
|
145
|
-
const entry = document.createElement('div');
|
|
146
|
-
entry.className = `log-entry log-${type}`;
|
|
147
|
-
entry.textContent = `[${new Date().toLocaleTimeString()}] ${message}`;
|
|
148
|
-
logPanel.appendChild(entry);
|
|
149
|
-
logPanel.scrollTop = logPanel.scrollHeight;
|
|
150
|
-
console.log(`[${type.toUpperCase()}]`, message);
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
function setConversionStatus(message: string, state: 'success' | 'warn' | 'info' = 'info') {
|
|
154
|
-
conversionStatus.textContent = message;
|
|
155
|
-
conversionStatus.classList.remove('success', 'warn');
|
|
156
|
-
if (state !== 'info') {
|
|
157
|
-
conversionStatus.classList.add(state);
|
|
158
|
-
}
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
function updateConvertButtons() {
|
|
162
|
-
const hasTree = !!currentTree;
|
|
163
|
-
convertToObfBtn.disabled = !hasTree;
|
|
164
|
-
convertToObzBtn.disabled = !hasTree;
|
|
165
|
-
if (!hasTree) {
|
|
166
|
-
setConversionStatus('No pageset loaded yet.', 'info');
|
|
167
|
-
} else {
|
|
168
|
-
setConversionStatus(`Ready to export: ${currentSourceLabel}`, 'success');
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
function updateStatsForTree(tree: AACTree, textCount?: number, loadTimeMs?: number) {
|
|
173
|
-
const pageCount = Object.keys(tree.pages).length;
|
|
174
|
-
const buttonCount = Object.values(tree.pages).reduce(
|
|
175
|
-
(sum: number, page: AACPage) => sum + page.buttons.length,
|
|
176
|
-
0
|
|
177
|
-
);
|
|
178
|
-
|
|
179
|
-
document.getElementById('pageCount')!.textContent = pageCount.toString();
|
|
180
|
-
document.getElementById('buttonCount')!.textContent = buttonCount.toString();
|
|
181
|
-
document.getElementById('textCount')!.textContent = (textCount ?? 0).toString();
|
|
182
|
-
document.getElementById('loadTime')!.textContent =
|
|
183
|
-
loadTimeMs !== undefined ? `${loadTimeMs.toFixed(0)}ms` : '—';
|
|
184
|
-
stats.style.display = 'grid';
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
function collectTextCount(tree: AACTree): number {
|
|
188
|
-
const texts = new Set<string>();
|
|
189
|
-
Object.values(tree.pages).forEach((page) => {
|
|
190
|
-
if (page.name) texts.add(page.name);
|
|
191
|
-
page.buttons.forEach((button) => {
|
|
192
|
-
if (button.label) texts.add(button.label);
|
|
193
|
-
if (button.message) texts.add(button.message);
|
|
194
|
-
});
|
|
195
|
-
});
|
|
196
|
-
return texts.size;
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Get file extension
|
|
200
|
-
function getFileExtension(filename: string): string {
|
|
201
|
-
const match = filename.toLowerCase().match(/\.\w+$/);
|
|
202
|
-
return match ? match[0] : '';
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
// Format file size
|
|
206
|
-
function formatFileSize(bytes: number): string {
|
|
207
|
-
if (bytes < 1024) return bytes + ' B';
|
|
208
|
-
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
|
209
|
-
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
// Handle file selection
|
|
213
|
-
function handleFile(file: File) {
|
|
214
|
-
currentFile = file;
|
|
215
|
-
const extension = getFileExtension(file.name);
|
|
216
|
-
|
|
217
|
-
log(`Selected file: ${file.name} (${formatFileSize(file.size)})`, 'info');
|
|
218
|
-
|
|
219
|
-
// Check if extension is supported
|
|
220
|
-
if (!getSupportedExtensions().includes(extension)) {
|
|
221
|
-
log(`Unsupported file type: ${extension}`, 'error');
|
|
222
|
-
processorName.textContent = '❌ Unsupported file type';
|
|
223
|
-
fileDetails.textContent = extension;
|
|
224
|
-
fileInfo.style.display = 'block';
|
|
225
|
-
processBtn.disabled = true;
|
|
226
|
-
validateBtn.disabled = true;
|
|
227
|
-
return;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
// Get processor
|
|
231
|
-
try {
|
|
232
|
-
currentProcessor = getProcessor(extension);
|
|
233
|
-
processorName.textContent = `✅ ${currentProcessor.constructor.name}`;
|
|
234
|
-
fileDetails.textContent = `${file.name} • ${formatFileSize(file.size)}`;
|
|
235
|
-
fileInfo.style.display = 'block';
|
|
236
|
-
processBtn.disabled = false;
|
|
237
|
-
validateBtn.disabled = false;
|
|
238
|
-
currentSourceLabel = file.name;
|
|
239
|
-
|
|
240
|
-
log(`Using processor: ${currentProcessor.constructor.name}`, 'success');
|
|
241
|
-
} catch (error) {
|
|
242
|
-
log(`Error getting processor: ${(error as Error).message}`, 'error');
|
|
243
|
-
processBtn.disabled = true;
|
|
244
|
-
validateBtn.disabled = true;
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
// Drag and drop handlers
|
|
249
|
-
dropArea.addEventListener('dragover', (e) => {
|
|
250
|
-
e.preventDefault();
|
|
251
|
-
dropArea.classList.add('dragover');
|
|
252
|
-
});
|
|
253
|
-
|
|
254
|
-
dropArea.addEventListener('dragleave', () => {
|
|
255
|
-
dropArea.classList.remove('dragover');
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
dropArea.addEventListener('drop', (e) => {
|
|
259
|
-
e.preventDefault();
|
|
260
|
-
dropArea.classList.remove('dragover');
|
|
261
|
-
|
|
262
|
-
const file = e.dataTransfer?.files[0];
|
|
263
|
-
if (file) {
|
|
264
|
-
fileInput.files = e.dataTransfer!.files;
|
|
265
|
-
handleFile(file);
|
|
266
|
-
}
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
dropArea.addEventListener('click', () => {
|
|
270
|
-
fileInput.click();
|
|
271
|
-
});
|
|
272
|
-
|
|
273
|
-
fileInput.addEventListener('change', (e) => {
|
|
274
|
-
const file = (e.target as HTMLInputElement).files?.[0];
|
|
275
|
-
if (file) {
|
|
276
|
-
handleFile(file);
|
|
277
|
-
}
|
|
278
|
-
});
|
|
279
|
-
|
|
280
|
-
// Process file
|
|
281
|
-
processBtn.addEventListener('click', async () => {
|
|
282
|
-
if (!currentFile || !currentProcessor) return;
|
|
283
|
-
|
|
284
|
-
const startTime = performance.now();
|
|
285
|
-
log('Processing file...', 'info');
|
|
286
|
-
|
|
287
|
-
try {
|
|
288
|
-
processBtn.disabled = true;
|
|
289
|
-
results.innerHTML = '<p style="text-align: center; padding: 40px;">⏳ Loading...</p>';
|
|
290
|
-
|
|
291
|
-
// Read file as ArrayBuffer
|
|
292
|
-
const arrayBuffer = await currentFile.arrayBuffer();
|
|
293
|
-
|
|
294
|
-
// Load into tree
|
|
295
|
-
log('Loading tree structure...', 'info');
|
|
296
|
-
currentTree = await currentProcessor.loadIntoTree(arrayBuffer);
|
|
297
|
-
|
|
298
|
-
const loadTime = performance.now() - startTime;
|
|
299
|
-
log(`Tree loaded in ${loadTime.toFixed(0)}ms`, 'success');
|
|
300
|
-
|
|
301
|
-
// Extract texts
|
|
302
|
-
log('Extracting texts...', 'info');
|
|
303
|
-
const texts = await currentProcessor.extractTexts(arrayBuffer);
|
|
304
|
-
log(`Extracted ${texts.length} texts`, 'success');
|
|
305
|
-
|
|
306
|
-
// Update stats
|
|
307
|
-
updateStatsForTree(currentTree, texts.length, loadTime);
|
|
308
|
-
|
|
309
|
-
// Display results
|
|
310
|
-
displayResults(currentTree);
|
|
311
|
-
updateConvertButtons();
|
|
312
|
-
|
|
313
|
-
log(`✅ Successfully processed ${Object.keys(currentTree.pages).length} pages`, 'success');
|
|
314
|
-
} catch (error) {
|
|
315
|
-
const errorMsg = (error as Error).message;
|
|
316
|
-
log(`❌ Error: ${errorMsg}`, 'error');
|
|
317
|
-
results.innerHTML = `<p style="color: #f48771; text-align: center; padding: 40px;">
|
|
318
|
-
❌ Error: ${errorMsg}
|
|
319
|
-
</p>`;
|
|
320
|
-
} finally {
|
|
321
|
-
processBtn.disabled = false;
|
|
322
|
-
}
|
|
323
|
-
});
|
|
324
|
-
|
|
325
|
-
function collectValidationMessages(
|
|
326
|
-
result: ValidationResult,
|
|
327
|
-
prefix = ''
|
|
328
|
-
): Array<{ type: 'error' | 'warn'; message: string }> {
|
|
329
|
-
const messages: Array<{ type: 'error' | 'warn'; message: string }> = [];
|
|
330
|
-
const label = prefix ? `${prefix}: ` : '';
|
|
331
|
-
result.results.forEach((check) => {
|
|
332
|
-
if (!check.valid && check.error) {
|
|
333
|
-
messages.push({ type: 'error', message: `${label}${check.description}: ${check.error}` });
|
|
334
|
-
}
|
|
335
|
-
if (check.warnings?.length) {
|
|
336
|
-
check.warnings.forEach((warning) => {
|
|
337
|
-
messages.push({ type: 'warn', message: `${label}${check.description}: ${warning}` });
|
|
338
|
-
});
|
|
339
|
-
}
|
|
340
|
-
});
|
|
341
|
-
result.sub_results?.forEach((sub) => {
|
|
342
|
-
const nextPrefix = `${label}${sub.filename || sub.format}`;
|
|
343
|
-
messages.push(...collectValidationMessages(sub, nextPrefix));
|
|
344
|
-
});
|
|
345
|
-
return messages;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
function renderValidationResult(result: ValidationResult) {
|
|
349
|
-
validationPanel.style.display = 'block';
|
|
350
|
-
validationSummary.classList.remove('success', 'error');
|
|
351
|
-
validationSummary.classList.add(result.valid ? 'success' : 'error');
|
|
352
|
-
validationSummary.textContent = `${result.valid ? '✅ Valid' : '❌ Invalid'} • ${result.format.toUpperCase()} • ${result.errors} errors, ${result.warnings} warnings`;
|
|
353
|
-
|
|
354
|
-
validationList.innerHTML = '';
|
|
355
|
-
const messages = collectValidationMessages(result).slice(0, 30);
|
|
356
|
-
if (messages.length === 0) {
|
|
357
|
-
const empty = document.createElement('div');
|
|
358
|
-
empty.className = 'validation-item';
|
|
359
|
-
empty.textContent = 'No issues reported.';
|
|
360
|
-
validationList.appendChild(empty);
|
|
361
|
-
return;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
messages.forEach((entry) => {
|
|
365
|
-
const item = document.createElement('div');
|
|
366
|
-
item.className = `validation-item ${entry.type}`;
|
|
367
|
-
item.textContent = entry.message;
|
|
368
|
-
validationList.appendChild(item);
|
|
369
|
-
});
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
validateBtn.addEventListener('click', async () => {
|
|
373
|
-
if (!currentFile) return;
|
|
374
|
-
log('Validating file...', 'info');
|
|
375
|
-
|
|
376
|
-
try {
|
|
377
|
-
validateBtn.disabled = true;
|
|
378
|
-
const arrayBuffer = await currentFile.arrayBuffer();
|
|
379
|
-
const result = await validateFileOrBuffer(new Uint8Array(arrayBuffer), currentFile.name);
|
|
380
|
-
renderValidationResult(result);
|
|
381
|
-
log(
|
|
382
|
-
`${result.valid ? '✅' : '❌'} Validation complete: ${result.errors} errors, ${result.warnings} warnings`,
|
|
383
|
-
result.valid ? 'success' : 'warn'
|
|
384
|
-
);
|
|
385
|
-
} catch (error) {
|
|
386
|
-
const errorMsg = (error as Error).message;
|
|
387
|
-
validationPanel.style.display = 'block';
|
|
388
|
-
validationSummary.classList.remove('success');
|
|
389
|
-
validationSummary.classList.add('error');
|
|
390
|
-
validationSummary.textContent = `❌ Validation failed: ${errorMsg}`;
|
|
391
|
-
validationList.innerHTML = '';
|
|
392
|
-
log(`❌ Validation failed: ${errorMsg}`, 'error');
|
|
393
|
-
} finally {
|
|
394
|
-
validateBtn.disabled = !currentFile;
|
|
395
|
-
}
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
// Display results
|
|
399
|
-
function displayResults(tree: AACTree) {
|
|
400
|
-
results.innerHTML = '';
|
|
401
|
-
|
|
402
|
-
const sortedPageIds = Object.keys(tree.pages).sort((a, b) => {
|
|
403
|
-
// Show root page first
|
|
404
|
-
if (a === tree.rootId) return -1;
|
|
405
|
-
if (b === tree.rootId) return 1;
|
|
406
|
-
return a.localeCompare(b);
|
|
407
|
-
});
|
|
408
|
-
|
|
409
|
-
sortedPageIds.forEach((pageId) => {
|
|
410
|
-
const page = tree.pages[pageId];
|
|
411
|
-
const pageCard = document.createElement('div');
|
|
412
|
-
pageCard.className = 'page-card';
|
|
413
|
-
|
|
414
|
-
const pageTitle = document.createElement('div');
|
|
415
|
-
pageTitle.className = 'page-title';
|
|
416
|
-
pageTitle.textContent = `${page.name} ${pageId === tree.rootId ? '🏠' : ''}`;
|
|
417
|
-
pageCard.appendChild(pageTitle);
|
|
418
|
-
|
|
419
|
-
if (page.buttons.length > 0) {
|
|
420
|
-
const buttonGrid = document.createElement('div');
|
|
421
|
-
buttonGrid.className = 'button-grid';
|
|
422
|
-
|
|
423
|
-
page.buttons.forEach((button) => {
|
|
424
|
-
const buttonItem = document.createElement('div');
|
|
425
|
-
buttonItem.className = 'button-item';
|
|
426
|
-
|
|
427
|
-
const label = document.createElement('div');
|
|
428
|
-
label.className = 'button-label';
|
|
429
|
-
label.textContent = button.label || '(no label)';
|
|
430
|
-
buttonItem.appendChild(label);
|
|
431
|
-
|
|
432
|
-
if (button.message) {
|
|
433
|
-
const message = document.createElement('div');
|
|
434
|
-
message.className = 'button-message';
|
|
435
|
-
message.textContent = button.message;
|
|
436
|
-
buttonItem.appendChild(message);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
const type = document.createElement('div');
|
|
440
|
-
type.className = 'button-type';
|
|
441
|
-
type.textContent = button.type;
|
|
442
|
-
|
|
443
|
-
switch (button.type) {
|
|
444
|
-
case 'SPEAK':
|
|
445
|
-
type.classList.add('type-speak');
|
|
446
|
-
break;
|
|
447
|
-
case 'NAVIGATE':
|
|
448
|
-
type.classList.add('type-navigate');
|
|
449
|
-
break;
|
|
450
|
-
default:
|
|
451
|
-
type.classList.add('type-other');
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
buttonItem.appendChild(type);
|
|
455
|
-
|
|
456
|
-
// Click handler
|
|
457
|
-
buttonItem.addEventListener('click', () => {
|
|
458
|
-
if (button.type === 'SPEAK' && button.message) {
|
|
459
|
-
log(`🔊 Speaking: "${button.message}"`, 'info');
|
|
460
|
-
if ('speechSynthesis' in window) {
|
|
461
|
-
const utterance = new SpeechSynthesisUtterance(button.message);
|
|
462
|
-
speechSynthesis.speak(utterance);
|
|
463
|
-
}
|
|
464
|
-
} else if (button.type === 'NAVIGATE' && button.targetPageId) {
|
|
465
|
-
const targetPage = tree.pages[button.targetPageId];
|
|
466
|
-
if (targetPage) {
|
|
467
|
-
log(`🔗 Navigating to: ${targetPage.name}`, 'info');
|
|
468
|
-
// Scroll to page
|
|
469
|
-
const targetCard = Array.from(results.querySelectorAll('.page-card')).find((card) =>
|
|
470
|
-
card.querySelector('.page-title')?.textContent?.includes(targetPage.name)
|
|
471
|
-
);
|
|
472
|
-
if (targetCard) {
|
|
473
|
-
targetCard.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
474
|
-
targetCard.style.animation = 'highlight 1s';
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
}
|
|
478
|
-
});
|
|
479
|
-
|
|
480
|
-
buttonGrid.appendChild(buttonItem);
|
|
481
|
-
});
|
|
482
|
-
|
|
483
|
-
pageCard.appendChild(buttonGrid);
|
|
484
|
-
} else {
|
|
485
|
-
const noButtons = document.createElement('p');
|
|
486
|
-
noButtons.textContent = 'No buttons';
|
|
487
|
-
noButtons.style.color = '#999';
|
|
488
|
-
noButtons.style.fontSize = '12px';
|
|
489
|
-
pageCard.appendChild(noButtons);
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
results.appendChild(pageCard);
|
|
493
|
-
});
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// Clear results
|
|
497
|
-
clearBtn.addEventListener('click', () => {
|
|
498
|
-
currentFile = null;
|
|
499
|
-
currentProcessor = null;
|
|
500
|
-
currentTree = null;
|
|
501
|
-
currentSourceLabel = 'pageset';
|
|
502
|
-
fileInput.value = '';
|
|
503
|
-
fileInfo.style.display = 'none';
|
|
504
|
-
stats.style.display = 'none';
|
|
505
|
-
results.innerHTML = '<p style="color: #999; text-align: center; padding: 40px;">Load a file to see its contents here</p>';
|
|
506
|
-
testResults.style.display = 'none';
|
|
507
|
-
validationPanel.style.display = 'none';
|
|
508
|
-
validationSummary.textContent = '';
|
|
509
|
-
validationList.innerHTML = '';
|
|
510
|
-
logPanel.innerHTML = '<div class="log-entry log-info">Cleared. Ready to process files...</div>';
|
|
511
|
-
pagesetOutput.textContent = 'Generate or convert a pageset to preview the output JSON.';
|
|
512
|
-
updateConvertButtons();
|
|
513
|
-
});
|
|
514
|
-
|
|
515
|
-
function sanitizeFilename(name: string): string {
|
|
516
|
-
return name
|
|
517
|
-
.toLowerCase()
|
|
518
|
-
.replace(/[^a-z0-9]+/g, '-')
|
|
519
|
-
.replace(/(^-|-$)/g, '') || 'pageset';
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
function buildSampleTree(template: string): AACTree {
|
|
523
|
-
const tree = new AACTree();
|
|
524
|
-
tree.metadata = {
|
|
525
|
-
name: template === 'home' ? 'Home & Core Demo' : 'Starter Demo',
|
|
526
|
-
description: 'Generated in the AAC Processors browser demo',
|
|
527
|
-
locale: 'en',
|
|
528
|
-
};
|
|
529
|
-
|
|
530
|
-
if (template === 'home') {
|
|
531
|
-
const hello = new AACButton({ id: 'hello', label: 'Hello', message: 'Hello', action: { type: 'SPEAK' } });
|
|
532
|
-
const want = new AACButton({ id: 'want', label: 'I want', message: 'I want', action: { type: 'SPEAK' } });
|
|
533
|
-
const help = new AACButton({ id: 'help', label: 'Help', message: 'Help', action: { type: 'SPEAK' } });
|
|
534
|
-
const more = new AACButton({
|
|
535
|
-
id: 'more',
|
|
536
|
-
label: 'More',
|
|
537
|
-
targetPageId: 'core',
|
|
538
|
-
action: { type: 'NAVIGATE', targetPageId: 'core' },
|
|
539
|
-
});
|
|
540
|
-
const yes = new AACButton({ id: 'yes', label: 'Yes', message: 'Yes', action: { type: 'SPEAK' } });
|
|
541
|
-
const no = new AACButton({ id: 'no', label: 'No', message: 'No', action: { type: 'SPEAK' } });
|
|
542
|
-
const stop = new AACButton({ id: 'stop', label: 'Stop', message: 'Stop', action: { type: 'SPEAK' } });
|
|
543
|
-
const go = new AACButton({ id: 'go', label: 'Go', message: 'Go', action: { type: 'SPEAK' } });
|
|
544
|
-
const food = new AACButton({
|
|
545
|
-
id: 'food',
|
|
546
|
-
label: 'Food',
|
|
547
|
-
targetPageId: 'food',
|
|
548
|
-
action: { type: 'NAVIGATE', targetPageId: 'food' },
|
|
549
|
-
});
|
|
550
|
-
|
|
551
|
-
const homePage = new AACPage({
|
|
552
|
-
id: 'home',
|
|
553
|
-
name: 'Home',
|
|
554
|
-
buttons: [hello, want, help, more, yes, no, stop, go, food],
|
|
555
|
-
grid: [
|
|
556
|
-
[hello, want, help],
|
|
557
|
-
[more, yes, no],
|
|
558
|
-
[stop, go, food],
|
|
559
|
-
],
|
|
560
|
-
});
|
|
561
|
-
|
|
562
|
-
const hungry = new AACButton({ id: 'hungry', label: 'Hungry', message: 'I am hungry', action: { type: 'SPEAK' } });
|
|
563
|
-
const drink = new AACButton({ id: 'drink', label: 'Drink', message: 'I want a drink', action: { type: 'SPEAK' } });
|
|
564
|
-
const snack = new AACButton({ id: 'snack', label: 'Snack', message: 'Snack', action: { type: 'SPEAK' } });
|
|
565
|
-
const backFood = new AACButton({
|
|
566
|
-
id: 'back-food',
|
|
567
|
-
label: 'Back',
|
|
568
|
-
targetPageId: 'home',
|
|
569
|
-
action: { type: 'NAVIGATE', targetPageId: 'home' },
|
|
570
|
-
});
|
|
571
|
-
|
|
572
|
-
const foodPage = new AACPage({
|
|
573
|
-
id: 'food',
|
|
574
|
-
name: 'Food',
|
|
575
|
-
buttons: [hungry, drink, snack, backFood],
|
|
576
|
-
grid: [
|
|
577
|
-
[hungry, drink],
|
|
578
|
-
[snack, backFood],
|
|
579
|
-
],
|
|
580
|
-
});
|
|
581
|
-
|
|
582
|
-
const coreYes = new AACButton({ id: 'core-yes', label: 'Yes', message: 'Yes', action: { type: 'SPEAK' } });
|
|
583
|
-
const coreNo = new AACButton({ id: 'core-no', label: 'No', message: 'No', action: { type: 'SPEAK' } });
|
|
584
|
-
const coreStop = new AACButton({ id: 'core-stop', label: 'Stop', message: 'Stop', action: { type: 'SPEAK' } });
|
|
585
|
-
const coreGo = new AACButton({ id: 'core-go', label: 'Go', message: 'Go', action: { type: 'SPEAK' } });
|
|
586
|
-
const backCore = new AACButton({
|
|
587
|
-
id: 'back-core',
|
|
588
|
-
label: 'Back',
|
|
589
|
-
targetPageId: 'home',
|
|
590
|
-
action: { type: 'NAVIGATE', targetPageId: 'home' },
|
|
591
|
-
});
|
|
592
|
-
|
|
593
|
-
const corePage = new AACPage({
|
|
594
|
-
id: 'core',
|
|
595
|
-
name: 'Core Words',
|
|
596
|
-
buttons: [coreYes, coreNo, coreStop, coreGo, backCore],
|
|
597
|
-
grid: [
|
|
598
|
-
[coreYes, coreNo],
|
|
599
|
-
[coreStop, coreGo],
|
|
600
|
-
[backCore, null],
|
|
601
|
-
],
|
|
602
|
-
});
|
|
603
|
-
|
|
604
|
-
tree.addPage(homePage);
|
|
605
|
-
tree.addPage(corePage);
|
|
606
|
-
tree.addPage(foodPage);
|
|
607
|
-
tree.rootId = 'home';
|
|
608
|
-
return tree;
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
const hello = new AACButton({ id: 'hello', label: 'Hello', message: 'Hello', action: { type: 'SPEAK' } });
|
|
612
|
-
const thanks = new AACButton({ id: 'thanks', label: 'Thanks', message: 'Thank you', action: { type: 'SPEAK' } });
|
|
613
|
-
const yes = new AACButton({ id: 'yes', label: 'Yes', message: 'Yes', action: { type: 'SPEAK' } });
|
|
614
|
-
const more = new AACButton({
|
|
615
|
-
id: 'more',
|
|
616
|
-
label: 'Feelings',
|
|
617
|
-
targetPageId: 'feelings',
|
|
618
|
-
action: { type: 'NAVIGATE', targetPageId: 'feelings' },
|
|
619
|
-
});
|
|
620
|
-
|
|
621
|
-
const homePage = new AACPage({
|
|
622
|
-
id: 'home',
|
|
623
|
-
name: 'Starter',
|
|
624
|
-
buttons: [hello, thanks, yes, more],
|
|
625
|
-
grid: [
|
|
626
|
-
[hello, thanks],
|
|
627
|
-
[yes, more],
|
|
628
|
-
],
|
|
629
|
-
});
|
|
630
|
-
|
|
631
|
-
const happy = new AACButton({ id: 'happy', label: 'Happy', message: 'I feel happy', action: { type: 'SPEAK' } });
|
|
632
|
-
const sad = new AACButton({ id: 'sad', label: 'Sad', message: 'I feel sad', action: { type: 'SPEAK' } });
|
|
633
|
-
const back = new AACButton({
|
|
634
|
-
id: 'back',
|
|
635
|
-
label: 'Back',
|
|
636
|
-
targetPageId: 'home',
|
|
637
|
-
action: { type: 'NAVIGATE', targetPageId: 'home' },
|
|
638
|
-
});
|
|
639
|
-
|
|
640
|
-
const feelingsPage = new AACPage({
|
|
641
|
-
id: 'feelings',
|
|
642
|
-
name: 'Feelings',
|
|
643
|
-
buttons: [happy, sad, back],
|
|
644
|
-
grid: [
|
|
645
|
-
[happy, sad],
|
|
646
|
-
[back, null],
|
|
647
|
-
],
|
|
648
|
-
});
|
|
649
|
-
|
|
650
|
-
tree.addPage(homePage);
|
|
651
|
-
tree.addPage(feelingsPage);
|
|
652
|
-
tree.rootId = 'home';
|
|
653
|
-
return tree;
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
function buildFallbackObfBoard(page: AACPage, metadata?: AACTree['metadata']) {
|
|
657
|
-
const rows = page.grid.length || 1;
|
|
658
|
-
const columns = page.grid.reduce((max, row) => Math.max(max, row.length), 0) || page.buttons.length;
|
|
659
|
-
const order: (string | null)[][] = [];
|
|
660
|
-
const positions = new Map<string, number>();
|
|
661
|
-
|
|
662
|
-
if (page.grid.length) {
|
|
663
|
-
page.grid.forEach((row, rowIndex) => {
|
|
664
|
-
const orderRow: (string | null)[] = [];
|
|
665
|
-
for (let colIndex = 0; colIndex < columns; colIndex++) {
|
|
666
|
-
const cell = row[colIndex] || null;
|
|
667
|
-
if (cell) {
|
|
668
|
-
const id = String(cell.id ?? '');
|
|
669
|
-
orderRow.push(id);
|
|
670
|
-
positions.set(id, rowIndex * columns + colIndex);
|
|
671
|
-
} else {
|
|
672
|
-
orderRow.push(null);
|
|
673
|
-
}
|
|
674
|
-
}
|
|
675
|
-
order.push(orderRow);
|
|
676
|
-
});
|
|
677
|
-
} else {
|
|
678
|
-
const fallbackRow = page.buttons.map((button, index) => {
|
|
679
|
-
const id = String(button.id ?? '');
|
|
680
|
-
positions.set(id, index);
|
|
681
|
-
return id;
|
|
682
|
-
});
|
|
683
|
-
order.push(fallbackRow);
|
|
684
|
-
}
|
|
685
|
-
|
|
686
|
-
return {
|
|
687
|
-
format: 'open-board-0.1',
|
|
688
|
-
id: page.id,
|
|
689
|
-
locale: metadata?.locale || page.locale || 'en',
|
|
690
|
-
name: page.name || metadata?.name || 'Board',
|
|
691
|
-
description_html: page.descriptionHtml || metadata?.description || '',
|
|
692
|
-
grid: { rows, columns, order },
|
|
693
|
-
buttons: page.buttons.map((button) => ({
|
|
694
|
-
id: button.id,
|
|
695
|
-
label: button.label,
|
|
696
|
-
vocalization: button.message || button.label,
|
|
697
|
-
load_board: button.targetPageId ? { path: button.targetPageId } : undefined,
|
|
698
|
-
box_id: positions.get(String(button.id ?? '')),
|
|
699
|
-
background_color: button.style?.backgroundColor,
|
|
700
|
-
border_color: button.style?.borderColor,
|
|
701
|
-
})),
|
|
702
|
-
};
|
|
703
|
-
}
|
|
704
|
-
|
|
705
|
-
async function buildObfExport(tree: AACTree, format: 'obf' | 'obz') {
|
|
706
|
-
const obfProcessor = new ObfProcessor();
|
|
707
|
-
const obfInternal = obfProcessor as ObfProcessor & {
|
|
708
|
-
createObfBoardFromPage?: (page: AACPage, fallbackName: string, metadata?: AACTree['metadata']) => any;
|
|
709
|
-
};
|
|
710
|
-
|
|
711
|
-
const boards = Object.values(tree.pages).map((page) => ({
|
|
712
|
-
pageId: page.id,
|
|
713
|
-
board: obfInternal.createObfBoardFromPage
|
|
714
|
-
? obfInternal.createObfBoardFromPage(page, 'Board', tree.metadata)
|
|
715
|
-
: buildFallbackObfBoard(page, tree.metadata),
|
|
716
|
-
}));
|
|
717
|
-
|
|
718
|
-
if (format === 'obf') {
|
|
719
|
-
const rootPage = tree.rootId ? tree.getPage(tree.rootId) : Object.values(tree.pages)[0];
|
|
720
|
-
const board =
|
|
721
|
-
boards.find((entry) => entry.pageId === rootPage?.id)?.board ?? boards[0]?.board ?? {};
|
|
722
|
-
const json = JSON.stringify(board, null, 2);
|
|
723
|
-
return { filename: `${sanitizeFilename(tree.metadata?.name || 'pageset')}.obf`, data: json };
|
|
724
|
-
}
|
|
725
|
-
|
|
726
|
-
const module = await import('jszip');
|
|
727
|
-
const JSZip = module.default || module;
|
|
728
|
-
const zip = new JSZip();
|
|
729
|
-
boards.forEach((entry) => {
|
|
730
|
-
zip.file(`${entry.pageId}.obf`, JSON.stringify(entry.board, null, 2));
|
|
731
|
-
});
|
|
732
|
-
const zipData = await zip.generateAsync({ type: 'uint8array' });
|
|
733
|
-
return { filename: `${sanitizeFilename(tree.metadata?.name || 'pageset')}.obz`, data: zipData };
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
function triggerDownload(data: Uint8Array | string, filename: string, mime: string) {
|
|
737
|
-
const blob = new Blob([data], { type: mime });
|
|
738
|
-
const url = URL.createObjectURL(blob);
|
|
739
|
-
const a = document.createElement('a');
|
|
740
|
-
a.href = url;
|
|
741
|
-
a.download = filename;
|
|
742
|
-
a.click();
|
|
743
|
-
URL.revokeObjectURL(url);
|
|
744
|
-
}
|
|
745
|
-
|
|
746
|
-
createPagesetBtn.addEventListener('click', async () => {
|
|
747
|
-
const template = templateSelect.value;
|
|
748
|
-
const format = formatSelect.value === 'obz' ? 'obz' : 'obf';
|
|
749
|
-
const tree = buildSampleTree(template);
|
|
750
|
-
currentTree = tree;
|
|
751
|
-
currentSourceLabel = `${tree.metadata?.name || 'sample pageset'}`;
|
|
752
|
-
updateConvertButtons();
|
|
753
|
-
|
|
754
|
-
const exportData = await buildObfExport(tree, format);
|
|
755
|
-
const isObf = typeof exportData.data === 'string';
|
|
756
|
-
triggerDownload(
|
|
757
|
-
exportData.data,
|
|
758
|
-
exportData.filename,
|
|
759
|
-
isObf ? 'application/json' : 'application/zip'
|
|
760
|
-
);
|
|
761
|
-
|
|
762
|
-
pagesetOutput.textContent = isObf
|
|
763
|
-
? exportData.data
|
|
764
|
-
: `Generated OBZ with ${Object.keys(tree.pages).length} boards.`;
|
|
765
|
-
|
|
766
|
-
log(`Created sample pageset and exported ${exportData.filename}`, 'success');
|
|
767
|
-
setConversionStatus(`Exported ${exportData.filename}`, 'success');
|
|
768
|
-
});
|
|
769
|
-
|
|
770
|
-
previewPagesetBtn.addEventListener('click', () => {
|
|
771
|
-
const tree = buildSampleTree(templateSelect.value);
|
|
772
|
-
currentTree = tree;
|
|
773
|
-
currentSourceLabel = `${tree.metadata?.name || 'sample pageset'}`;
|
|
774
|
-
displayResults(tree);
|
|
775
|
-
updateStatsForTree(tree, collectTextCount(tree));
|
|
776
|
-
updateConvertButtons();
|
|
777
|
-
setActiveTab('inspectTab');
|
|
778
|
-
log('Previewing sample pageset in viewer', 'info');
|
|
779
|
-
});
|
|
780
|
-
|
|
781
|
-
convertToObfBtn.addEventListener('click', async () => {
|
|
782
|
-
if (!currentTree) return;
|
|
783
|
-
const exportData = await buildObfExport(currentTree, 'obf');
|
|
784
|
-
triggerDownload(exportData.data, exportData.filename, 'application/json');
|
|
785
|
-
pagesetOutput.textContent = exportData.data as string;
|
|
786
|
-
log(`Converted ${currentSourceLabel} to ${exportData.filename}`, 'success');
|
|
787
|
-
setConversionStatus(`Exported ${exportData.filename}`, 'success');
|
|
788
|
-
});
|
|
789
|
-
|
|
790
|
-
convertToObzBtn.addEventListener('click', async () => {
|
|
791
|
-
if (!currentTree) return;
|
|
792
|
-
const exportData = await buildObfExport(currentTree, 'obz');
|
|
793
|
-
triggerDownload(exportData.data, exportData.filename, 'application/zip');
|
|
794
|
-
pagesetOutput.textContent = `Generated OBZ with ${Object.keys(currentTree.pages).length} boards.`;
|
|
795
|
-
log(`Converted ${currentSourceLabel} to ${exportData.filename}`, 'success');
|
|
796
|
-
setConversionStatus(`Exported ${exportData.filename}`, 'success');
|
|
797
|
-
});
|
|
798
|
-
|
|
799
|
-
// Run compatibility tests
|
|
800
|
-
runTestsBtn.addEventListener('click', async () => {
|
|
801
|
-
log('Running compatibility tests...', 'info');
|
|
802
|
-
testResults.style.display = 'block';
|
|
803
|
-
testList.innerHTML = '';
|
|
804
|
-
|
|
805
|
-
const tests: { name: string; fn: () => Promise<boolean> }[] = [
|
|
806
|
-
{
|
|
807
|
-
name: 'getProcessor() factory function',
|
|
808
|
-
fn: async () => {
|
|
809
|
-
const dotProc = getProcessor('.dot');
|
|
810
|
-
const opmlProc = getProcessor('.opml');
|
|
811
|
-
const obfProc = getProcessor('.obf');
|
|
812
|
-
const gridsetProc = getProcessor('.gridset');
|
|
813
|
-
const snapProc = getProcessor('.sps');
|
|
814
|
-
const touchChatProc = getProcessor('.ce');
|
|
815
|
-
return (
|
|
816
|
-
dotProc instanceof DotProcessor &&
|
|
817
|
-
opmlProc instanceof OpmlProcessor &&
|
|
818
|
-
obfProc instanceof ObfProcessor &&
|
|
819
|
-
gridsetProc instanceof GridsetProcessor &&
|
|
820
|
-
snapProc instanceof SnapProcessor &&
|
|
821
|
-
touchChatProc instanceof TouchChatProcessor
|
|
822
|
-
);
|
|
823
|
-
}
|
|
824
|
-
},
|
|
825
|
-
{
|
|
826
|
-
name: 'getSupportedExtensions() returns all extensions',
|
|
827
|
-
fn: async () => {
|
|
828
|
-
const extensions = getSupportedExtensions();
|
|
829
|
-
const expected = [
|
|
830
|
-
'.dot',
|
|
831
|
-
'.opml',
|
|
832
|
-
'.obf',
|
|
833
|
-
'.obz',
|
|
834
|
-
'.gridset',
|
|
835
|
-
'.spb',
|
|
836
|
-
'.sps',
|
|
837
|
-
'.ce',
|
|
838
|
-
'.plist',
|
|
839
|
-
'.grd'
|
|
840
|
-
];
|
|
841
|
-
return expected.every((ext) => extensions.includes(ext));
|
|
842
|
-
}
|
|
843
|
-
},
|
|
844
|
-
{
|
|
845
|
-
name: 'DotProcessor instantiation',
|
|
846
|
-
fn: async () => {
|
|
847
|
-
try {
|
|
848
|
-
new DotProcessor();
|
|
849
|
-
return true;
|
|
850
|
-
} catch {
|
|
851
|
-
return false;
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
},
|
|
855
|
-
{
|
|
856
|
-
name: 'OpmlProcessor instantiation',
|
|
857
|
-
fn: async () => {
|
|
858
|
-
try {
|
|
859
|
-
new OpmlProcessor();
|
|
860
|
-
return true;
|
|
861
|
-
} catch {
|
|
862
|
-
return false;
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
},
|
|
866
|
-
{
|
|
867
|
-
name: 'ObfProcessor instantiation',
|
|
868
|
-
fn: async () => {
|
|
869
|
-
try {
|
|
870
|
-
new ObfProcessor();
|
|
871
|
-
return true;
|
|
872
|
-
} catch {
|
|
873
|
-
return false;
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
},
|
|
877
|
-
{
|
|
878
|
-
name: 'GridsetProcessor instantiation',
|
|
879
|
-
fn: async () => {
|
|
880
|
-
try {
|
|
881
|
-
new GridsetProcessor();
|
|
882
|
-
return true;
|
|
883
|
-
} catch {
|
|
884
|
-
return false;
|
|
885
|
-
}
|
|
886
|
-
}
|
|
887
|
-
},
|
|
888
|
-
{
|
|
889
|
-
name: 'SnapProcessor instantiation',
|
|
890
|
-
fn: async () => {
|
|
891
|
-
try {
|
|
892
|
-
new SnapProcessor();
|
|
893
|
-
return true;
|
|
894
|
-
} catch {
|
|
895
|
-
return false;
|
|
896
|
-
}
|
|
897
|
-
}
|
|
898
|
-
},
|
|
899
|
-
{
|
|
900
|
-
name: 'TouchChatProcessor instantiation',
|
|
901
|
-
fn: async () => {
|
|
902
|
-
try {
|
|
903
|
-
new TouchChatProcessor();
|
|
904
|
-
return true;
|
|
905
|
-
} catch {
|
|
906
|
-
return false;
|
|
907
|
-
}
|
|
908
|
-
}
|
|
909
|
-
},
|
|
910
|
-
{
|
|
911
|
-
name: 'ApplePanelsProcessor instantiation',
|
|
912
|
-
fn: async () => {
|
|
913
|
-
try {
|
|
914
|
-
new ApplePanelsProcessor();
|
|
915
|
-
return true;
|
|
916
|
-
} catch {
|
|
917
|
-
return false;
|
|
918
|
-
}
|
|
919
|
-
}
|
|
920
|
-
},
|
|
921
|
-
{
|
|
922
|
-
name: 'AstericsGridProcessor instantiation',
|
|
923
|
-
fn: async () => {
|
|
924
|
-
try {
|
|
925
|
-
new AstericsGridProcessor();
|
|
926
|
-
return true;
|
|
927
|
-
} catch {
|
|
928
|
-
return false;
|
|
929
|
-
}
|
|
930
|
-
}
|
|
931
|
-
},
|
|
932
|
-
{
|
|
933
|
-
name: 'Processors accept ArrayBuffer type',
|
|
934
|
-
fn: async () => {
|
|
935
|
-
try {
|
|
936
|
-
const proc = new DotProcessor();
|
|
937
|
-
const buffer = new Uint8Array([123, 125]); // Invalid but tests type acceptance
|
|
938
|
-
await proc.loadIntoTree(buffer); // Will fail but tests that it accepts the type
|
|
939
|
-
return true;
|
|
940
|
-
} catch {
|
|
941
|
-
return true; // Expected to fail with invalid data, but type was accepted
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
}
|
|
945
|
-
];
|
|
946
|
-
|
|
947
|
-
let passed = 0;
|
|
948
|
-
let failed = 0;
|
|
949
|
-
|
|
950
|
-
for (const test of tests) {
|
|
951
|
-
const item = document.createElement('div');
|
|
952
|
-
item.className = 'test-item';
|
|
953
|
-
|
|
954
|
-
const status = document.createElement('div');
|
|
955
|
-
status.className = 'test-status test-pending';
|
|
956
|
-
status.textContent = '⏳';
|
|
957
|
-
|
|
958
|
-
const name = document.createElement('div');
|
|
959
|
-
name.className = 'test-name';
|
|
960
|
-
name.textContent = test.name;
|
|
961
|
-
|
|
962
|
-
item.appendChild(status);
|
|
963
|
-
item.appendChild(name);
|
|
964
|
-
testList.appendChild(item);
|
|
965
|
-
|
|
966
|
-
try {
|
|
967
|
-
const result = await test.fn();
|
|
968
|
-
if (result) {
|
|
969
|
-
status.className = 'test-status test-pass';
|
|
970
|
-
status.textContent = '✓';
|
|
971
|
-
passed++;
|
|
972
|
-
log(`✓ ${test.name}`, 'success');
|
|
973
|
-
} else {
|
|
974
|
-
status.className = 'test-status test-fail';
|
|
975
|
-
status.textContent = '✗';
|
|
976
|
-
failed++;
|
|
977
|
-
log(`✗ ${test.name}`, 'error');
|
|
978
|
-
}
|
|
979
|
-
} catch (error) {
|
|
980
|
-
status.className = 'test-status test-fail';
|
|
981
|
-
status.textContent = '✗';
|
|
982
|
-
failed++;
|
|
983
|
-
log(`✗ ${test.name}: ${(error as Error).message}`, 'error');
|
|
984
|
-
}
|
|
985
|
-
}
|
|
986
|
-
|
|
987
|
-
log(`Tests complete: ${passed} passed, ${failed} failed`, passed === tests.length ? 'success' : 'warn');
|
|
988
|
-
|
|
989
|
-
const summary = document.createElement('div');
|
|
990
|
-
summary.style.marginTop = '15px';
|
|
991
|
-
summary.style.paddingTop = '15px';
|
|
992
|
-
summary.style.borderTop = '2px solid #e0e0e0';
|
|
993
|
-
summary.style.fontWeight = '600';
|
|
994
|
-
summary.textContent = `📊 Summary: ${passed}/${tests.length} tests passed`;
|
|
995
|
-
testList.appendChild(summary);
|
|
996
|
-
});
|
|
997
|
-
|
|
998
|
-
// Log initialization
|
|
999
|
-
log('✅ AAC Processors Browser Demo initialized', 'success');
|
|
1000
|
-
log('📋 Supported extensions: ' + getSupportedExtensions().join(', '), 'info');
|
|
1001
|
-
log('💡 Drop a file or click to upload', 'info');
|