@willwade/aac-processors 0.2.12 → 0.2.14

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 (37) hide show
  1. package/dist/browser/core/treeStructure.js +60 -0
  2. package/dist/browser/processors/applePanelsProcessor.js +7 -1
  3. package/dist/browser/processors/astericsGridProcessor.js +7 -1
  4. package/dist/browser/processors/dotProcessor.js +9 -2
  5. package/dist/browser/processors/gridset/saveMutations.js +212 -0
  6. package/dist/browser/processors/gridsetProcessor.js +37 -39
  7. package/dist/browser/processors/obfProcessor.js +51 -1
  8. package/dist/browser/processors/opmlProcessor.js +7 -1
  9. package/dist/browser/processors/snapProcessor.js +7 -1
  10. package/dist/browser/processors/touchchatProcessor.js +8 -3
  11. package/dist/core/baseProcessor.d.ts +2 -0
  12. package/dist/core/treeStructure.d.ts +43 -2
  13. package/dist/core/treeStructure.js +60 -0
  14. package/dist/processors/applePanelsProcessor.d.ts +5 -0
  15. package/dist/processors/applePanelsProcessor.js +7 -1
  16. package/dist/processors/astericsGridProcessor.d.ts +5 -0
  17. package/dist/processors/astericsGridProcessor.js +7 -1
  18. package/dist/processors/dotProcessor.d.ts +5 -0
  19. package/dist/processors/dotProcessor.js +9 -2
  20. package/dist/processors/excelProcessor.d.ts +5 -0
  21. package/dist/processors/excelProcessor.js +8 -0
  22. package/dist/processors/gridset/saveMutations.d.ts +39 -0
  23. package/dist/processors/gridset/saveMutations.js +216 -0
  24. package/dist/processors/gridsetProcessor.d.ts +5 -0
  25. package/dist/processors/gridsetProcessor.js +37 -39
  26. package/dist/processors/obfProcessor.d.ts +14 -0
  27. package/dist/processors/obfProcessor.js +51 -1
  28. package/dist/processors/obfsetProcessor.d.ts +5 -0
  29. package/dist/processors/obfsetProcessor.js +5 -0
  30. package/dist/processors/opmlProcessor.d.ts +5 -0
  31. package/dist/processors/opmlProcessor.js +7 -1
  32. package/dist/processors/snapProcessor.d.ts +5 -0
  33. package/dist/processors/snapProcessor.js +7 -1
  34. package/dist/processors/touchchatProcessor.d.ts +5 -0
  35. package/dist/processors/touchchatProcessor.js +8 -3
  36. package/dist/types/aac.d.ts +54 -0
  37. package/package.json +1 -1
@@ -4,6 +4,11 @@ import { ValidationResult } from '../validation/validationTypes';
4
4
  import { type ButtonForTranslation, type LLMLTranslationResult } from '../utilities/translation/translationProcessor';
5
5
  import { ProcessorInput } from '../utils/io';
6
6
  declare class ObfProcessor extends BaseProcessor {
7
+ readonly capabilities: {
8
+ wordList: "none";
9
+ preservesAssetsOnSave: boolean;
10
+ newCellCreation: "allowed";
11
+ };
7
12
  private zipFile?;
8
13
  private imageCache;
9
14
  constructor(options?: ProcessorOptions);
@@ -21,6 +26,15 @@ declare class ObfProcessor extends BaseProcessor {
21
26
  extractTexts(filePathOrBuffer: ProcessorInput): Promise<string[]>;
22
27
  loadIntoTree(filePathOrBuffer: ProcessorInput): Promise<AACTree>;
23
28
  private buildGridMetadata;
29
+ /**
30
+ * Apply mutations to a buttons array for OBF export
31
+ * Returns a modified copy of the buttons array with mutations applied
32
+ *
33
+ * Note: addButton mutations are NOT applied because the button is already
34
+ * in the buttons array (added by page.addButton()). We only apply
35
+ * removeButton and updateButton mutations.
36
+ */
37
+ private applyMutationsToButtons;
24
38
  private createObfBoardFromPage;
25
39
  processTexts(filePathOrBuffer: ProcessorInput, translations: Map<string, string>, outputPath: string): Promise<Uint8Array>;
26
40
  saveFromTree(tree: AACTree, outputPath: string, embedData?: boolean): Promise<void>;
@@ -44,6 +44,11 @@ function mapObfVisibility(hidden) {
44
44
  class ObfProcessor extends baseProcessor_1.BaseProcessor {
45
45
  constructor(options) {
46
46
  super(options);
47
+ this.capabilities = {
48
+ wordList: 'none',
49
+ preservesAssetsOnSave: true,
50
+ newCellCreation: 'allowed',
51
+ };
47
52
  this.imageCache = new Map(); // Cache for data URLs
48
53
  }
49
54
  /**
@@ -557,6 +562,46 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
557
562
  }
558
563
  return { rows: totalRows, columns: totalColumns, order, buttonPositions };
559
564
  }
565
+ /**
566
+ * Apply mutations to a buttons array for OBF export
567
+ * Returns a modified copy of the buttons array with mutations applied
568
+ *
569
+ * Note: addButton mutations are NOT applied because the button is already
570
+ * in the buttons array (added by page.addButton()). We only apply
571
+ * removeButton and updateButton mutations.
572
+ */
573
+ applyMutationsToButtons(buttons, mutations) {
574
+ let modifiedButtons = [...buttons];
575
+ for (const mutation of mutations) {
576
+ switch (mutation.type) {
577
+ case 'addButton':
578
+ // Skip - button is already in the array from page.addButton()
579
+ break;
580
+ case 'removeButton': {
581
+ modifiedButtons = modifiedButtons.filter((b) => b.id !== mutation.buttonId);
582
+ break;
583
+ }
584
+ case 'updateButton': {
585
+ modifiedButtons = modifiedButtons.map((b) => {
586
+ if (b.id === mutation.buttonId) {
587
+ // Create a new AACButton instance with the patched properties
588
+ const patched = Object.create(Object.getPrototypeOf(b));
589
+ Object.assign(patched, b, mutation.patch);
590
+ return patched;
591
+ }
592
+ return b;
593
+ });
594
+ break;
595
+ }
596
+ case 'addWordListItem':
597
+ case 'removeWordListItem':
598
+ case 'clearWordList':
599
+ // OBF doesn't have WordList - skip
600
+ break;
601
+ }
602
+ }
603
+ return modifiedButtons;
604
+ }
560
605
  createObfBoardFromPage(page, fallbackName, metadata, embedData = false) {
561
606
  const { rows, columns, order, buttonPositions } = this.buildGridMetadata(page);
562
607
  const boardName = metadata?.name && page.id === metadata?.defaultHomePageId
@@ -569,6 +614,10 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
569
614
  return image;
570
615
  });
571
616
  }
617
+ // Apply mutations if present
618
+ const buttons = page.pendingMutations.length > 0
619
+ ? this.applyMutationsToButtons(page.buttons, page.pendingMutations)
620
+ : page.buttons;
572
621
  return {
573
622
  format: OBF_FORMAT_VERSION,
574
623
  id: page.id,
@@ -583,7 +632,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
583
632
  columns,
584
633
  order,
585
634
  },
586
- buttons: page.buttons.map((button) => {
635
+ buttons: buttons.map((button) => {
587
636
  const extraButtonInfo = button;
588
637
  const imageId = button.parameters?.image_id ||
589
638
  button.parameters?.imageId ||
@@ -728,6 +777,7 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
728
777
  for (const page of Object.values(tree.pages)) {
729
778
  const obfFilename = this.getPageFilename(page.id, tree.metadata);
730
779
  modifiedObfFiles.add(obfFilename);
780
+ // createObfBoardFromPage will automatically apply mutations if present
731
781
  const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
732
782
  const obfContent = JSON.stringify(obfBoard, null, 2);
733
783
  newObfFiles.set(obfFilename, obfContent);
@@ -6,6 +6,11 @@ import { AACTree } from '../core/treeStructure';
6
6
  import { BaseProcessor, ProcessorOptions } from '../core/baseProcessor';
7
7
  import { ProcessorInput } from '../utils/io';
8
8
  export declare class ObfsetProcessor extends BaseProcessor {
9
+ readonly capabilities: {
10
+ wordList: "none";
11
+ preservesAssetsOnSave: boolean;
12
+ newCellCreation: "allowed";
13
+ };
9
14
  constructor(options?: ProcessorOptions);
10
15
  /**
11
16
  * Extract all text content
@@ -11,6 +11,11 @@ const baseProcessor_1 = require("../core/baseProcessor");
11
11
  class ObfsetProcessor extends baseProcessor_1.BaseProcessor {
12
12
  constructor(options = {}) {
13
13
  super(options);
14
+ this.capabilities = {
15
+ wordList: 'none',
16
+ preservesAssetsOnSave: false,
17
+ newCellCreation: 'allowed',
18
+ };
14
19
  }
15
20
  /**
16
21
  * Extract all text content
@@ -2,6 +2,11 @@ import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString
2
2
  import { AACTree } from '../core/treeStructure';
3
3
  import { ProcessorInput } from '../utils/io';
4
4
  declare class OpmlProcessor extends BaseProcessor {
5
+ readonly capabilities: {
6
+ wordList: "none";
7
+ preservesAssetsOnSave: boolean;
8
+ newCellCreation: "allowed";
9
+ };
5
10
  constructor(options?: ProcessorOptions);
6
11
  private processOutline;
7
12
  extractTexts(filePathOrBuffer: ProcessorInput): Promise<string[]>;
@@ -9,6 +9,11 @@ const io_1 = require("../utils/io");
9
9
  class OpmlProcessor extends baseProcessor_1.BaseProcessor {
10
10
  constructor(options) {
11
11
  super(options);
12
+ this.capabilities = {
13
+ wordList: 'none',
14
+ preservesAssetsOnSave: false,
15
+ newCellCreation: 'allowed',
16
+ };
12
17
  }
13
18
  processOutline(outline, parentId = null) {
14
19
  if (!outline || typeof outline !== 'object') {
@@ -40,7 +45,8 @@ class OpmlProcessor extends baseProcessor_1.BaseProcessor {
40
45
  message: '',
41
46
  targetPageId: childText.replace(/[^a-zA-Z0-9]/g, '_'),
42
47
  });
43
- page.addButton(button);
48
+ // Load path: do not record as a user mutation
49
+ page._loadButton(button);
44
50
  const { page: childPage, childPages: grandChildren } = this.processOutline(child, page.id);
45
51
  if (childPage && childPage.id)
46
52
  childPages.push(childPage, ...grandChildren);
@@ -3,6 +3,11 @@ import { AACTree } from '../core/treeStructure';
3
3
  import { ValidationResult } from '../validation/validationTypes';
4
4
  import { ProcessorInput } from '../utils/io';
5
5
  declare class SnapProcessor extends BaseProcessor {
6
+ readonly capabilities: {
7
+ wordList: "none";
8
+ preservesAssetsOnSave: boolean;
9
+ newCellCreation: "allowed";
10
+ };
6
11
  private symbolResolver;
7
12
  private loadAudio;
8
13
  private pageLayoutPreference;
@@ -40,6 +40,11 @@ function mapSnapVisibility(visible) {
40
40
  class SnapProcessor extends baseProcessor_1.BaseProcessor {
41
41
  constructor(symbolResolver = null, options) {
42
42
  super(options);
43
+ this.capabilities = {
44
+ wordList: 'none',
45
+ preservesAssetsOnSave: false,
46
+ newCellCreation: 'allowed',
47
+ };
43
48
  this.symbolResolver = null;
44
49
  this.loadAudio = false;
45
50
  this.pageLayoutPreference = 'scanning'; // Default to scanning for metrics
@@ -577,7 +582,8 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
577
582
  // Add to the intended parent page
578
583
  const parentPage = tree.getPage(parentUniqueId);
579
584
  if (parentPage) {
580
- parentPage.addButton(button);
585
+ // Load path: do not record as a user mutation
586
+ parentPage._loadButton(button);
581
587
  // Add button to grid layout if position data is available
582
588
  const gridPositionStr = String(btnRow.GridPosition || '');
583
589
  if (gridPositionStr && gridPositionStr.includes(',')) {
@@ -4,6 +4,11 @@ import { ValidationResult } from '../validation/validationTypes';
4
4
  import { ProcessorInput } from '../utils/io';
5
5
  import { type ButtonForTranslation, type LLMLTranslationResult } from '../utilities/translation/translationProcessor';
6
6
  declare class TouchChatProcessor extends BaseProcessor {
7
+ readonly capabilities: {
8
+ wordList: "none";
9
+ preservesAssetsOnSave: boolean;
10
+ newCellCreation: "allowed";
11
+ };
7
12
  private tree;
8
13
  private sourceFile;
9
14
  constructor(options?: ProcessorOptions);
@@ -34,6 +34,11 @@ function mapTouchChatVisibility(visible) {
34
34
  class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
35
35
  constructor(options) {
36
36
  super(options);
37
+ this.capabilities = {
38
+ wordList: 'none',
39
+ preservesAssetsOnSave: false,
40
+ newCellCreation: 'allowed',
41
+ };
37
42
  this.tree = null;
38
43
  this.sourceFile = null;
39
44
  }
@@ -264,8 +269,8 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
264
269
  // Set button's x and y coordinates
265
270
  button.x = absoluteX;
266
271
  button.y = absoluteY;
267
- // Add button to page
268
- page.addButton(button);
272
+ // Add button to page (load path: do not record as a user mutation)
273
+ page._loadButton(button);
269
274
  // Place button in grid (handle span)
270
275
  for (let r = absoluteY; r < absoluteY + safeSpanY && r < 10; r++) {
271
276
  for (let c = absoluteX; c < absoluteX + safeSpanX && c < 10; c++) {
@@ -366,7 +371,7 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
366
371
  // Find the page that references this resource
367
372
  const page = Object.values(tree.pages).find((p) => p.id === (numericToRid.get(btnRow.id) || String(btnRow.id)));
368
373
  if (page)
369
- page.addButton(button);
374
+ page._loadButton(button); // load path: do not record as a user mutation
370
375
  });
371
376
  }
372
377
  catch (_e) {
@@ -206,3 +206,57 @@ export interface AACProcessor {
206
206
  extractTexts(filePath: string | Buffer): Promise<string[]>;
207
207
  loadIntoTree(filePath: string | Buffer): Promise<AACTree>;
208
208
  }
209
+ /**
210
+ * Word List Item for dynamic content cells (e.g., Grid 3 WordLists)
211
+ */
212
+ export interface AACWordListItem {
213
+ text: string;
214
+ image?: string;
215
+ partOfSpeech?: string;
216
+ }
217
+ /**
218
+ * Mutation types for page modifications
219
+ */
220
+ export type AACPageMutation = {
221
+ type: 'addButton';
222
+ button: AACButton;
223
+ } | {
224
+ type: 'removeButton';
225
+ buttonId: string;
226
+ } | {
227
+ type: 'updateButton';
228
+ buttonId: string;
229
+ patch: Partial<AACButton>;
230
+ } | {
231
+ type: 'addWordListItem';
232
+ item: AACWordListItem;
233
+ } | {
234
+ type: 'removeWordListItem';
235
+ match: string | ((item: AACWordListItem) => boolean);
236
+ } | {
237
+ type: 'clearWordList';
238
+ };
239
+ /**
240
+ * Processor capabilities declaration
241
+ */
242
+ export interface ProcessorCapabilities {
243
+ /**
244
+ * WordList support level
245
+ * - 'native': addWordListItem writes a real WordList structure on disk
246
+ * - 'fallback': addWordListItem becomes addButton (still useful, just not dynamic)
247
+ * - 'none': addWordListItem throws CapabilityError
248
+ */
249
+ wordList: 'native' | 'fallback' | 'none';
250
+ /**
251
+ * Whether the processor has a real saveModifiedTree that keeps original images/settings
252
+ * If false, saveModifiedTree falls back to saveFromTree with a warning
253
+ */
254
+ preservesAssetsOnSave: boolean;
255
+ /**
256
+ * Rules for creating new cells
257
+ * - 'allowed': addButton at any (x,y) creates a cell on save
258
+ * - 'restricted': addButton routes to a WordList if (x,y) is a WordList cell, else dropped with warning
259
+ * - 'forbidden': addButton requires explicit (x,y) of an existing cell; otherwise CapabilityError
260
+ */
261
+ newCellCreation: 'allowed' | 'restricted' | 'forbidden';
262
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@willwade/aac-processors",
3
- "version": "0.2.12",
3
+ "version": "0.2.14",
4
4
  "description": "A comprehensive TypeScript library for processing AAC (Augmentative and Alternative Communication) file formats with translation support",
5
5
  "main": "dist/index.js",
6
6
  "browser": "dist/browser/index.browser.js",