@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.
- package/README.md +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/core/treeStructure.d.ts +9 -1
- package/dist/core/treeStructure.js +5 -1
- package/dist/processors/applePanelsProcessor.js +67 -41
- package/dist/processors/astericsGridProcessor.js +120 -88
- package/dist/processors/excelProcessor.d.ts +3 -3
- package/dist/processors/excelProcessor.js +48 -77
- package/dist/processors/gridset/styleHelpers.d.ts +46 -0
- package/dist/processors/gridset/styleHelpers.js +211 -0
- package/dist/processors/gridset/wordlistHelpers.js +3 -2
- package/dist/processors/gridsetProcessor.js +97 -105
- package/dist/processors/index.d.ts +1 -0
- package/dist/processors/index.js +8 -1
- package/dist/processors/obfProcessor.d.ts +2 -0
- package/dist/processors/obfProcessor.js +129 -52
- package/dist/processors/opmlProcessor.js +13 -3
- package/dist/processors/snapProcessor.js +18 -10
- package/dist/processors/touchchatProcessor.js +45 -22
- package/dist/types/aac.d.ts +4 -0
- package/docs/Grid3-Styling-Guide.md +287 -0
- package/package.json +1 -1
|
@@ -32,7 +32,6 @@ const path_1 = __importDefault(require("path"));
|
|
|
32
32
|
const ExcelJS = __importStar(require("exceljs"));
|
|
33
33
|
const baseProcessor_1 = require("../core/baseProcessor");
|
|
34
34
|
const treeStructure_1 = require("../core/treeStructure");
|
|
35
|
-
const treeStructure_2 = require("../core/treeStructure");
|
|
36
35
|
/**
|
|
37
36
|
* Excel Processor for converting AAC grids to Excel format
|
|
38
37
|
* Converts AAC tree structures to Excel workbooks with each page as a worksheet
|
|
@@ -44,47 +43,18 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
44
43
|
* @param filePathOrBuffer - Path to Excel file or Buffer containing Excel data
|
|
45
44
|
* @returns Array of all text content found in the Excel file
|
|
46
45
|
*/
|
|
47
|
-
extractTexts(
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
const workbook = new ExcelJS.Workbook();
|
|
51
|
-
if (Buffer.isBuffer(filePathOrBuffer)) {
|
|
52
|
-
// For buffer input, we need to read it differently
|
|
53
|
-
// This is a placeholder - actual implementation would need to handle buffer reading
|
|
54
|
-
throw new Error('Buffer input not yet implemented for Excel files');
|
|
55
|
-
}
|
|
56
|
-
else {
|
|
57
|
-
// Read from file path
|
|
58
|
-
if (!fs_1.default.existsSync(filePathOrBuffer)) {
|
|
59
|
-
return texts;
|
|
60
|
-
}
|
|
61
|
-
// Note: ExcelJS readFile is async, but we need sync for this interface
|
|
62
|
-
// This is a limitation that would need to be addressed in a real implementation
|
|
63
|
-
throw new Error('Synchronous Excel reading not yet implemented');
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
catch (error) {
|
|
67
|
-
console.warn(`Failed to extract texts from Excel file: ${error}`);
|
|
68
|
-
return texts;
|
|
69
|
-
}
|
|
46
|
+
extractTexts(_filePathOrBuffer) {
|
|
47
|
+
console.warn('ExcelProcessor.extractTexts is not implemented yet.');
|
|
48
|
+
return [];
|
|
70
49
|
}
|
|
71
50
|
/**
|
|
72
51
|
* Load Excel file into AACTree structure
|
|
73
52
|
* @param filePathOrBuffer - Path to Excel file or Buffer containing Excel data
|
|
74
53
|
* @returns AACTree representation of the Excel file
|
|
75
54
|
*/
|
|
76
|
-
loadIntoTree(
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
// For now, return empty tree as Excel -> AAC conversion is not the primary use case
|
|
80
|
-
// This would be implemented if bidirectional conversion is needed
|
|
81
|
-
console.warn('Excel to AAC conversion not implemented - returning empty tree');
|
|
82
|
-
return tree;
|
|
83
|
-
}
|
|
84
|
-
catch (error) {
|
|
85
|
-
console.warn(`Failed to load Excel file into tree: ${error}`);
|
|
86
|
-
return tree;
|
|
87
|
-
}
|
|
55
|
+
loadIntoTree(_filePathOrBuffer) {
|
|
56
|
+
console.warn('ExcelProcessor.loadIntoTree is not implemented yet.');
|
|
57
|
+
return new treeStructure_1.AACTree();
|
|
88
58
|
}
|
|
89
59
|
/**
|
|
90
60
|
* Process texts in Excel file (apply translations)
|
|
@@ -93,18 +63,13 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
93
63
|
* @param outputPath - Path where translated Excel file should be saved
|
|
94
64
|
* @returns Buffer containing the translated Excel file
|
|
95
65
|
*/
|
|
96
|
-
processTexts(
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
throw new Error('Excel text processing not yet implemented');
|
|
102
|
-
}
|
|
103
|
-
catch (error) {
|
|
104
|
-
console.warn(`Failed to process Excel texts: ${error}`);
|
|
105
|
-
// Return empty buffer as fallback
|
|
106
|
-
return Buffer.alloc(0);
|
|
66
|
+
processTexts(_filePathOrBuffer, _translations, outputPath) {
|
|
67
|
+
console.warn('ExcelProcessor.processTexts is not implemented yet.');
|
|
68
|
+
const outputDir = path_1.default.dirname(outputPath);
|
|
69
|
+
if (!fs_1.default.existsSync(outputDir)) {
|
|
70
|
+
fs_1.default.mkdirSync(outputDir, { recursive: true });
|
|
107
71
|
}
|
|
72
|
+
return Buffer.alloc(0);
|
|
108
73
|
}
|
|
109
74
|
/**
|
|
110
75
|
* Convert an AAC page to an Excel worksheet
|
|
@@ -113,7 +78,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
113
78
|
* @param tree - Full AAC tree for navigation context
|
|
114
79
|
* @param usedNames - Set of already used worksheet names to avoid duplicates
|
|
115
80
|
*/
|
|
116
|
-
|
|
81
|
+
convertPageToWorksheet(workbook, page, tree, usedNames = new Set()) {
|
|
117
82
|
// Create worksheet with page name (sanitized for Excel and unique)
|
|
118
83
|
const worksheetName = this.getUniqueWorksheetName(page.name || page.id, usedNames);
|
|
119
84
|
const worksheet = workbook.addWorksheet(worksheetName);
|
|
@@ -122,16 +87,16 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
122
87
|
// Add navigation row if enabled (optional feature)
|
|
123
88
|
let startRow = 1;
|
|
124
89
|
if (this.shouldAddNavigationRow()) {
|
|
125
|
-
|
|
90
|
+
this.addNavigationRow(worksheet, page, tree);
|
|
126
91
|
startRow = 2; // Start content after navigation row
|
|
127
92
|
}
|
|
128
93
|
// Convert grid layout if available
|
|
129
94
|
if (page.grid && page.grid.length > 0) {
|
|
130
|
-
|
|
95
|
+
this.convertGridLayout(worksheet, page.grid, startRow);
|
|
131
96
|
}
|
|
132
97
|
else {
|
|
133
98
|
// Convert button list to grid layout
|
|
134
|
-
|
|
99
|
+
this.convertButtonsToGrid(worksheet, page.buttons, rows, cols, startRow);
|
|
135
100
|
}
|
|
136
101
|
// Apply worksheet formatting
|
|
137
102
|
this.formatWorksheet(worksheet, rows, cols, startRow);
|
|
@@ -165,14 +130,14 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
165
130
|
* @param grid - 2D array of AAC buttons
|
|
166
131
|
* @param startRow - Starting row number
|
|
167
132
|
*/
|
|
168
|
-
|
|
133
|
+
convertGridLayout(worksheet, grid, startRow) {
|
|
169
134
|
for (let row = 0; row < grid.length; row++) {
|
|
170
135
|
for (let col = 0; col < grid[row].length; col++) {
|
|
171
136
|
const button = grid[row][col];
|
|
172
137
|
if (button) {
|
|
173
138
|
const excelRow = startRow + row;
|
|
174
139
|
const excelCol = col + 1; // Excel columns are 1-based
|
|
175
|
-
|
|
140
|
+
this.setButtonCell(worksheet, button, excelRow, excelCol);
|
|
176
141
|
}
|
|
177
142
|
}
|
|
178
143
|
}
|
|
@@ -185,7 +150,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
185
150
|
* @param cols - Number of columns in grid
|
|
186
151
|
* @param startRow - Starting row number
|
|
187
152
|
*/
|
|
188
|
-
|
|
153
|
+
convertButtonsToGrid(worksheet, buttons, rows, cols, startRow) {
|
|
189
154
|
for (let i = 0; i < buttons.length; i++) {
|
|
190
155
|
const button = buttons[i];
|
|
191
156
|
const row = Math.floor(i / cols);
|
|
@@ -193,7 +158,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
193
158
|
if (row < rows) {
|
|
194
159
|
const excelRow = startRow + row;
|
|
195
160
|
const excelCol = col + 1; // Excel columns are 1-based
|
|
196
|
-
|
|
161
|
+
this.setButtonCell(worksheet, button, excelRow, excelCol);
|
|
197
162
|
}
|
|
198
163
|
}
|
|
199
164
|
}
|
|
@@ -204,7 +169,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
204
169
|
* @param row - Excel row number
|
|
205
170
|
* @param col - Excel column number
|
|
206
171
|
*/
|
|
207
|
-
|
|
172
|
+
setButtonCell(worksheet, button, row, col) {
|
|
208
173
|
const cell = worksheet.getCell(row, col);
|
|
209
174
|
// Set cell value to button label
|
|
210
175
|
cell.value = button.label || '';
|
|
@@ -217,7 +182,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
217
182
|
this.applyCellStyling(cell, button.style);
|
|
218
183
|
}
|
|
219
184
|
// Add navigation link if this is a navigation button
|
|
220
|
-
if (button.semanticAction?.intent ===
|
|
185
|
+
if (button.semanticAction?.intent === treeStructure_1.AACSemanticIntent.NAVIGATE_TO && button.targetPageId) {
|
|
221
186
|
this.addNavigationLink(cell, button.targetPageId);
|
|
222
187
|
}
|
|
223
188
|
// Set cell size for better visibility
|
|
@@ -229,14 +194,16 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
229
194
|
* @param style - AAC style object
|
|
230
195
|
*/
|
|
231
196
|
applyCellStyling(cell, style) {
|
|
232
|
-
|
|
197
|
+
let fill;
|
|
233
198
|
const font = {};
|
|
234
|
-
|
|
199
|
+
let border;
|
|
235
200
|
// Background color
|
|
236
201
|
if (style.backgroundColor) {
|
|
237
|
-
fill
|
|
238
|
-
|
|
239
|
-
|
|
202
|
+
fill = {
|
|
203
|
+
type: 'pattern',
|
|
204
|
+
pattern: 'solid',
|
|
205
|
+
fgColor: { argb: this.convertColorToArgb(style.backgroundColor) },
|
|
206
|
+
};
|
|
240
207
|
}
|
|
241
208
|
// Font color
|
|
242
209
|
if (style.fontColor) {
|
|
@@ -263,24 +230,27 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
263
230
|
font.underline = true;
|
|
264
231
|
}
|
|
265
232
|
// Border
|
|
266
|
-
if (style.borderColor || style.borderWidth) {
|
|
267
|
-
const
|
|
233
|
+
if (style.borderColor || typeof style.borderWidth === 'number') {
|
|
234
|
+
const borderWidth = style.borderWidth ?? 1;
|
|
235
|
+
const borderStyle = borderWidth > 1 ? 'thick' : 'thin';
|
|
268
236
|
const borderColor = style.borderColor
|
|
269
237
|
? { argb: this.convertColorToArgb(style.borderColor) }
|
|
270
238
|
: { argb: 'FF000000' }; // Default black
|
|
271
|
-
border
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
239
|
+
border = {
|
|
240
|
+
top: { style: borderStyle, color: borderColor },
|
|
241
|
+
left: { style: borderStyle, color: borderColor },
|
|
242
|
+
bottom: { style: borderStyle, color: borderColor },
|
|
243
|
+
right: { style: borderStyle, color: borderColor },
|
|
244
|
+
};
|
|
275
245
|
}
|
|
276
246
|
// Apply styling to cell
|
|
277
|
-
if (
|
|
247
|
+
if (fill) {
|
|
278
248
|
cell.fill = fill;
|
|
279
249
|
}
|
|
280
250
|
if (Object.keys(font).length > 0) {
|
|
281
251
|
cell.font = font;
|
|
282
252
|
}
|
|
283
|
-
if (
|
|
253
|
+
if (border) {
|
|
284
254
|
cell.border = border;
|
|
285
255
|
}
|
|
286
256
|
// Center align text
|
|
@@ -369,7 +339,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
369
339
|
* @param page - Current AAC page
|
|
370
340
|
* @param tree - Full AAC tree for navigation context
|
|
371
341
|
*/
|
|
372
|
-
|
|
342
|
+
addNavigationRow(worksheet, page, tree) {
|
|
373
343
|
const navButtons = ExcelProcessor.NAVIGATION_BUTTONS;
|
|
374
344
|
for (let i = 0; i < navButtons.length; i++) {
|
|
375
345
|
const cell = worksheet.getCell(1, i + 1);
|
|
@@ -440,9 +410,9 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
440
410
|
// - Max 31 characters
|
|
441
411
|
// - Cannot contain: \ / ? * [ ] :
|
|
442
412
|
// - Cannot be empty
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
413
|
+
let cleaned = (name || '').replace(/[\\/?*:]/g, '_');
|
|
414
|
+
cleaned = cleaned.replace(/\[/g, '_').replace(/\]/g, '_');
|
|
415
|
+
cleaned = cleaned.substring(0, 31);
|
|
446
416
|
if (cleaned.length === 0) {
|
|
447
417
|
return 'Sheet1';
|
|
448
418
|
}
|
|
@@ -499,11 +469,12 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
499
469
|
await this.saveFromTreeAsync(tree, outputPath);
|
|
500
470
|
}
|
|
501
471
|
catch (error) {
|
|
502
|
-
|
|
472
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
473
|
+
console.error('Failed to save Excel file:', message);
|
|
503
474
|
try {
|
|
504
475
|
const fallbackPath = outputPath.replace(/\.xlsx$/i, '_error.txt');
|
|
505
476
|
fs_1.default.mkdirSync(path_1.default.dirname(fallbackPath), { recursive: true });
|
|
506
|
-
fs_1.default.writeFileSync(fallbackPath, `Error saving Excel file: ${
|
|
477
|
+
fs_1.default.writeFileSync(fallbackPath, `Error saving Excel file: ${message}`);
|
|
507
478
|
}
|
|
508
479
|
catch (writeError) {
|
|
509
480
|
console.error('Failed to write Excel error file:', writeError);
|
|
@@ -532,7 +503,7 @@ class ExcelProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
532
503
|
// Convert each AAC page to an Excel worksheet
|
|
533
504
|
for (const pageId in tree.pages) {
|
|
534
505
|
const page = tree.pages[pageId];
|
|
535
|
-
|
|
506
|
+
this.convertPageToWorksheet(workbook, page, tree, usedNames);
|
|
536
507
|
}
|
|
537
508
|
// Save the workbook
|
|
538
509
|
await workbook.xlsx.writeFile(outputPath);
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grid3 Style Helpers
|
|
3
|
+
*
|
|
4
|
+
* Utilities for creating and managing Grid3 styles, including default styles,
|
|
5
|
+
* style XML generation, and style conversion utilities.
|
|
6
|
+
*/
|
|
7
|
+
/**
|
|
8
|
+
* Grid3 Style object structure
|
|
9
|
+
*/
|
|
10
|
+
export interface Grid3Style {
|
|
11
|
+
BackColour?: string;
|
|
12
|
+
TileColour?: string;
|
|
13
|
+
BorderColour?: string;
|
|
14
|
+
FontColour?: string;
|
|
15
|
+
FontName?: string;
|
|
16
|
+
FontSize?: string | number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Default Grid3 styles for common use cases
|
|
20
|
+
* Colors are in 8-digit ARGB hex format (#AARRGGBBFF)
|
|
21
|
+
*/
|
|
22
|
+
export declare const DEFAULT_GRID3_STYLES: Record<string, Grid3Style>;
|
|
23
|
+
/**
|
|
24
|
+
* Category-specific styles for navigation and organization
|
|
25
|
+
*/
|
|
26
|
+
export declare const CATEGORY_STYLES: Record<string, Grid3Style>;
|
|
27
|
+
/**
|
|
28
|
+
* Ensure a color has an alpha channel (Grid3 format requires 8-digit ARGB)
|
|
29
|
+
* @param color - Color string (hex format)
|
|
30
|
+
* @returns Color with alpha channel in format #AARRGGBBFF
|
|
31
|
+
*/
|
|
32
|
+
export declare function ensureAlphaChannel(color: string | undefined): string;
|
|
33
|
+
/**
|
|
34
|
+
* Create a Grid3 style XML string with default and category styles
|
|
35
|
+
* @param includeCategories - Whether to include category-specific styles (default: true)
|
|
36
|
+
* @returns XML string for Settings0/styles.xml
|
|
37
|
+
*/
|
|
38
|
+
export declare function createDefaultStylesXml(includeCategories?: boolean): string;
|
|
39
|
+
/**
|
|
40
|
+
* Create a custom category style
|
|
41
|
+
* @param categoryName - Name of the category
|
|
42
|
+
* @param backgroundColor - Background color in hex format
|
|
43
|
+
* @param fontColor - Font color in hex format (default: white)
|
|
44
|
+
* @returns Grid3Style object
|
|
45
|
+
*/
|
|
46
|
+
export declare function createCategoryStyle(categoryName: string, backgroundColor: string, fontColor?: string): Grid3Style;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Grid3 Style Helpers
|
|
4
|
+
*
|
|
5
|
+
* Utilities for creating and managing Grid3 styles, including default styles,
|
|
6
|
+
* style XML generation, and style conversion utilities.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.CATEGORY_STYLES = exports.DEFAULT_GRID3_STYLES = void 0;
|
|
10
|
+
exports.ensureAlphaChannel = ensureAlphaChannel;
|
|
11
|
+
exports.createDefaultStylesXml = createDefaultStylesXml;
|
|
12
|
+
exports.createCategoryStyle = createCategoryStyle;
|
|
13
|
+
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
14
|
+
/**
|
|
15
|
+
* Default Grid3 styles for common use cases
|
|
16
|
+
* Colors are in 8-digit ARGB hex format (#AARRGGBBFF)
|
|
17
|
+
*/
|
|
18
|
+
exports.DEFAULT_GRID3_STYLES = {
|
|
19
|
+
Default: {
|
|
20
|
+
BackColour: '#E2EDF8FF',
|
|
21
|
+
TileColour: '#FFFFFFFF',
|
|
22
|
+
BorderColour: '#000000FF',
|
|
23
|
+
FontColour: '#000000FF',
|
|
24
|
+
FontName: 'Arial',
|
|
25
|
+
FontSize: '16',
|
|
26
|
+
},
|
|
27
|
+
Workspace: {
|
|
28
|
+
BackColour: '#FFFFFFFF',
|
|
29
|
+
TileColour: '#FFFFFFFF',
|
|
30
|
+
BorderColour: '#CCCCCCFF',
|
|
31
|
+
FontColour: '#000000FF',
|
|
32
|
+
FontName: 'Arial',
|
|
33
|
+
FontSize: '14',
|
|
34
|
+
},
|
|
35
|
+
'Auto content': {
|
|
36
|
+
BackColour: '#E8F4F8FF',
|
|
37
|
+
TileColour: '#E8F4F8FF',
|
|
38
|
+
BorderColour: '#2C82C9FF',
|
|
39
|
+
FontColour: '#000000FF',
|
|
40
|
+
FontName: 'Arial',
|
|
41
|
+
FontSize: '14',
|
|
42
|
+
},
|
|
43
|
+
'Vocab cell': {
|
|
44
|
+
BackColour: '#E8F4F8FF',
|
|
45
|
+
TileColour: '#E8F4F8FF',
|
|
46
|
+
BorderColour: '#2C82C9FF',
|
|
47
|
+
FontColour: '#000000FF',
|
|
48
|
+
FontName: 'Arial',
|
|
49
|
+
FontSize: '14',
|
|
50
|
+
},
|
|
51
|
+
'Keyboard key': {
|
|
52
|
+
BackColour: '#F0F0F0FF',
|
|
53
|
+
TileColour: '#F0F0F0FF',
|
|
54
|
+
BorderColour: '#808080FF',
|
|
55
|
+
FontColour: '#000000FF',
|
|
56
|
+
FontName: 'Arial',
|
|
57
|
+
FontSize: '12',
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* Category-specific styles for navigation and organization
|
|
62
|
+
*/
|
|
63
|
+
exports.CATEGORY_STYLES = {
|
|
64
|
+
'Actions category style': {
|
|
65
|
+
BackColour: '#4472C4FF',
|
|
66
|
+
TileColour: '#4472C4FF',
|
|
67
|
+
BorderColour: '#2F5496FF',
|
|
68
|
+
FontColour: '#FFFFFFFF',
|
|
69
|
+
FontName: 'Arial',
|
|
70
|
+
FontSize: '16',
|
|
71
|
+
},
|
|
72
|
+
'People category style': {
|
|
73
|
+
BackColour: '#ED7D31FF',
|
|
74
|
+
TileColour: '#ED7D31FF',
|
|
75
|
+
BorderColour: '#C65911FF',
|
|
76
|
+
FontColour: '#FFFFFFFF',
|
|
77
|
+
FontName: 'Arial',
|
|
78
|
+
FontSize: '16',
|
|
79
|
+
},
|
|
80
|
+
'Places category style': {
|
|
81
|
+
BackColour: '#A5A5A5FF',
|
|
82
|
+
TileColour: '#A5A5A5FF',
|
|
83
|
+
BorderColour: '#595959FF',
|
|
84
|
+
FontColour: '#FFFFFFFF',
|
|
85
|
+
FontName: 'Arial',
|
|
86
|
+
FontSize: '16',
|
|
87
|
+
},
|
|
88
|
+
'Descriptive category style': {
|
|
89
|
+
BackColour: '#70AD47FF',
|
|
90
|
+
TileColour: '#70AD47FF',
|
|
91
|
+
BorderColour: '#4F7C2FFF',
|
|
92
|
+
FontColour: '#FFFFFFFF',
|
|
93
|
+
FontName: 'Arial',
|
|
94
|
+
FontSize: '16',
|
|
95
|
+
},
|
|
96
|
+
'Social category style': {
|
|
97
|
+
BackColour: '#FFC000FF',
|
|
98
|
+
TileColour: '#FFC000FF',
|
|
99
|
+
BorderColour: '#BF8F00FF',
|
|
100
|
+
FontColour: '#000000FF',
|
|
101
|
+
FontName: 'Arial',
|
|
102
|
+
FontSize: '16',
|
|
103
|
+
},
|
|
104
|
+
'Questions category style': {
|
|
105
|
+
BackColour: '#5B9BD5FF',
|
|
106
|
+
TileColour: '#5B9BD5FF',
|
|
107
|
+
BorderColour: '#2E5C8AFF',
|
|
108
|
+
FontColour: '#FFFFFFFF',
|
|
109
|
+
FontName: 'Arial',
|
|
110
|
+
FontSize: '16',
|
|
111
|
+
},
|
|
112
|
+
'Little words category style': {
|
|
113
|
+
BackColour: '#C55A11FF',
|
|
114
|
+
TileColour: '#C55A11FF',
|
|
115
|
+
BorderColour: '#8B3F0AFF',
|
|
116
|
+
FontColour: '#FFFFFFFF',
|
|
117
|
+
FontName: 'Arial',
|
|
118
|
+
FontSize: '16',
|
|
119
|
+
},
|
|
120
|
+
};
|
|
121
|
+
/**
|
|
122
|
+
* Ensure a color has an alpha channel (Grid3 format requires 8-digit ARGB)
|
|
123
|
+
* @param color - Color string (hex format)
|
|
124
|
+
* @returns Color with alpha channel in format #AARRGGBBFF
|
|
125
|
+
*/
|
|
126
|
+
function ensureAlphaChannel(color) {
|
|
127
|
+
if (!color)
|
|
128
|
+
return '#FFFFFFFF';
|
|
129
|
+
// If already 8 digits (with alpha), return as is
|
|
130
|
+
if (color.match(/^#[0-9A-Fa-f]{8}$/))
|
|
131
|
+
return color;
|
|
132
|
+
// If 6 digits (no alpha), add FF for fully opaque
|
|
133
|
+
if (color.match(/^#[0-9A-Fa-f]{6}$/))
|
|
134
|
+
return color + 'FF';
|
|
135
|
+
// If 3 digits (shorthand), expand to 8
|
|
136
|
+
if (color.match(/^#[0-9A-Fa-f]{3}$/)) {
|
|
137
|
+
const r = color[1];
|
|
138
|
+
const g = color[2];
|
|
139
|
+
const b = color[3];
|
|
140
|
+
return `#${r}${r}${g}${g}${b}${b}FF`;
|
|
141
|
+
}
|
|
142
|
+
// Invalid or unknown format, return white
|
|
143
|
+
return '#FFFFFFFF';
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Create a Grid3 style XML string with default and category styles
|
|
147
|
+
* @param includeCategories - Whether to include category-specific styles (default: true)
|
|
148
|
+
* @returns XML string for Settings0/styles.xml
|
|
149
|
+
*/
|
|
150
|
+
function createDefaultStylesXml(includeCategories = true) {
|
|
151
|
+
const builder = new fast_xml_parser_1.XMLBuilder({
|
|
152
|
+
ignoreAttributes: false,
|
|
153
|
+
format: true,
|
|
154
|
+
indentBy: ' ',
|
|
155
|
+
});
|
|
156
|
+
const styles = { ...exports.DEFAULT_GRID3_STYLES };
|
|
157
|
+
if (includeCategories) {
|
|
158
|
+
Object.assign(styles, exports.CATEGORY_STYLES);
|
|
159
|
+
}
|
|
160
|
+
const styleArray = Object.entries(styles).map(([key, style]) => ({
|
|
161
|
+
'@_Key': key,
|
|
162
|
+
BackColour: style.BackColour,
|
|
163
|
+
TileColour: style.TileColour,
|
|
164
|
+
BorderColour: style.BorderColour,
|
|
165
|
+
FontColour: style.FontColour,
|
|
166
|
+
FontName: style.FontName,
|
|
167
|
+
FontSize: style.FontSize?.toString(),
|
|
168
|
+
}));
|
|
169
|
+
const stylesData = {
|
|
170
|
+
StyleData: {
|
|
171
|
+
'@_xmlns:xsi': 'http://www.w3.org/2001/XMLSchema-instance',
|
|
172
|
+
Styles: {
|
|
173
|
+
Style: styleArray,
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
};
|
|
177
|
+
return builder.build(stylesData);
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Create a custom category style
|
|
181
|
+
* @param categoryName - Name of the category
|
|
182
|
+
* @param backgroundColor - Background color in hex format
|
|
183
|
+
* @param fontColor - Font color in hex format (default: white)
|
|
184
|
+
* @returns Grid3Style object
|
|
185
|
+
*/
|
|
186
|
+
function createCategoryStyle(categoryName, backgroundColor, fontColor = '#FFFFFFFF') {
|
|
187
|
+
return {
|
|
188
|
+
BackColour: ensureAlphaChannel(backgroundColor),
|
|
189
|
+
TileColour: ensureAlphaChannel(backgroundColor),
|
|
190
|
+
BorderColour: ensureAlphaChannel(darkenColor(backgroundColor, 30)),
|
|
191
|
+
FontColour: ensureAlphaChannel(fontColor),
|
|
192
|
+
FontName: 'Arial',
|
|
193
|
+
FontSize: '16',
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Darken a hex color by a given amount
|
|
198
|
+
* @param hexColor - Hex color string
|
|
199
|
+
* @param amount - Amount to darken (0-255)
|
|
200
|
+
* @returns Darkened hex color
|
|
201
|
+
*/
|
|
202
|
+
function darkenColor(hexColor, amount) {
|
|
203
|
+
const normalized = ensureAlphaChannel(hexColor);
|
|
204
|
+
const hex = normalized.slice(1, 7); // Extract RGB part (skip # and alpha)
|
|
205
|
+
const num = parseInt(hex, 16);
|
|
206
|
+
const clamp = (value) => Math.max(0, Math.min(255, value));
|
|
207
|
+
const r = clamp(((num >> 16) & 0xff) - amount);
|
|
208
|
+
const g = clamp(((num >> 8) & 0xff) - amount);
|
|
209
|
+
const b = clamp((num & 0xff) - amount);
|
|
210
|
+
return `#${((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1)}`;
|
|
211
|
+
}
|
|
@@ -49,7 +49,7 @@ function createWordlist(input) {
|
|
|
49
49
|
}
|
|
50
50
|
else if (typeof input === 'object') {
|
|
51
51
|
// Handle dictionary/object input
|
|
52
|
-
items = Object.entries(input).map(([
|
|
52
|
+
items = Object.entries(input).map(([, value]) => {
|
|
53
53
|
if (typeof value === 'string') {
|
|
54
54
|
return { text: value };
|
|
55
55
|
}
|
|
@@ -222,7 +222,8 @@ function updateWordlist(gridsetBuffer, gridName, wordlist) {
|
|
|
222
222
|
found = true;
|
|
223
223
|
}
|
|
224
224
|
catch (error) {
|
|
225
|
-
|
|
225
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
226
|
+
throw new Error(`Failed to update wordlist in grid "${gridName}": ${message}`);
|
|
226
227
|
}
|
|
227
228
|
}
|
|
228
229
|
}
|