@willwade/aac-processors 0.0.21 → 0.0.23

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.
Files changed (32) hide show
  1. package/README.md +19 -27
  2. package/dist/core/treeStructure.d.ts +2 -2
  3. package/dist/core/treeStructure.js +4 -1
  4. package/dist/processors/applePanelsProcessor.js +166 -123
  5. package/dist/processors/astericsGridProcessor.js +121 -105
  6. package/dist/processors/dotProcessor.js +83 -65
  7. package/dist/processors/gridsetProcessor.js +2 -0
  8. package/dist/processors/obfProcessor.js +11 -4
  9. package/dist/processors/opmlProcessor.js +82 -44
  10. package/dist/processors/snapProcessor.js +19 -9
  11. package/dist/processors/touchchatProcessor.js +72 -21
  12. package/dist/utilities/analytics/metrics/core.d.ts +1 -1
  13. package/dist/utilities/analytics/metrics/core.js +191 -212
  14. package/dist/validation/applePanelsValidator.d.ts +10 -0
  15. package/dist/validation/applePanelsValidator.js +124 -0
  16. package/dist/validation/astericsValidator.d.ts +16 -0
  17. package/dist/validation/astericsValidator.js +115 -0
  18. package/dist/validation/dotValidator.d.ts +10 -0
  19. package/dist/validation/dotValidator.js +113 -0
  20. package/dist/validation/excelValidator.d.ts +10 -0
  21. package/dist/validation/excelValidator.js +89 -0
  22. package/dist/validation/index.d.ts +14 -1
  23. package/dist/validation/index.js +104 -1
  24. package/dist/validation/obfsetValidator.d.ts +10 -0
  25. package/dist/validation/obfsetValidator.js +103 -0
  26. package/dist/validation/opmlValidator.d.ts +10 -0
  27. package/dist/validation/opmlValidator.js +107 -0
  28. package/dist/validation/validationTypes.d.ts +22 -0
  29. package/dist/validation/validationTypes.js +38 -1
  30. package/dist/validation.d.ts +8 -2
  31. package/dist/validation.js +16 -1
  32. package/package.json +1 -1
@@ -104,7 +104,8 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
104
104
  // Set toolbarId if there's a global toolbar
105
105
  if (hasGlobalToolbar) {
106
106
  tree.toolbarId = toolbarId || null;
107
- tree.rootId = toolbarId || defaultHomePageId || null;
107
+ // Use defaultHomePageId as root (the content pageset), not the toolbar
108
+ tree.rootId = defaultHomePageId || null;
108
109
  }
109
110
  else if (defaultHomePageId) {
110
111
  tree.rootId = defaultHomePageId;
@@ -114,14 +115,7 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
114
115
  catch (e) {
115
116
  console.warn('[SnapProcessor] Failed to load PageSetProperties:', e);
116
117
  }
117
- // If still no root, look for a page titled "Tool Bar" or similar
118
- if (!tree.rootId || tree.rootId === defaultHomePageId) {
119
- const toolbarPage = pages.find((p) => p.Title === 'Tool Bar' || p.Name === 'Tool Bar');
120
- if (toolbarPage) {
121
- tree.rootId = String(toolbarPage.UniqueId || toolbarPage.Id);
122
- }
123
- }
124
- // If still no root, fallback to first page
118
+ // If still no root, fallback to first page (but don't override a valid defaultHomePageId)
125
119
  if (!tree.rootId && pages.length > 0) {
126
120
  tree.rootId = String(pages[0].UniqueId || pages[0].Id);
127
121
  }
@@ -144,6 +138,22 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
144
138
  });
145
139
  tree.addPage(page);
146
140
  });
141
+ // Try to find toolbar page even if not set in PageSetProperties
142
+ // Some SNAP files have a toolbar page but don't set ToolBarUniqueId
143
+ // This must be done AFTER pages are added to the tree
144
+ if (!tree.toolbarId || tree.toolbarId === '00000000-0000-0000-0000-000000000000') {
145
+ const toolbarPage = Object.values(tree.pages).find((p) => {
146
+ const name = (p.name || '').toLowerCase();
147
+ return name === 'tool bar' || name === 'toolbar';
148
+ });
149
+ if (toolbarPage) {
150
+ tree.toolbarId = toolbarPage.id;
151
+ // Update metadata to reflect toolbar detection
152
+ if (tree.metadata) {
153
+ tree.metadata.hasGlobalToolbar = true;
154
+ }
155
+ }
156
+ }
147
157
  const scanGroupsByPageLayout = new Map();
148
158
  try {
149
159
  const scanGroupRows = db
@@ -159,7 +159,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
159
159
  });
160
160
  // Load button boxes and their cells
161
161
  const buttonBoxQuery = `
162
- SELECT bbc.*, b.*, bb.id as box_id
162
+ SELECT bbc.*, b.*, bb.id as box_id, bb.layout_x, bb.layout_y
163
163
  FROM button_box_cells bbc
164
164
  JOIN buttons b ON b.resource_id = bbc.resource_id
165
165
  JOIN button_boxes bb ON bb.id = bbc.button_box_id
@@ -168,8 +168,14 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
168
168
  const buttonBoxCells = db.prepare(buttonBoxQuery).all();
169
169
  const buttonBoxes = new Map();
170
170
  buttonBoxCells.forEach((cell) => {
171
- if (!buttonBoxes.has(cell.box_id)) {
172
- buttonBoxes.set(cell.box_id, []);
171
+ let boxData = buttonBoxes.get(cell.box_id);
172
+ if (!boxData) {
173
+ boxData = {
174
+ layoutX: cell.layout_x || 10,
175
+ layoutY: cell.layout_y || 6,
176
+ buttons: [],
177
+ };
178
+ buttonBoxes.set(cell.box_id, boxData);
173
179
  }
174
180
  const style = buttonStyles.get(cell.button_style_id);
175
181
  // Create semantic action for TouchChat button
@@ -213,7 +219,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
213
219
  labelOnTop: toBooleanOrUndefined(style?.label_on_top),
214
220
  },
215
221
  });
216
- buttonBoxes.get(cell.box_id)?.push({
222
+ boxData.buttons.push({
217
223
  button,
218
224
  location: cell.location || 0,
219
225
  spanX: cell.span_x || 1,
@@ -228,8 +234,8 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
228
234
  // Use mapped string ID if available, otherwise use numeric ID as string
229
235
  const pageId = numericToRid.get(instance.page_id) || String(instance.page_id);
230
236
  const page = tree.getPage(pageId);
231
- const buttons = buttonBoxes.get(instance.button_box_id);
232
- if (page && buttons) {
237
+ const boxData = buttonBoxes.get(instance.button_box_id);
238
+ if (page && boxData) {
233
239
  // Initialize page grid if not exists (assume max 10x10 grid)
234
240
  if (!pageGrids.has(pageId)) {
235
241
  const grid = [];
@@ -243,15 +249,13 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
243
249
  return;
244
250
  const boxX = Number(instance.position_x) || 0;
245
251
  const boxY = Number(instance.position_y) || 0;
246
- const boxWidth = Number(instance.size_x) || 1;
252
+ const boxWidth = boxData.layoutX; // Use layout_x from button_boxes, not size_x from instance
247
253
  // boxHeight not currently used but kept for future span calculations
248
- // const boxHeight = Number(instance.size_y) || 1;
249
- buttons.forEach(({ button, location, spanX, spanY }) => {
254
+ // const boxHeight = boxData.layoutY;
255
+ boxData.buttons.forEach(({ button, location, spanX, spanY }) => {
250
256
  const safeLocation = Number(location) || 0;
251
257
  const safeSpanX = Number(spanX) || 1;
252
258
  const safeSpanY = Number(spanY) || 1;
253
- // Add button to page
254
- page.addButton(button);
255
259
  // Calculate button position within the button box
256
260
  // location is a linear index, convert to grid coordinates
257
261
  const buttonX = safeLocation % boxWidth;
@@ -259,6 +263,11 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
259
263
  // Calculate absolute position on page
260
264
  const absoluteX = boxX + buttonX;
261
265
  const absoluteY = boxY + buttonY;
266
+ // Set button's x and y coordinates
267
+ button.x = absoluteX;
268
+ button.y = absoluteY;
269
+ // Add button to page
270
+ page.addButton(button);
262
271
  // Place button in grid (handle span)
263
272
  for (let r = absoluteY; r < absoluteY + safeSpanY && r < 10; r++) {
264
273
  for (let c = absoluteX; c < absoluteX + safeSpanX && c < 10; c++) {
@@ -412,21 +421,49 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
412
421
  catch (e) {
413
422
  // console.log('No navigation actions found:', e);
414
423
  }
415
- // Try to load root ID from metadata, fallback to first page
424
+ // Try to load root ID from multiple sources in order of priority
416
425
  try {
417
- const metadataQuery = "SELECT value FROM tree_metadata WHERE key = 'rootId'";
418
- const rootIdRow = db.prepare(metadataQuery).get();
419
- if (rootIdRow && tree.getPage(rootIdRow.value)) {
420
- tree.rootId = rootIdRow.value;
426
+ // First, try to get HOME page from special_pages table (TouchChat specific)
427
+ const specialPagesQuery = "SELECT page_id FROM special_pages WHERE name = 'HOME'";
428
+ const homePageRow = db.prepare(specialPagesQuery).get();
429
+ if (homePageRow) {
430
+ // The page_id is the page's id (not resource_id), need to get the RID
431
+ const homePageIdQuery = `
432
+ SELECT p.id, r.rid
433
+ FROM pages p
434
+ JOIN resources r ON r.id = p.resource_id
435
+ WHERE p.id = ?
436
+ LIMIT 1
437
+ `;
438
+ const homePage = db.prepare(homePageIdQuery).get(homePageRow.page_id);
439
+ if (homePage) {
440
+ const homePageUUID = homePage.rid || String(homePage.id);
441
+ if (tree.getPage(homePageUUID)) {
442
+ tree.rootId = homePageUUID;
443
+ tree.metadata.defaultHomePageId = homePageUUID;
444
+ }
445
+ }
446
+ }
447
+ // If no HOME page found, try tree_metadata table (general fallback)
448
+ if (!tree.rootId) {
449
+ const metadataQuery = "SELECT value FROM tree_metadata WHERE key = 'rootId'";
450
+ const rootIdRow = db.prepare(metadataQuery).get();
451
+ if (rootIdRow && tree.getPage(rootIdRow.value)) {
452
+ tree.rootId = rootIdRow.value;
453
+ tree.metadata.defaultHomePageId = rootIdRow.value;
454
+ }
421
455
  }
422
- else if (rootPageId) {
456
+ // Final fallback: first page
457
+ if (!tree.rootId && rootPageId) {
423
458
  tree.rootId = rootPageId;
459
+ tree.metadata.defaultHomePageId = rootPageId;
424
460
  }
425
461
  }
426
462
  catch (e) {
427
- // No metadata table, use first page as root
463
+ // No metadata table or other error, use first page as root
428
464
  if (rootPageId) {
429
465
  tree.rootId = rootPageId;
466
+ tree.metadata.defaultHomePageId = rootPageId;
430
467
  }
431
468
  }
432
469
  // Set metadata for TouchChat files
@@ -519,7 +556,14 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
519
556
  );
520
557
 
521
558
  CREATE TABLE IF NOT EXISTS button_boxes (
522
- id INTEGER PRIMARY KEY
559
+ id INTEGER PRIMARY KEY,
560
+ resource_id INTEGER,
561
+ layout_x INTEGER DEFAULT 10,
562
+ layout_y INTEGER DEFAULT 6,
563
+ init_size_x INTEGER DEFAULT 10000,
564
+ init_size_y INTEGER DEFAULT 10000,
565
+ scan_pattern_id INTEGER DEFAULT 0,
566
+ FOREIGN KEY (resource_id) REFERENCES resources (id)
523
567
  );
524
568
 
525
569
  CREATE TABLE IF NOT EXISTS button_box_cells (
@@ -677,8 +721,15 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
677
721
  }
678
722
  // Create a button box for this page's buttons
679
723
  const buttonBoxId = buttonBoxIdCounter++;
680
- const insertButtonBox = db.prepare('INSERT INTO button_boxes (id) VALUES (?)');
681
- insertButtonBox.run(buttonBoxId);
724
+ // Create a resource for the button box
725
+ const buttonBoxResourceId = resourceIdCounter++;
726
+ const insertButtonBoxResource = db.prepare('INSERT INTO resources (id, name, type) VALUES (?, ?, ?)');
727
+ insertButtonBoxResource.run(buttonBoxResourceId, page.name || 'ButtonBox', 0);
728
+ // Insert button box with layout dimensions
729
+ const insertButtonBox = db.prepare('INSERT INTO button_boxes (id, resource_id, layout_x, layout_y, init_size_x, init_size_y) VALUES (?, ?, ?, ?, ?, ?)');
730
+ insertButtonBox.run(buttonBoxId, buttonBoxResourceId, gridWidth, gridHeight, 10000, // init_size_x in internal units
731
+ 10000 // init_size_y in internal units
732
+ );
682
733
  // Create button box instance with calculated dimensions
683
734
  const insertButtonBoxInstance = db.prepare('INSERT INTO button_box_instances (id, page_id, button_box_id, position_x, position_y, size_x, size_y) VALUES (?, ?, ?, ?, ?, ?, ?)');
684
735
  insertButtonBoxInstance.run(buttonBoxInstanceIdCounter++, numericPageId, buttonBoxId, 0, // Box starts at origin
@@ -30,7 +30,7 @@ export declare class MetricsCalculator {
30
30
  * Count scan items for visual scanning effort
31
31
  * When block scanning is enabled, count unique scan blocks instead of individual buttons
32
32
  */
33
- private countScanItems;
33
+ private countScanBlocks;
34
34
  /**
35
35
  * Analyze starting from a specific board
36
36
  */