@willwade/aac-processors 0.0.3

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 (89) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +787 -0
  3. package/dist/cli/index.d.ts +2 -0
  4. package/dist/cli/index.js +189 -0
  5. package/dist/cli/prettyPrint.d.ts +2 -0
  6. package/dist/cli/prettyPrint.js +28 -0
  7. package/dist/core/analyze.d.ts +6 -0
  8. package/dist/core/analyze.js +49 -0
  9. package/dist/core/baseProcessor.d.ts +94 -0
  10. package/dist/core/baseProcessor.js +208 -0
  11. package/dist/core/fileProcessor.d.ts +7 -0
  12. package/dist/core/fileProcessor.js +51 -0
  13. package/dist/core/stringCasing.d.ts +37 -0
  14. package/dist/core/stringCasing.js +174 -0
  15. package/dist/core/treeStructure.d.ts +190 -0
  16. package/dist/core/treeStructure.js +223 -0
  17. package/dist/index.d.ts +23 -0
  18. package/dist/index.js +96 -0
  19. package/dist/optional/symbolTools.d.ts +28 -0
  20. package/dist/optional/symbolTools.js +126 -0
  21. package/dist/processors/applePanelsProcessor.d.ts +23 -0
  22. package/dist/processors/applePanelsProcessor.js +521 -0
  23. package/dist/processors/astericsGridProcessor.d.ts +49 -0
  24. package/dist/processors/astericsGridProcessor.js +1427 -0
  25. package/dist/processors/dotProcessor.d.ts +21 -0
  26. package/dist/processors/dotProcessor.js +191 -0
  27. package/dist/processors/excelProcessor.d.ts +145 -0
  28. package/dist/processors/excelProcessor.js +556 -0
  29. package/dist/processors/gridset/helpers.d.ts +4 -0
  30. package/dist/processors/gridset/helpers.js +48 -0
  31. package/dist/processors/gridset/resolver.d.ts +8 -0
  32. package/dist/processors/gridset/resolver.js +100 -0
  33. package/dist/processors/gridsetProcessor.d.ts +28 -0
  34. package/dist/processors/gridsetProcessor.js +1339 -0
  35. package/dist/processors/index.d.ts +14 -0
  36. package/dist/processors/index.js +42 -0
  37. package/dist/processors/obfProcessor.d.ts +21 -0
  38. package/dist/processors/obfProcessor.js +278 -0
  39. package/dist/processors/opmlProcessor.d.ts +21 -0
  40. package/dist/processors/opmlProcessor.js +235 -0
  41. package/dist/processors/snap/helpers.d.ts +4 -0
  42. package/dist/processors/snap/helpers.js +27 -0
  43. package/dist/processors/snapProcessor.d.ts +44 -0
  44. package/dist/processors/snapProcessor.js +586 -0
  45. package/dist/processors/touchchat/helpers.d.ts +4 -0
  46. package/dist/processors/touchchat/helpers.js +27 -0
  47. package/dist/processors/touchchatProcessor.d.ts +27 -0
  48. package/dist/processors/touchchatProcessor.js +768 -0
  49. package/dist/types/aac.d.ts +47 -0
  50. package/dist/types/aac.js +2 -0
  51. package/docs/.keep +1 -0
  52. package/docs/ApplePanels.md +309 -0
  53. package/docs/Grid3-XML-Format.md +1788 -0
  54. package/docs/TobiiDynavox-Snap-Details.md +394 -0
  55. package/docs/asterics-Grid-fileformat-details.md +443 -0
  56. package/docs/obf_.obz Open Board File Formats.md +432 -0
  57. package/docs/touchchat.md +520 -0
  58. package/examples/.coverage +0 -0
  59. package/examples/.keep +1 -0
  60. package/examples/README.md +31 -0
  61. package/examples/communikate.dot +2637 -0
  62. package/examples/demo.js +143 -0
  63. package/examples/example-images.gridset +0 -0
  64. package/examples/example.ce +0 -0
  65. package/examples/example.dot +14 -0
  66. package/examples/example.grd +1 -0
  67. package/examples/example.gridset +0 -0
  68. package/examples/example.obf +27 -0
  69. package/examples/example.obz +0 -0
  70. package/examples/example.opml +18 -0
  71. package/examples/example.spb +0 -0
  72. package/examples/example.sps +0 -0
  73. package/examples/example2.grd +1 -0
  74. package/examples/gemini_response.txt +845 -0
  75. package/examples/image-map.js +45 -0
  76. package/examples/package-lock.json +1326 -0
  77. package/examples/package.json +10 -0
  78. package/examples/styled-output/converted-snap-to-touchchat.ce +0 -0
  79. package/examples/styled-output/styled-example.ce +0 -0
  80. package/examples/styled-output/styled-example.gridset +0 -0
  81. package/examples/styled-output/styled-example.obf +37 -0
  82. package/examples/styled-output/styled-example.spb +0 -0
  83. package/examples/styling-example.ts +316 -0
  84. package/examples/translate.js +39 -0
  85. package/examples/translate_demo.js +254 -0
  86. package/examples/translation_cache.json +44894 -0
  87. package/examples/typescript-demo.ts +251 -0
  88. package/examples/unified-interface-demo.ts +183 -0
  89. package/package.json +106 -0
@@ -0,0 +1,521 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.ApplePanelsProcessor = void 0;
7
+ const baseProcessor_1 = require("../core/baseProcessor");
8
+ const treeStructure_1 = require("../core/treeStructure");
9
+ // Removed unused import: FileProcessor
10
+ const plist_1 = __importDefault(require("plist"));
11
+ const fs_1 = __importDefault(require("fs"));
12
+ const path_1 = __importDefault(require("path"));
13
+ class ApplePanelsProcessor extends baseProcessor_1.BaseProcessor {
14
+ constructor(options) {
15
+ super(options);
16
+ }
17
+ // Helper function to parse Apple Panels Rect format "{{x, y}, {width, height}}"
18
+ parseRect(rectString) {
19
+ if (!rectString)
20
+ return null;
21
+ // Parse format like "{{0, 0}, {100, 25}}"
22
+ const match = rectString.match(/\{\{(\d+),\s*(\d+)\},\s*\{(\d+),\s*(\d+)\}\}/);
23
+ if (!match)
24
+ return null;
25
+ return {
26
+ x: parseInt(match[1], 10),
27
+ y: parseInt(match[2], 10),
28
+ width: parseInt(match[3], 10),
29
+ height: parseInt(match[4], 10),
30
+ };
31
+ }
32
+ // Convert pixel coordinates to grid coordinates (assuming 25px grid cells)
33
+ pixelToGrid(pixelX, pixelY, cellSize = 25) {
34
+ return {
35
+ gridX: Math.floor(pixelX / cellSize),
36
+ gridY: Math.floor(pixelY / cellSize),
37
+ };
38
+ }
39
+ extractTexts(filePathOrBuffer) {
40
+ const tree = this.loadIntoTree(filePathOrBuffer);
41
+ const texts = [];
42
+ for (const pageId in tree.pages) {
43
+ const page = tree.pages[pageId];
44
+ if (page.name)
45
+ texts.push(page.name);
46
+ page.buttons.forEach((btn) => {
47
+ if (btn.label)
48
+ texts.push(btn.label);
49
+ if (btn.message && btn.message !== btn.label)
50
+ texts.push(btn.message);
51
+ });
52
+ }
53
+ return texts;
54
+ }
55
+ loadIntoTree(filePathOrBuffer) {
56
+ let content;
57
+ if (Buffer.isBuffer(filePathOrBuffer)) {
58
+ content = filePathOrBuffer.toString('utf8');
59
+ }
60
+ else if (typeof filePathOrBuffer === 'string') {
61
+ // Check if it's a .ascconfig folder or a direct .plist file
62
+ if (filePathOrBuffer.endsWith('.ascconfig')) {
63
+ // Read from proper Apple Panels structure: *.ascconfig/Contents/Resources/PanelDefinitions.plist
64
+ const panelDefsPath = `${filePathOrBuffer}/Contents/Resources/PanelDefinitions.plist`;
65
+ if (fs_1.default.existsSync(panelDefsPath)) {
66
+ content = fs_1.default.readFileSync(panelDefsPath, 'utf8');
67
+ }
68
+ else {
69
+ throw new Error(`Apple Panels file not found: ${panelDefsPath}`);
70
+ }
71
+ }
72
+ else {
73
+ // Fallback: treat as direct .plist file
74
+ content = fs_1.default.readFileSync(filePathOrBuffer, 'utf8');
75
+ }
76
+ }
77
+ else {
78
+ throw new Error('Invalid input: expected string path or Buffer');
79
+ }
80
+ const parsedData = plist_1.default.parse(content);
81
+ // Handle both old format (panels array) and new Apple Panels format (Panels dict)
82
+ let panelsData = [];
83
+ if (Array.isArray(parsedData.panels)) {
84
+ // Old format
85
+ panelsData = parsedData.panels;
86
+ }
87
+ else if (parsedData.Panels) {
88
+ // Apple Panels format: convert Panels dict to array
89
+ const panelsDict = parsedData.Panels;
90
+ panelsData = Object.keys(panelsDict).map((panelId) => {
91
+ const panel = panelsDict[panelId];
92
+ return {
93
+ id: (panel.ID || panelId).replace(/^USER\./, ''), // Strip USER. prefix to maintain original IDs
94
+ name: panel.Name || 'Panel',
95
+ buttons: (panel.PanelObjects || [])
96
+ .filter((obj) => obj.PanelObjectType === 'Button')
97
+ .map((btn) => {
98
+ const firstAction = Array.isArray(btn.Actions) && btn.Actions.length > 0 ? btn.Actions[0] : undefined;
99
+ const isCharSequence = firstAction &&
100
+ (firstAction.ActionType === 'ActionPressKeyCharSequence' ||
101
+ firstAction.ActionType === 'ActionSendKeys');
102
+ const charString = isCharSequence
103
+ ? (firstAction.ActionParam?.CharString ?? undefined)
104
+ : undefined;
105
+ const targetPanel = firstAction && firstAction.ActionType === 'ActionOpenPanel'
106
+ ? firstAction.ActionParam?.PanelID?.replace(/^USER\./, '')
107
+ : undefined;
108
+ return {
109
+ label: btn.DisplayText || 'Button',
110
+ message: charString || btn.DisplayText || 'Button',
111
+ DisplayColor: btn.DisplayColor,
112
+ DisplayImageWeight: btn.DisplayImageWeight,
113
+ FontSize: btn.FontSize,
114
+ Rect: btn.Rect,
115
+ targetPanel,
116
+ };
117
+ }),
118
+ };
119
+ });
120
+ }
121
+ const data = { panels: panelsData };
122
+ const tree = new treeStructure_1.AACTree();
123
+ data.panels.forEach((panel) => {
124
+ const page = new treeStructure_1.AACPage({
125
+ id: panel.id,
126
+ name: panel.name,
127
+ grid: [],
128
+ buttons: [],
129
+ parentId: null,
130
+ });
131
+ // Create a 2D grid to track button positions
132
+ const gridLayout = [];
133
+ const maxRows = 20; // Reasonable default for Apple Panels
134
+ const maxCols = 20;
135
+ for (let r = 0; r < maxRows; r++) {
136
+ gridLayout[r] = new Array(maxCols).fill(null);
137
+ }
138
+ panel.buttons.forEach((btn, idx) => {
139
+ // Create semantic action from Apple Panels button
140
+ let semanticAction;
141
+ if (btn.targetPanel) {
142
+ semanticAction = {
143
+ category: treeStructure_1.AACSemanticCategory.NAVIGATION,
144
+ intent: treeStructure_1.AACSemanticIntent.NAVIGATE_TO,
145
+ targetId: btn.targetPanel,
146
+ platformData: {
147
+ applePanels: {
148
+ actionType: 'ActionOpenPanel',
149
+ parameters: { PanelID: `USER.${btn.targetPanel}` },
150
+ },
151
+ },
152
+ fallback: {
153
+ type: 'NAVIGATE',
154
+ targetPageId: btn.targetPanel,
155
+ },
156
+ };
157
+ }
158
+ else {
159
+ semanticAction = {
160
+ category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
161
+ intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
162
+ text: btn.message || btn.label,
163
+ platformData: {
164
+ applePanels: {
165
+ actionType: 'ActionPressKeyCharSequence',
166
+ parameters: {
167
+ CharString: btn.message || btn.label || '',
168
+ isStickyKey: false,
169
+ },
170
+ },
171
+ },
172
+ fallback: {
173
+ type: 'SPEAK',
174
+ message: btn.message || btn.label,
175
+ },
176
+ };
177
+ }
178
+ const button = new treeStructure_1.AACButton({
179
+ id: `${panel.id}_btn_${idx}`,
180
+ label: btn.label,
181
+ message: btn.message || btn.label,
182
+ targetPageId: btn.targetPanel,
183
+ semanticAction: semanticAction,
184
+ style: {
185
+ backgroundColor: btn.DisplayColor,
186
+ fontSize: btn.FontSize,
187
+ fontWeight: btn.DisplayImageWeight === 'bold' ? 'bold' : 'normal',
188
+ },
189
+ });
190
+ page.addButton(button);
191
+ // Place button in grid layout using Rect position data
192
+ if (btn.Rect) {
193
+ const rect = this.parseRect(btn.Rect);
194
+ if (rect) {
195
+ const gridPos = this.pixelToGrid(rect.x, rect.y);
196
+ const gridWidth = Math.max(1, Math.ceil(rect.width / 25));
197
+ const gridHeight = Math.max(1, Math.ceil(rect.height / 25));
198
+ // Place button in grid (handle width/height span)
199
+ for (let r = gridPos.gridY; r < gridPos.gridY + gridHeight && r < maxRows; r++) {
200
+ for (let c = gridPos.gridX; c < gridPos.gridX + gridWidth && c < maxCols; c++) {
201
+ if (gridLayout[r] && gridLayout[r][c] === null) {
202
+ gridLayout[r][c] = button;
203
+ }
204
+ }
205
+ }
206
+ }
207
+ }
208
+ });
209
+ // Set the page's grid layout
210
+ page.grid = gridLayout;
211
+ tree.addPage(page);
212
+ });
213
+ return tree;
214
+ }
215
+ processTexts(filePathOrBuffer, translations, outputPath) {
216
+ // Load the tree, apply translations, and save to new file
217
+ const tree = this.loadIntoTree(filePathOrBuffer);
218
+ // Apply translations to all text content
219
+ Object.values(tree.pages).forEach((page) => {
220
+ // Translate page names
221
+ if (page.name && translations.has(page.name)) {
222
+ page.name = translations.get(page.name);
223
+ }
224
+ // Translate button labels and messages
225
+ page.buttons.forEach((button) => {
226
+ if (button.label && translations.has(button.label)) {
227
+ button.label = translations.get(button.label);
228
+ }
229
+ if (button.message && translations.has(button.message)) {
230
+ button.message = translations.get(button.message);
231
+ }
232
+ if (button.semanticAction) {
233
+ const intentStr = String(button.semanticAction.intent);
234
+ if (intentStr === 'SPEAK_TEXT' || intentStr === 'INSERT_TEXT') {
235
+ const updatedText = button.message || button.label || '';
236
+ button.semanticAction.text = updatedText;
237
+ if (button.semanticAction.fallback) {
238
+ button.semanticAction.fallback.message = updatedText;
239
+ }
240
+ const platformParams = button.semanticAction.platformData?.applePanels?.parameters;
241
+ if (platformParams && typeof platformParams === 'object') {
242
+ if ('CharString' in platformParams) {
243
+ platformParams.CharString = updatedText;
244
+ }
245
+ if ('PanelID' in platformParams && button.targetPageId) {
246
+ platformParams.PanelID = `USER.${button.targetPageId}`;
247
+ }
248
+ }
249
+ }
250
+ }
251
+ });
252
+ });
253
+ // Save the translated tree to the requested location and return its content
254
+ this.saveFromTree(tree, outputPath);
255
+ if (outputPath.endsWith('.plist')) {
256
+ return fs_1.default.readFileSync(outputPath);
257
+ }
258
+ // In bundle mode, return the PanelDefinitions.plist content
259
+ const configPath = outputPath.endsWith('.ascconfig') ? outputPath : `${outputPath}.ascconfig`;
260
+ const panelDefsPath = path_1.default.join(configPath, 'Contents', 'Resources', 'PanelDefinitions.plist');
261
+ return fs_1.default.readFileSync(panelDefsPath);
262
+ }
263
+ saveFromTree(tree, outputPath) {
264
+ // Support two output modes:
265
+ // 1) Single-file .plist (PanelDefinitions.plist content written directly)
266
+ // 2) Apple Panels bundle folder (*.ascconfig) with Contents/Resources structure
267
+ const isSinglePlist = outputPath.endsWith('.plist');
268
+ // Prepare folder structure only when exporting as bundle
269
+ let configPath = '';
270
+ let contentsPath = '';
271
+ let resourcesPath = '';
272
+ if (!isSinglePlist) {
273
+ configPath = outputPath.endsWith('.ascconfig') ? outputPath : `${outputPath}.ascconfig`;
274
+ contentsPath = path_1.default.join(configPath, 'Contents');
275
+ resourcesPath = path_1.default.join(contentsPath, 'Resources');
276
+ if (!fs_1.default.existsSync(configPath))
277
+ fs_1.default.mkdirSync(configPath, { recursive: true });
278
+ if (!fs_1.default.existsSync(contentsPath))
279
+ fs_1.default.mkdirSync(contentsPath, { recursive: true });
280
+ if (!fs_1.default.existsSync(resourcesPath))
281
+ fs_1.default.mkdirSync(resourcesPath, { recursive: true });
282
+ // Create Info.plist (bundle mode only)
283
+ const infoPlist = {
284
+ ASCConfigurationDisplayName: 'AAC Processors Export',
285
+ ASCConfigurationIdentifier: `com.aacprocessors.${Date.now()}`,
286
+ ASCConfigurationProductSupportType: 'VirtualKeyboard',
287
+ ASCConfigurationVersion: '7.1',
288
+ CFBundleDevelopmentRegion: 'en',
289
+ CFBundleIdentifier: 'com.aacprocessors.panel.export',
290
+ CFBundleName: 'AAC Processors Panels',
291
+ CFBundleShortVersionString: '1.0',
292
+ CFBundleVersion: '1',
293
+ NSHumanReadableCopyright: 'Generated by AAC Processors',
294
+ };
295
+ const infoPlistContent = plist_1.default.build(infoPlist);
296
+ fs_1.default.writeFileSync(path_1.default.join(contentsPath, 'Info.plist'), infoPlistContent);
297
+ // Create AssetIndex.plist (empty)
298
+ const assetIndexContent = plist_1.default.build({});
299
+ fs_1.default.writeFileSync(path_1.default.join(resourcesPath, 'AssetIndex.plist'), assetIndexContent);
300
+ }
301
+ // Build PanelDefinitions content from tree
302
+ const panelsDict = {};
303
+ Object.values(tree.pages).forEach((page, pageIndex) => {
304
+ const panelId = `USER.${page.id}`;
305
+ // Detect actual grid dimensions from the source data
306
+ let gridCols = 4; // Default fallback
307
+ let gridRows = Math.ceil(page.buttons.length / gridCols);
308
+ if (page.grid && page.grid.length > 0) {
309
+ // Use actual grid dimensions from source
310
+ gridRows = page.grid.length;
311
+ gridCols = page.grid[0] ? page.grid[0].length : 4;
312
+ // Find the actual used area to avoid empty space
313
+ let maxUsedX = 0, maxUsedY = 0;
314
+ for (let y = 0; y < page.grid.length; y++) {
315
+ for (let x = 0; x < page.grid[y].length; x++) {
316
+ if (page.grid[y][x]) {
317
+ maxUsedX = Math.max(maxUsedX, x);
318
+ maxUsedY = Math.max(maxUsedY, y);
319
+ }
320
+ }
321
+ }
322
+ // Use the actual used dimensions if they're reasonable
323
+ if (maxUsedX > 0 && maxUsedY > 0) {
324
+ gridCols = maxUsedX + 1;
325
+ gridRows = maxUsedY + 1;
326
+ }
327
+ }
328
+ else {
329
+ // Intelligent auto-layout: try to make a reasonable grid
330
+ const buttonCount = page.buttons.length;
331
+ if (buttonCount <= 6) {
332
+ gridCols = Math.min(buttonCount, 3); // 1-3 columns for small sets
333
+ }
334
+ else if (buttonCount <= 12) {
335
+ gridCols = 4; // 4 columns for medium sets
336
+ }
337
+ else if (buttonCount <= 24) {
338
+ gridCols = 6; // 6 columns for larger sets
339
+ }
340
+ else {
341
+ gridCols = 8; // 8 columns for very large sets
342
+ }
343
+ gridRows = Math.ceil(buttonCount / gridCols);
344
+ }
345
+ const panelObjects = page.buttons.map((button, buttonIndex) => {
346
+ // Find button position in grid layout and convert to Rect format
347
+ let rect;
348
+ if (page.grid && page.grid.length > 0) {
349
+ // Search for button in actual grid layout
350
+ let found = false;
351
+ for (let y = 0; y < page.grid.length && !found; y++) {
352
+ for (let x = 0; x < page.grid[y].length && !found; x++) {
353
+ const gridButton = page.grid[y][x];
354
+ if (gridButton && gridButton.id === button.id) {
355
+ // Convert grid coordinates to pixel coordinates
356
+ const pixelX = x * 105; // 105px per column (100px button + 5px spacing)
357
+ const pixelY = y * 30; // 30px per row (25px button + 5px spacing)
358
+ rect = `{{${pixelX}, ${pixelY}}, {100, 25}}`;
359
+ found = true;
360
+ }
361
+ }
362
+ }
363
+ if (!found) {
364
+ // Button not found in grid, use auto-layout
365
+ const autoX = (buttonIndex % gridCols) * 105;
366
+ const autoY = Math.floor(buttonIndex / gridCols) * 30;
367
+ rect = `{{${autoX}, ${autoY}}, {100, 25}}`;
368
+ }
369
+ }
370
+ else {
371
+ // Use auto-layout with detected grid dimensions
372
+ const autoX = (buttonIndex % gridCols) * 105;
373
+ const autoY = Math.floor(buttonIndex / gridCols) * 30;
374
+ rect = `{{${autoX}, ${autoY}}, {100, 25}}`;
375
+ }
376
+ const buttonObj = {
377
+ ButtonType: 0,
378
+ DisplayText: button.label || 'Button',
379
+ FontSize: button.style?.fontSize || 12,
380
+ ID: `Button.${button.id}`,
381
+ PanelObjectType: 'Button',
382
+ Rect: rect,
383
+ };
384
+ if (button.style?.backgroundColor) {
385
+ buttonObj.DisplayColor = button.style.backgroundColor;
386
+ }
387
+ if (button.style?.fontWeight === 'bold') {
388
+ buttonObj.DisplayImageWeight = 'FontWeightBold';
389
+ }
390
+ else {
391
+ buttonObj.DisplayImageWeight = 'FontWeightRegular';
392
+ }
393
+ // Add actions - prefer semantic action if available
394
+ buttonObj.Actions = [this.createApplePanelsAction(button)];
395
+ return buttonObj;
396
+ });
397
+ panelsDict[panelId] = {
398
+ DisplayOrder: pageIndex + 1,
399
+ GlidingLensSize: 5,
400
+ HasTransientPosition: false,
401
+ HideHome: false,
402
+ HideMinimize: false,
403
+ HidePanelAdjustments: false,
404
+ HideSwitchDock: false,
405
+ HideSwitchDockContextualButtons: false,
406
+ HideTitlebar: false,
407
+ ID: panelId,
408
+ Name: page.name || 'Panel',
409
+ PanelObjects: panelObjects,
410
+ ProductSupportType: 'All',
411
+ Rect: '{{15, 75}, {425, 55}}',
412
+ ScanStyle: 0,
413
+ ShowPanelLocationString: 'CustomPanelList',
414
+ UsesPinnedResizing: false,
415
+ };
416
+ });
417
+ const panelDefinitions = {
418
+ Panels: panelsDict,
419
+ ToolbarOrdering: {
420
+ ToolbarIdentifiersAfterBasePanel: [],
421
+ ToolbarIdentifiersPriorToBasePanel: [],
422
+ },
423
+ };
424
+ const panelDefsContent = plist_1.default.build(panelDefinitions);
425
+ if (isSinglePlist) {
426
+ // Write single PanelDefinitions.plist file directly
427
+ const dir = path_1.default.dirname(outputPath);
428
+ if (!fs_1.default.existsSync(dir))
429
+ fs_1.default.mkdirSync(dir, { recursive: true });
430
+ fs_1.default.writeFileSync(outputPath, panelDefsContent);
431
+ }
432
+ else {
433
+ // Write into bundle structure
434
+ fs_1.default.writeFileSync(path_1.default.join(resourcesPath, 'PanelDefinitions.plist'), panelDefsContent);
435
+ }
436
+ }
437
+ createApplePanelsAction(button) {
438
+ // Use semantic action if available
439
+ if (button.semanticAction?.platformData?.applePanels) {
440
+ const applePanelsData = button.semanticAction.platformData.applePanels;
441
+ return {
442
+ ActionParam: applePanelsData.parameters,
443
+ ActionRecordedOffset: 0.0,
444
+ ActionType: applePanelsData.actionType,
445
+ ID: `Action.${button.id}`,
446
+ };
447
+ }
448
+ // Handle semantic actions without Apple Panels specific data
449
+ if (button.semanticAction) {
450
+ const intentStr = String(button.semanticAction.intent);
451
+ switch (intentStr) {
452
+ case 'NAVIGATE_TO':
453
+ return {
454
+ ActionParam: {
455
+ PanelID: `USER.${button.semanticAction.targetId || button.targetPageId || ''}`,
456
+ },
457
+ ActionRecordedOffset: 0.0,
458
+ ActionType: 'ActionOpenPanel',
459
+ ID: `Action.${button.id}`,
460
+ };
461
+ case 'SPEAK_TEXT':
462
+ case 'INSERT_TEXT':
463
+ return {
464
+ ActionParam: {
465
+ CharString: button.semanticAction.text || button.message || button.label || '',
466
+ isStickyKey: false,
467
+ },
468
+ ActionRecordedOffset: 0.0,
469
+ ActionType: 'ActionPressKeyCharSequence',
470
+ ID: `Action.${button.id}`,
471
+ };
472
+ case 'SEND_KEYS':
473
+ return {
474
+ ActionParam: {
475
+ CharString: button.semanticAction.text || '',
476
+ isStickyKey: false,
477
+ },
478
+ ActionRecordedOffset: 0.0,
479
+ ActionType: 'ActionSendKeys',
480
+ ID: `Action.${button.id}`,
481
+ };
482
+ default:
483
+ // Fallback to speech for unknown semantic actions
484
+ return {
485
+ ActionParam: {
486
+ CharString: button.semanticAction.fallback?.message || button.message || button.label || '',
487
+ isStickyKey: false,
488
+ },
489
+ ActionRecordedOffset: 0.0,
490
+ ActionType: 'ActionPressKeyCharSequence',
491
+ ID: `Action.${button.id}`,
492
+ };
493
+ }
494
+ }
495
+ // Default SPEAK action if no semantic action
496
+ return {
497
+ ActionParam: {
498
+ CharString: button.message || button.label || '',
499
+ isStickyKey: false,
500
+ },
501
+ ActionRecordedOffset: 0.0,
502
+ ActionType: 'ActionPressKeyCharSequence',
503
+ ID: `Action.${button.id}`,
504
+ };
505
+ }
506
+ /**
507
+ * Extract strings with metadata for aac-tools-platform compatibility
508
+ * Uses the generic implementation from BaseProcessor
509
+ */
510
+ extractStringsWithMetadata(filePath) {
511
+ return this.extractStringsWithMetadataGeneric(filePath);
512
+ }
513
+ /**
514
+ * Generate translated download for aac-tools-platform compatibility
515
+ * Uses the generic implementation from BaseProcessor
516
+ */
517
+ generateTranslatedDownload(filePath, translatedStrings, sourceStrings) {
518
+ return this.generateTranslatedDownloadGeneric(filePath, translatedStrings, sourceStrings);
519
+ }
520
+ }
521
+ exports.ApplePanelsProcessor = ApplePanelsProcessor;
@@ -0,0 +1,49 @@
1
+ import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString, SourceString } from '../core/baseProcessor';
2
+ import { AACTree } from '../core/treeStructure';
3
+ declare class AstericsGridProcessor extends BaseProcessor {
4
+ private loadAudio;
5
+ constructor(options?: ProcessorOptions & {
6
+ loadAudio?: boolean;
7
+ });
8
+ extractTexts(filePathOrBuffer: string | Buffer): string[];
9
+ private extractRawTexts;
10
+ private extractActionTexts;
11
+ loadIntoTree(filePathOrBuffer: string | Buffer): AACTree;
12
+ private getLocalizedLabel;
13
+ private getLocalizedText;
14
+ private createButtonFromElement;
15
+ processTexts(filePathOrBuffer: string | Buffer, translations: Map<string, string>, outputPath: string): Buffer;
16
+ private applyTranslationsToGridFile;
17
+ private applyTranslationsToAction;
18
+ saveFromTree(tree: AACTree, outputPath: string): void;
19
+ /**
20
+ * Add audio recording to a specific grid element
21
+ */
22
+ addAudioToElement(filePath: string, elementId: string, audioData: Buffer, metadata?: string): void;
23
+ /**
24
+ * Create a copy of the grid file with audio recordings added
25
+ */
26
+ createAudioEnhancedGridFile(sourceFilePath: string, targetFilePath: string, audioMappings: Map<string, {
27
+ audioData: Buffer;
28
+ metadata?: string;
29
+ }>): void;
30
+ /**
31
+ * Extract all element IDs from the grid file for audio mapping
32
+ */
33
+ getElementIds(filePathOrBuffer: string | Buffer): string[];
34
+ /**
35
+ * Check if an element has audio recording
36
+ */
37
+ hasAudioRecording(filePathOrBuffer: string | Buffer, elementId: string): boolean;
38
+ /**
39
+ * Extract strings with metadata for aac-tools-platform compatibility
40
+ * Uses the generic implementation from BaseProcessor
41
+ */
42
+ extractStringsWithMetadata(filePath: string): Promise<ExtractStringsResult>;
43
+ /**
44
+ * Generate translated download for aac-tools-platform compatibility
45
+ * Uses the generic implementation from BaseProcessor
46
+ */
47
+ generateTranslatedDownload(filePath: string, translatedStrings: TranslatedString[], sourceStrings: SourceString[]): Promise<string>;
48
+ }
49
+ export { AstericsGridProcessor };