@willwade/aac-processors 0.0.4 → 0.0.6

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.
@@ -185,7 +185,6 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
185
185
  }
186
186
  // Create semantic action for Snap button
187
187
  let semanticAction;
188
- let legacyAction = null;
189
188
  if (targetPageUniqueId) {
190
189
  semanticAction = {
191
190
  category: treeStructure_1.AACSemanticCategory.NAVIGATION,
@@ -202,10 +201,6 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
202
201
  targetPageId: targetPageUniqueId,
203
202
  },
204
203
  };
205
- legacyAction = {
206
- type: 'NAVIGATE',
207
- targetPageId: targetPageUniqueId,
208
- };
209
204
  }
210
205
  else {
211
206
  semanticAction = {
@@ -283,15 +278,16 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
283
278
  return tree;
284
279
  }
285
280
  catch (error) {
281
+ const fileIdentifier = typeof filePathOrBuffer === 'string' ? filePathOrBuffer : '[buffer input]';
286
282
  // Provide more specific error messages
287
283
  if (error.code === 'SQLITE_NOTADB') {
288
284
  throw new Error(`Invalid SQLite database file: ${typeof filePathOrBuffer === 'string' ? filePathOrBuffer : 'buffer'}`);
289
285
  }
290
286
  else if (error.code === 'ENOENT') {
291
- throw new Error(`File not found: ${filePathOrBuffer}`);
287
+ throw new Error(`File not found: ${fileIdentifier}`);
292
288
  }
293
289
  else if (error.code === 'EACCES') {
294
- throw new Error(`Permission denied accessing file: ${filePathOrBuffer}`);
290
+ throw new Error(`Permission denied accessing file: ${fileIdentifier}`);
295
291
  }
296
292
  else {
297
293
  throw new Error(`Failed to load Snap file: ${error.message}`);
@@ -320,15 +316,24 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
320
316
  Object.values(tree.pages).forEach((page) => {
321
317
  // Translate page names
322
318
  if (page.name && translations.has(page.name)) {
323
- page.name = translations.get(page.name);
319
+ const translatedName = translations.get(page.name);
320
+ if (translatedName !== undefined) {
321
+ page.name = translatedName;
322
+ }
324
323
  }
325
324
  // Translate button labels and messages
326
325
  page.buttons.forEach((button) => {
327
326
  if (button.label && translations.has(button.label)) {
328
- button.label = translations.get(button.label);
327
+ const translatedLabel = translations.get(button.label);
328
+ if (translatedLabel !== undefined) {
329
+ button.label = translatedLabel;
330
+ }
329
331
  }
330
332
  if (button.message && translations.has(button.message)) {
331
- button.message = translations.get(button.message);
333
+ const translatedMessage = translations.get(button.message);
334
+ if (translatedMessage !== undefined) {
335
+ button.message = translatedMessage;
336
+ }
332
337
  }
333
338
  });
334
339
  });
@@ -419,6 +424,9 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
419
424
  // Second pass: create buttons with proper page references
420
425
  Object.values(tree.pages).forEach((page) => {
421
426
  const numericPageId = pageIdMap.get(page.id);
427
+ if (numericPageId === undefined) {
428
+ return;
429
+ }
422
430
  page.buttons.forEach((button, index) => {
423
431
  // Find button position in grid layout
424
432
  let gridPosition = `${index % 4},${Math.floor(index / 4)}`; // Default fallback
@@ -12,6 +12,9 @@ const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
12
12
  const path_1 = __importDefault(require("path"));
13
13
  const fs_1 = __importDefault(require("fs"));
14
14
  const os_1 = __importDefault(require("os"));
15
+ const toNumberOrUndefined = (value) => typeof value === 'number' ? value : undefined;
16
+ const toStringOrUndefined = (value) => typeof value === 'string' && value.length > 0 ? value : undefined;
17
+ const toBooleanOrUndefined = (value) => typeof value === 'number' ? value !== 0 : undefined;
15
18
  function intToHex(colorInt) {
16
19
  if (colorInt === null || typeof colorInt === 'undefined') {
17
20
  return undefined;
@@ -84,7 +87,9 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
84
87
  const buttonStyles = new Map();
85
88
  const pageStyles = new Map();
86
89
  try {
87
- const buttonStyleRows = db.prepare('SELECT * FROM button_styles').all();
90
+ const buttonStyleRows = db
91
+ .prepare('SELECT * FROM button_styles')
92
+ .all();
88
93
  buttonStyleRows.forEach((style) => {
89
94
  buttonStyles.set(style.id, style);
90
95
  });
@@ -162,15 +167,15 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
162
167
  style: {
163
168
  backgroundColor: intToHex(style?.body_color),
164
169
  borderColor: intToHex(style?.border_color),
165
- borderWidth: style?.border_width,
170
+ borderWidth: toNumberOrUndefined(style?.border_width),
166
171
  fontColor: intToHex(style?.font_color),
167
- fontSize: style?.font_height,
168
- fontFamily: style?.font_name,
169
- fontWeight: style?.font_bold ? 'bold' : 'normal',
170
- fontStyle: style?.font_italic ? 'italic' : 'normal',
171
- textUnderline: style?.font_underline,
172
- transparent: style?.transparent,
173
- labelOnTop: style?.label_on_top,
172
+ fontSize: toNumberOrUndefined(style?.font_height),
173
+ fontFamily: toStringOrUndefined(style?.font_name),
174
+ fontWeight: style?.font_bold ? 'bold' : undefined,
175
+ fontStyle: style?.font_italic ? 'italic' : undefined,
176
+ textUnderline: toBooleanOrUndefined(style?.font_underline),
177
+ transparent: toBooleanOrUndefined(style?.transparent),
178
+ labelOnTop: toBooleanOrUndefined(style?.label_on_top),
174
179
  },
175
180
  });
176
181
  buttonBoxes.get(cell.box_id)?.push({
@@ -276,15 +281,15 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
276
281
  style: {
277
282
  backgroundColor: intToHex(style?.body_color),
278
283
  borderColor: intToHex(style?.border_color),
279
- borderWidth: style?.border_width,
284
+ borderWidth: toNumberOrUndefined(style?.border_width),
280
285
  fontColor: intToHex(style?.font_color),
281
- fontSize: style?.font_height,
282
- fontFamily: style?.font_name,
283
- fontWeight: style?.font_bold ? 'bold' : 'normal',
284
- fontStyle: style?.font_italic ? 'italic' : 'normal',
285
- textUnderline: style?.font_underline,
286
- transparent: style?.transparent,
287
- labelOnTop: style?.label_on_top,
286
+ fontSize: toNumberOrUndefined(style?.font_height),
287
+ fontFamily: toStringOrUndefined(style?.font_name),
288
+ fontWeight: style?.font_bold ? 'bold' : undefined,
289
+ fontStyle: style?.font_italic ? 'italic' : undefined,
290
+ textUnderline: toBooleanOrUndefined(style?.font_underline),
291
+ transparent: toBooleanOrUndefined(style?.transparent),
292
+ labelOnTop: toBooleanOrUndefined(style?.label_on_top),
288
293
  },
289
294
  });
290
295
  // Find the page that references this resource
@@ -380,15 +385,24 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
380
385
  Object.values(tree.pages).forEach((page) => {
381
386
  // Translate page names
382
387
  if (page.name && translations.has(page.name)) {
383
- page.name = translations.get(page.name);
388
+ const translatedName = translations.get(page.name);
389
+ if (translatedName !== undefined) {
390
+ page.name = translatedName;
391
+ }
384
392
  }
385
393
  // Translate button labels and messages
386
394
  page.buttons.forEach((button) => {
387
395
  if (button.label && translations.has(button.label)) {
388
- button.label = translations.get(button.label);
396
+ const translatedLabel = translations.get(button.label);
397
+ if (translatedLabel !== undefined) {
398
+ button.label = translatedLabel;
399
+ }
389
400
  }
390
401
  if (button.message && translations.has(button.message)) {
391
- button.message = translations.get(button.message);
402
+ const translatedMessage = translations.get(button.message);
403
+ if (translatedMessage !== undefined) {
404
+ button.message = translatedMessage;
405
+ }
392
406
  }
393
407
  });
394
408
  });
@@ -560,7 +574,10 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
560
574
  insertPageStyle.run(pageStyleId, hexToInt(page.style.backgroundColor), page.style.backgroundColor ? 1 : 0);
561
575
  }
562
576
  else {
563
- pageStyleId = pageStyleMap.get(styleKey);
577
+ const existingPageStyleId = pageStyleMap.get(styleKey);
578
+ if (typeof existingPageStyleId === 'number') {
579
+ pageStyleId = existingPageStyleId;
580
+ }
564
581
  }
565
582
  }
566
583
  // Insert resource for page name
@@ -577,6 +594,9 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
577
594
  // Second pass: create buttons and their relationships
578
595
  Object.values(tree.pages).forEach((page) => {
579
596
  const numericPageId = pageIdMap.get(page.id);
597
+ if (numericPageId === undefined) {
598
+ return;
599
+ }
580
600
  if (page.buttons.length > 0) {
581
601
  // Calculate grid dimensions from page.grid or use fallback
582
602
  let gridWidth = 4; // Default fallback
@@ -631,7 +651,10 @@ class TouchChatProcessor extends baseProcessor_1.BaseProcessor {
631
651
  insertButtonStyle.run(buttonStyleId, button.style.labelOnTop ? 1 : 0, button.style.transparent ? 1 : 0, hexToInt(button.style.fontColor), hexToInt(button.style.backgroundColor), hexToInt(button.style.borderColor), button.style.borderWidth, button.style.fontFamily, button.style.fontWeight === 'bold' ? 1 : 0, button.style.textUnderline ? 1 : 0, button.style.fontStyle === 'italic' ? 1 : 0, button.style.fontSize);
632
652
  }
633
653
  else {
634
- buttonStyleId = buttonStyleMap.get(styleKey);
654
+ const existingButtonStyleId = buttonStyleMap.get(styleKey);
655
+ if (typeof existingButtonStyleId === 'number') {
656
+ buttonStyleId = existingButtonStyleId;
657
+ }
635
658
  }
636
659
  }
637
660
  if (!insertedButtonIds.has(numericButtonId)) {
@@ -33,6 +33,10 @@ export interface AACPage {
33
33
  buttons: AACButton[];
34
34
  parentId: string | null;
35
35
  style?: AACStyle;
36
+ locale?: string;
37
+ descriptionHtml?: string;
38
+ images?: any[];
39
+ sounds?: any[];
36
40
  }
37
41
  export interface AACTree {
38
42
  pages: {
@@ -0,0 +1,287 @@
1
+ # Grid3 Styling Guide
2
+
3
+ This guide explains how to work with Grid3 styles using the AACProcessors library.
4
+
5
+ ## Overview
6
+
7
+ Grid3 uses a sophisticated styling system with:
8
+ - **Default styles** for common UI elements
9
+ - **Category styles** for organizing content by semantic meaning
10
+ - **Inline style overrides** for cell-specific customization
11
+ - **Style inheritance** through `BasedOnStyle` references
12
+
13
+ ## Default Styles
14
+
15
+ The library provides built-in default styles for common use cases:
16
+
17
+ ### Available Default Styles
18
+
19
+ | Style Name | Purpose | Background | Text Color |
20
+ |-----------|---------|-----------|-----------|
21
+ | `Default` | General purpose cells | Light blue (#E2EDF8) | Black |
22
+ | `Workspace` | Message/chat area | White (#FFFFFF) | Black |
23
+ | `Auto content` | Wordlists, predictions | Light blue (#E8F4F8) | Black |
24
+ | `Vocab cell` | Vocabulary cells | Light blue (#E8F4F8) | Black |
25
+ | `Keyboard key` | On-screen keyboard | Light gray (#F0F0F0) | Black |
26
+
27
+ ### Category Styles
28
+
29
+ Category styles are used for organizing content by semantic meaning:
30
+
31
+ | Style Name | Color | Use Case |
32
+ |-----------|-------|----------|
33
+ | `Actions category style` | Blue (#4472C4) | Action verbs, commands |
34
+ | `People category style` | Orange (#ED7D31) | Names, people |
35
+ | `Places category style` | Gray (#A5A5A5) | Locations, places |
36
+ | `Descriptive category style` | Green (#70AD47) | Adjectives, descriptors |
37
+ | `Social category style` | Gold (#FFC000) | Social phrases, greetings |
38
+ | `Questions category style` | Light blue (#5B9BD5) | Questions, interrogatives |
39
+ | `Little words category style` | Brown (#C55A11) | Function words, particles |
40
+
41
+ ## Using Styles in Code
42
+
43
+ ### Import Style Helpers
44
+
45
+ ```typescript
46
+ import {
47
+ DEFAULT_GRID3_STYLES,
48
+ CATEGORY_STYLES,
49
+ createDefaultStylesXml,
50
+ createCategoryStyle,
51
+ ensureAlphaChannel,
52
+ } from 'aac-processors';
53
+ ```
54
+
55
+ ### Access Predefined Styles
56
+
57
+ ```typescript
58
+ // Get a specific default style
59
+ const defaultStyle = DEFAULT_GRID3_STYLES['Default'];
60
+ console.log(defaultStyle.BackColour); // #E2EDF8FF
61
+
62
+ // Get a category style
63
+ const actionStyle = CATEGORY_STYLES['Actions category style'];
64
+ console.log(actionStyle.BackColour); // #4472C4FF
65
+ ```
66
+
67
+ ### Create Custom Category Styles
68
+
69
+ ```typescript
70
+ // Create a custom category style with automatic border darkening
71
+ const customStyle = createCategoryStyle(
72
+ 'My Category',
73
+ '#FF6B6B', // Background color
74
+ '#FFFFFF' // Font color (optional, defaults to white)
75
+ );
76
+
77
+ // Result:
78
+ // {
79
+ // BackColour: '#FF6B6BFF',
80
+ // TileColour: '#FF6B6BFF',
81
+ // BorderColour: '#CB5555FF', // Automatically darkened
82
+ // FontColour: '#FFFFFFFF',
83
+ // FontName: 'Arial',
84
+ // FontSize: '16'
85
+ // }
86
+ ```
87
+
88
+ ### Generate Default Styles XML
89
+
90
+ ```typescript
91
+ // Generate Settings0/styles.xml with all default and category styles
92
+ const stylesXml = createDefaultStylesXml(true);
93
+
94
+ // Or just default styles without categories
95
+ const basicStylesXml = createDefaultStylesXml(false);
96
+ ```
97
+
98
+ ### Ensure Color Has Alpha Channel
99
+
100
+ Grid3 requires colors in 8-digit ARGB format (#AARRGGBBFF):
101
+
102
+ ```typescript
103
+ import { ensureAlphaChannel } from 'aac-processors';
104
+
105
+ ensureAlphaChannel('#FF0000'); // Returns: #FF0000FF
106
+ ensureAlphaChannel('#F00'); // Returns: #FF0000FF
107
+ ensureAlphaChannel('#FF0000FF'); // Returns: #FF0000FF (unchanged)
108
+ ensureAlphaChannel(undefined); // Returns: #FFFFFFFF (white)
109
+ ```
110
+
111
+ ## Applying Styles to Cells
112
+
113
+ ### Using BasedOnStyle Reference
114
+
115
+ In Grid3 XML, cells reference styles by name:
116
+
117
+ ```xml
118
+ <Cell X="0" Y="0">
119
+ <Content>
120
+ <CaptionAndImage>
121
+ <Caption>Hello</Caption>
122
+ </CaptionAndImage>
123
+ <Style>
124
+ <BasedOnStyle>Actions category style</BasedOnStyle>
125
+ </Style>
126
+ </Content>
127
+ </Cell>
128
+ ```
129
+
130
+ ### Inline Style Overrides
131
+
132
+ You can override specific properties while keeping the base style:
133
+
134
+ ```xml
135
+ <Cell X="1" Y="0">
136
+ <Content>
137
+ <CaptionAndImage>
138
+ <Caption>Custom</Caption>
139
+ </CaptionAndImage>
140
+ <Style>
141
+ <BasedOnStyle>Default</BasedOnStyle>
142
+ <BackColour>#FF0000FF</BackColour> <!-- Override background -->
143
+ <FontSize>20</FontSize> <!-- Override font size -->
144
+ </Style>
145
+ </Content>
146
+ </Cell>
147
+ ```
148
+
149
+ ## Color Format
150
+
151
+ Grid3 uses 8-digit ARGB hexadecimal format: `#AARRGGBBFF`
152
+
153
+ - **AA**: Alpha channel (FF = fully opaque, 00 = fully transparent)
154
+ - **RR**: Red component (00-FF)
155
+ - **GG**: Green component (00-FF)
156
+ - **BB**: Blue component (00-FF)
157
+ - **FF**: Always FF for Grid3 (fully opaque)
158
+
159
+ ### Examples
160
+
161
+ | Color | Hex Code | Description |
162
+ |-------|----------|-------------|
163
+ | White | #FFFFFFFF | Fully opaque white |
164
+ | Black | #000000FF | Fully opaque black |
165
+ | Red | #FF0000FF | Fully opaque red |
166
+ | Blue | #0000FFFF | Fully opaque blue |
167
+ | Green | #00FF00FF | Fully opaque green |
168
+
169
+ ## Style Inheritance
170
+
171
+ Grid3 uses a cascading style system:
172
+
173
+ 1. **Theme** provides base properties (Modern, Kids/Bubble, Flat/Blocky, Explorer)
174
+ 2. **Built-in style** defines category defaults
175
+ 3. **Cell-specific overrides** apply on top
176
+
177
+ ```xml
178
+ <!-- Example: Override just the background color -->
179
+ <Style>
180
+ <BasedOnStyle>Actions category style</BasedOnStyle>
181
+ <BackColour>#FF0000FF</BackColour> <!-- Override just this property -->
182
+ </Style>
183
+ ```
184
+
185
+ ## Creating Gridsets with Styles
186
+
187
+ ### Using GridsetProcessor
188
+
189
+ ```typescript
190
+ import { GridsetProcessor, AACTree, AACPage, AACButton } from 'aac-processors';
191
+
192
+ const processor = new GridsetProcessor();
193
+ const tree = new AACTree();
194
+
195
+ // Create a page with styling
196
+ const page = new AACPage({
197
+ id: 'main-page',
198
+ name: 'Main Board',
199
+ grid: [],
200
+ buttons: [],
201
+ parentId: null,
202
+ style: {
203
+ backgroundColor: '#f0f8ff',
204
+ fontFamily: 'Arial',
205
+ fontSize: 16,
206
+ },
207
+ });
208
+
209
+ // Create styled buttons
210
+ const button = new AACButton({
211
+ id: 'btn-1',
212
+ label: 'Hello',
213
+ message: 'Hello, how are you?',
214
+ style: {
215
+ backgroundColor: '#4472C4', // Blue (Actions category)
216
+ fontColor: '#FFFFFF',
217
+ fontSize: 18,
218
+ fontFamily: 'Arial',
219
+ },
220
+ });
221
+
222
+ page.addButton(button);
223
+ tree.addPage(page);
224
+
225
+ // Save with styles
226
+ processor.saveFromTree(tree, 'output.gridset');
227
+ ```
228
+
229
+ ### Using Grid-Generator
230
+
231
+ ```typescript
232
+ import { generateGridset } from '@willwade/grid-generator';
233
+
234
+ const template = {
235
+ aacsystem: 'Grid3',
236
+ homeGrid: {
237
+ enabled: true,
238
+ name: 'Home',
239
+ title: 'Categories',
240
+ },
241
+ wordlists: [
242
+ {
243
+ name: 'Greetings',
244
+ items: ['Hello', 'Hi', 'Hey'],
245
+ partOfSpeech: 'Interjection',
246
+ },
247
+ ],
248
+ };
249
+
250
+ const gridset = generateGridset(template);
251
+ ```
252
+
253
+ ## Best Practices
254
+
255
+ 1. **Use category styles** for semantic organization - helps with accessibility and consistency
256
+ 2. **Maintain contrast** - ensure text color has sufficient contrast with background
257
+ 3. **Use consistent fonts** - stick to standard fonts like Arial, Verdana, or Roboto
258
+ 4. **Test in Grid3** - always verify styling in the actual Grid3 application
259
+ 5. **Document custom styles** - if creating custom category styles, document their purpose
260
+ 6. **Use inline overrides sparingly** - prefer creating new styles for significant variations
261
+
262
+ ## Troubleshooting
263
+
264
+ ### Styles Not Appearing
265
+
266
+ - Verify `Settings0/styles.xml` exists in the gridset
267
+ - Check that style names in cells match exactly (case-sensitive)
268
+ - Ensure colors are in 8-digit ARGB format
269
+
270
+ ### Colors Look Wrong
271
+
272
+ - Verify alpha channel is FF (fully opaque)
273
+ - Check RGB values are correct
274
+ - Test in Grid3 to see actual rendering
275
+
276
+ ### Performance Issues
277
+
278
+ - Avoid creating too many unique styles (consolidate similar styles)
279
+ - Use style references instead of inline overrides when possible
280
+ - Keep font sizes reasonable (12-24 points typical)
281
+
282
+ ## See Also
283
+
284
+ - [Grid3 XML Format Documentation](./Grid3-XML-Format.md)
285
+ - [Wordlist Helpers Guide](./Grid3-Wordlist-Helpers.md)
286
+ - [AACProcessors API Reference](./API-Reference.md)
287
+
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@willwade/aac-processors",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
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
  "types": "dist/index.d.ts",