@willwade/aac-processors 0.1.5 → 0.1.7
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 +14 -0
- package/dist/browser/index.browser.js +15 -1
- package/dist/browser/processors/gridset/password.js +11 -0
- package/dist/browser/processors/gridsetProcessor.js +42 -46
- package/dist/browser/processors/obfProcessor.js +47 -63
- package/dist/browser/processors/snapProcessor.js +1031 -0
- package/dist/browser/processors/touchchatProcessor.js +1004 -0
- package/dist/browser/utils/io.js +36 -2
- package/dist/browser/utils/sqlite.js +109 -0
- package/dist/browser/utils/zip.js +54 -0
- package/dist/browser/validation/gridsetValidator.js +7 -27
- package/dist/browser/validation/obfValidator.js +9 -4
- package/dist/browser/validation/snapValidator.js +197 -0
- package/dist/browser/validation/touchChatValidator.js +201 -0
- package/dist/index.browser.d.ts +7 -0
- package/dist/index.browser.js +19 -2
- package/dist/processors/gridset/helpers.js +3 -4
- package/dist/processors/gridset/index.d.ts +1 -1
- package/dist/processors/gridset/index.js +3 -2
- package/dist/processors/gridset/password.d.ts +3 -2
- package/dist/processors/gridset/password.js +12 -0
- package/dist/processors/gridset/wordlistHelpers.js +107 -51
- package/dist/processors/gridsetProcessor.js +40 -44
- package/dist/processors/obfProcessor.js +46 -62
- package/dist/processors/snapProcessor.js +60 -54
- package/dist/processors/touchchatProcessor.js +38 -36
- package/dist/utils/io.d.ts +4 -0
- package/dist/utils/io.js +40 -2
- package/dist/utils/sqlite.d.ts +21 -0
- package/dist/utils/sqlite.js +137 -0
- package/dist/utils/zip.d.ts +7 -0
- package/dist/utils/zip.js +80 -0
- package/dist/validation/applePanelsValidator.js +11 -28
- package/dist/validation/astericsValidator.js +11 -30
- package/dist/validation/dotValidator.js +11 -30
- package/dist/validation/excelValidator.js +5 -6
- package/dist/validation/gridsetValidator.js +29 -26
- package/dist/validation/index.d.ts +2 -1
- package/dist/validation/index.js +9 -32
- package/dist/validation/obfValidator.js +8 -3
- package/dist/validation/obfsetValidator.js +11 -30
- package/dist/validation/opmlValidator.js +11 -30
- package/dist/validation/snapValidator.js +6 -9
- package/dist/validation/touchChatValidator.js +6 -7
- package/docs/BROWSER_USAGE.md +2 -10
- package/examples/README.md +3 -75
- package/examples/vitedemo/README.md +13 -7
- package/examples/vitedemo/index.html +51 -2
- package/examples/vitedemo/package-lock.json +9 -0
- package/examples/vitedemo/package.json +1 -0
- package/examples/vitedemo/src/main.ts +132 -2
- package/examples/vitedemo/src/vite-env.d.ts +1 -0
- package/examples/vitedemo/vite.config.ts +26 -7
- package/package.json +3 -1
- package/examples/browser-test-server.js +0 -81
|
@@ -40,31 +40,7 @@ const resolver_2 = require("./gridset/resolver");
|
|
|
40
40
|
const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
|
|
41
41
|
const symbolAlignment_1 = require("./gridset/symbolAlignment");
|
|
42
42
|
const io_1 = require("../utils/io");
|
|
43
|
-
|
|
44
|
-
async function getJSZip() {
|
|
45
|
-
if (!JSZipModule) {
|
|
46
|
-
try {
|
|
47
|
-
// Try ES module import first (browser/Vite)
|
|
48
|
-
const module = await Promise.resolve().then(() => __importStar(require('jszip')));
|
|
49
|
-
JSZipModule = module.default || module;
|
|
50
|
-
}
|
|
51
|
-
catch (error) {
|
|
52
|
-
// Fall back to CommonJS require (Node.js)
|
|
53
|
-
try {
|
|
54
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
55
|
-
const module = require('jszip');
|
|
56
|
-
JSZipModule = module.default || module;
|
|
57
|
-
}
|
|
58
|
-
catch (err2) {
|
|
59
|
-
throw new Error('Zip handling requires JSZip in this environment.');
|
|
60
|
-
}
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
if (!JSZipModule) {
|
|
64
|
-
throw new Error('Zip handling requires JSZip in this environment.');
|
|
65
|
-
}
|
|
66
|
-
return JSZipModule;
|
|
67
|
-
}
|
|
43
|
+
const zip_1 = require("../utils/zip");
|
|
68
44
|
class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
69
45
|
constructor(options) {
|
|
70
46
|
super(options);
|
|
@@ -437,17 +413,15 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
437
413
|
}
|
|
438
414
|
async loadIntoTree(filePathOrBuffer) {
|
|
439
415
|
const tree = new treeStructure_1.AACTree();
|
|
440
|
-
let
|
|
416
|
+
let zipResult;
|
|
441
417
|
try {
|
|
442
|
-
|
|
443
|
-
const zipInput = (0, io_1.readBinaryFromInput)(filePathOrBuffer);
|
|
444
|
-
zip = await JSZip.loadAsync(zipInput);
|
|
418
|
+
zipResult = await (0, zip_1.openZipFromInput)((0, io_1.readBinaryFromInput)(filePathOrBuffer));
|
|
445
419
|
}
|
|
446
420
|
catch (error) {
|
|
447
421
|
throw new Error(`Invalid ZIP file format: ${error.message}`);
|
|
448
422
|
}
|
|
449
423
|
const password = this.getGridsetPassword(filePathOrBuffer);
|
|
450
|
-
const entries = (0, password_1.
|
|
424
|
+
const entries = (0, password_1.getZipEntriesFromAdapter)(zipResult.zip, password);
|
|
451
425
|
const parser = new fast_xml_parser_1.XMLParser({ ignoreAttributes: false });
|
|
452
426
|
const isEncryptedArchive = typeof filePathOrBuffer === 'string' && filePathOrBuffer.toLowerCase().endsWith('.gridsetx');
|
|
453
427
|
const encryptedContentPassword = this.getGridsetPassword(filePathOrBuffer);
|
|
@@ -458,14 +432,11 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
458
432
|
passwordProtected: !!password,
|
|
459
433
|
};
|
|
460
434
|
const readEntryBuffer = async (entry) => {
|
|
461
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-argument
|
|
462
435
|
const raw = await entry.getData();
|
|
463
436
|
if (!isEncryptedArchive) {
|
|
464
|
-
// eslint-disable-next-line @typescript-eslint/no-unsafe-return
|
|
465
437
|
return raw;
|
|
466
438
|
}
|
|
467
|
-
|
|
468
|
-
return (0, crypto_1.decryptGridsetEntry)(raw, encryptedContentPassword);
|
|
439
|
+
return (0, crypto_1.decryptGridsetEntry)(Buffer.from(raw), encryptedContentPassword);
|
|
469
440
|
};
|
|
470
441
|
// Parse FileMap.xml if present to index dynamic files per grid
|
|
471
442
|
const fileMapIndex = new Map();
|
|
@@ -785,7 +756,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
785
756
|
const gridEntryPath = entry.entryName.replace(/\\/g, '/');
|
|
786
757
|
const baseDir = gridEntryPath.replace(/\/grid\.xml$/, '/');
|
|
787
758
|
const dynamicFiles = fileMapIndex.get(gridEntryPath) || [];
|
|
788
|
-
const resolvedImageEntry = (0, resolver_1.resolveGrid3CellImage)(
|
|
759
|
+
const resolvedImageEntry = (0, resolver_1.resolveGrid3CellImage)(null, {
|
|
789
760
|
baseDir,
|
|
790
761
|
imageName: declaredImageName,
|
|
791
762
|
x: cellX + 1,
|
|
@@ -1636,11 +1607,36 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1636
1607
|
return (0, io_1.readBinaryFromInput)(outputPath);
|
|
1637
1608
|
}
|
|
1638
1609
|
async saveFromTree(tree, outputPath) {
|
|
1639
|
-
const
|
|
1640
|
-
|
|
1610
|
+
const useNodeZip = (0, io_1.isNodeRuntime)();
|
|
1611
|
+
let addText;
|
|
1612
|
+
let addBinary;
|
|
1613
|
+
let finalizeZip;
|
|
1614
|
+
if (useNodeZip) {
|
|
1615
|
+
const AdmZip = (0, io_1.getNodeRequire)()('adm-zip');
|
|
1616
|
+
const zip = new AdmZip();
|
|
1617
|
+
addText = (entryPath, content) => {
|
|
1618
|
+
zip.addFile(entryPath, Buffer.from(content, 'utf8'));
|
|
1619
|
+
};
|
|
1620
|
+
addBinary = (entryPath, content) => {
|
|
1621
|
+
zip.addFile(entryPath, Buffer.from(content));
|
|
1622
|
+
};
|
|
1623
|
+
finalizeZip = () => Promise.resolve(zip.toBuffer());
|
|
1624
|
+
}
|
|
1625
|
+
else {
|
|
1626
|
+
const module = await Promise.resolve().then(() => __importStar(require('jszip')));
|
|
1627
|
+
const JSZip = module.default || module;
|
|
1628
|
+
const zip = new JSZip();
|
|
1629
|
+
addText = (entryPath, content) => {
|
|
1630
|
+
zip.file(entryPath, content, { binary: false });
|
|
1631
|
+
};
|
|
1632
|
+
addBinary = (entryPath, content) => {
|
|
1633
|
+
zip.file(entryPath, content);
|
|
1634
|
+
};
|
|
1635
|
+
finalizeZip = async () => zip.generateAsync({ type: 'uint8array' });
|
|
1636
|
+
}
|
|
1641
1637
|
if (Object.keys(tree.pages).length === 0) {
|
|
1642
1638
|
// Create empty zip for empty tree
|
|
1643
|
-
const zipBuffer = await
|
|
1639
|
+
const zipBuffer = await finalizeZip();
|
|
1644
1640
|
(0, io_1.writeBinaryToPath)(outputPath, zipBuffer);
|
|
1645
1641
|
return;
|
|
1646
1642
|
}
|
|
@@ -1713,7 +1709,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1713
1709
|
suppressEmptyNode: true,
|
|
1714
1710
|
});
|
|
1715
1711
|
const settingsXmlContent = settingsBuilder.build(settingsData);
|
|
1716
|
-
|
|
1712
|
+
addText('Settings0/settings.xml', settingsXmlContent);
|
|
1717
1713
|
// Create Settings0/Styles/style.xml if there are styles
|
|
1718
1714
|
if (uniqueStyles.size > 0) {
|
|
1719
1715
|
const stylesArray = Array.from(uniqueStyles.values()).map(({ id, style }) => {
|
|
@@ -1746,7 +1742,7 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1746
1742
|
indentBy: ' ',
|
|
1747
1743
|
});
|
|
1748
1744
|
const styleXmlContent = styleBuilder.build(styleData);
|
|
1749
|
-
|
|
1745
|
+
addText('Settings0/Styles/styles.xml', styleXmlContent);
|
|
1750
1746
|
}
|
|
1751
1747
|
// Collect grid file paths for FileMap.xml
|
|
1752
1748
|
const gridFilePaths = [];
|
|
@@ -1887,14 +1883,14 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1887
1883
|
// Add to zip in Grids folder with proper Grid3 naming
|
|
1888
1884
|
const gridPath = `Grids/${page.name || page.id}/grid.xml`;
|
|
1889
1885
|
gridFilePaths.push(gridPath);
|
|
1890
|
-
|
|
1886
|
+
addText(gridPath, xmlContent);
|
|
1891
1887
|
});
|
|
1892
1888
|
// Write image files to ZIP
|
|
1893
1889
|
buttonImages.forEach((imgData) => {
|
|
1894
1890
|
if (imgData.imageData && imgData.imageData.length > 0) {
|
|
1895
1891
|
// Create image path in the grid's directory
|
|
1896
1892
|
const imagePath = `Grids/${imgData.pageName}/${imgData.x}-${imgData.y}-0-text-0.${imgData.ext}`;
|
|
1897
|
-
|
|
1893
|
+
addBinary(imagePath, imgData.imageData);
|
|
1898
1894
|
}
|
|
1899
1895
|
});
|
|
1900
1896
|
// Create FileMap.xml to map all grid files with their dynamic image files
|
|
@@ -1933,9 +1929,9 @@ class GridsetProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1933
1929
|
indentBy: ' ',
|
|
1934
1930
|
});
|
|
1935
1931
|
const fileMapXmlContent = fileMapBuilder.build(fileMapData);
|
|
1936
|
-
|
|
1932
|
+
addText('FileMap.xml', fileMapXmlContent);
|
|
1937
1933
|
// Write the zip file
|
|
1938
|
-
const zipBuffer = await
|
|
1934
|
+
const zipBuffer = await finalizeZip();
|
|
1939
1935
|
(0, io_1.writeBinaryToPath)(outputPath, zipBuffer);
|
|
1940
1936
|
}
|
|
1941
1937
|
// Helper method to calculate column definitions based on page layout
|
|
@@ -29,31 +29,7 @@ const treeStructure_1 = require("../core/treeStructure");
|
|
|
29
29
|
const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
|
|
30
30
|
const translationProcessor_1 = require("../utilities/translation/translationProcessor");
|
|
31
31
|
const io_1 = require("../utils/io");
|
|
32
|
-
|
|
33
|
-
async function getJSZipObf() {
|
|
34
|
-
if (!JSZipModuleObf) {
|
|
35
|
-
try {
|
|
36
|
-
// Try ES module import first (browser/Vite)
|
|
37
|
-
const module = await Promise.resolve().then(() => __importStar(require('jszip')));
|
|
38
|
-
JSZipModuleObf = module.default || module;
|
|
39
|
-
}
|
|
40
|
-
catch (error) {
|
|
41
|
-
// Fall back to CommonJS require (Node.js)
|
|
42
|
-
try {
|
|
43
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
44
|
-
const module = require('jszip');
|
|
45
|
-
JSZipModuleObf = module.default || module;
|
|
46
|
-
}
|
|
47
|
-
catch (err2) {
|
|
48
|
-
throw new Error('Zip handling requires JSZip in this environment.');
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
if (!JSZipModuleObf) {
|
|
53
|
-
throw new Error('Zip handling requires JSZip in this environment.');
|
|
54
|
-
}
|
|
55
|
-
return JSZipModuleObf;
|
|
56
|
-
}
|
|
32
|
+
const zip_1 = require("../utils/zip");
|
|
57
33
|
const OBF_FORMAT_VERSION = 'open-board-0.1';
|
|
58
34
|
/**
|
|
59
35
|
* Map OBF hidden value to AAC standard visibility
|
|
@@ -91,10 +67,12 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
91
67
|
].filter(Boolean);
|
|
92
68
|
for (const imagePath of possiblePaths) {
|
|
93
69
|
try {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
70
|
+
const buffer = await this.zipFile.readFile(imagePath);
|
|
71
|
+
if (buffer) {
|
|
72
|
+
if (typeof Buffer !== 'undefined') {
|
|
73
|
+
return Buffer.from(buffer);
|
|
74
|
+
}
|
|
75
|
+
return null;
|
|
98
76
|
}
|
|
99
77
|
}
|
|
100
78
|
catch (err) {
|
|
@@ -128,9 +106,8 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
128
106
|
].filter(Boolean);
|
|
129
107
|
for (const imagePath of possiblePaths) {
|
|
130
108
|
try {
|
|
131
|
-
const
|
|
132
|
-
if (
|
|
133
|
-
const buffer = await file.async('uint8array');
|
|
109
|
+
const buffer = await this.zipFile.readFile(imagePath);
|
|
110
|
+
if (buffer) {
|
|
134
111
|
const contentType = imageData.content_type ||
|
|
135
112
|
this.getMimeTypeFromFilename(imagePath);
|
|
136
113
|
const dataUrl = `data:${contentType};base64,${(0, io_1.encodeBase64)(buffer)}`;
|
|
@@ -433,36 +410,28 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
433
410
|
if (!isLikelyZip(filePathOrBuffer)) {
|
|
434
411
|
throw new Error('Invalid OBF content: not JSON and not ZIP');
|
|
435
412
|
}
|
|
436
|
-
const JSZip = await getJSZipObf();
|
|
437
|
-
let zip;
|
|
438
413
|
try {
|
|
439
|
-
const
|
|
440
|
-
|
|
414
|
+
const zipResult = await (0, zip_1.openZipFromInput)(filePathOrBuffer);
|
|
415
|
+
this.zipFile = zipResult.zip;
|
|
441
416
|
}
|
|
442
417
|
catch (err) {
|
|
443
|
-
console.error('[OBF] Error loading ZIP
|
|
418
|
+
console.error('[OBF] Error loading ZIP:', err);
|
|
444
419
|
throw err;
|
|
445
420
|
}
|
|
446
421
|
// Store the ZIP file reference for image extraction
|
|
447
|
-
this.zipFile = zip;
|
|
448
422
|
this.imageCache.clear(); // Clear cache for new file
|
|
449
423
|
console.log('[OBF] Detected zip archive, extracting .obf files');
|
|
450
424
|
// Collect all .obf entries
|
|
451
|
-
const obfEntries =
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
return;
|
|
455
|
-
if (relativePath.toLowerCase().endsWith('.obf')) {
|
|
456
|
-
obfEntries.push({ name: relativePath, file });
|
|
457
|
-
}
|
|
458
|
-
});
|
|
425
|
+
const obfEntries = this.zipFile
|
|
426
|
+
.listFiles()
|
|
427
|
+
.filter((name) => name.toLowerCase().endsWith('.obf'));
|
|
459
428
|
// Process each .obf entry
|
|
460
|
-
for (const
|
|
429
|
+
for (const entryName of obfEntries) {
|
|
461
430
|
try {
|
|
462
|
-
const content = await
|
|
463
|
-
const boardData = tryParseObfJson(content);
|
|
431
|
+
const content = await this.zipFile.readFile(entryName);
|
|
432
|
+
const boardData = tryParseObfJson((0, io_1.decodeText)(content));
|
|
464
433
|
if (boardData) {
|
|
465
|
-
const page = await this.processBoard(boardData,
|
|
434
|
+
const page = await this.processBoard(boardData, entryName);
|
|
466
435
|
tree.addPage(page);
|
|
467
436
|
// Set metadata if not already set (use first board as reference)
|
|
468
437
|
if (!tree.metadata.format) {
|
|
@@ -479,11 +448,11 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
479
448
|
}
|
|
480
449
|
}
|
|
481
450
|
else {
|
|
482
|
-
console.warn('[OBF] Skipped entry (not valid OBF JSON):',
|
|
451
|
+
console.warn('[OBF] Skipped entry (not valid OBF JSON):', entryName);
|
|
483
452
|
}
|
|
484
453
|
}
|
|
485
454
|
catch (err) {
|
|
486
|
-
console.warn('[OBF] Error processing entry:',
|
|
455
|
+
console.warn('[OBF] Error processing entry:', entryName, err);
|
|
487
456
|
}
|
|
488
457
|
}
|
|
489
458
|
return tree;
|
|
@@ -609,16 +578,31 @@ class ObfProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
609
578
|
}
|
|
610
579
|
else {
|
|
611
580
|
// Save as OBZ (zip with multiple OBF files)
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
581
|
+
if ((0, io_1.isNodeRuntime)()) {
|
|
582
|
+
const AdmZip = (0, io_1.getNodeRequire)()('adm-zip');
|
|
583
|
+
const zip = new AdmZip();
|
|
584
|
+
Object.values(tree.pages).forEach((page) => {
|
|
585
|
+
const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
|
|
586
|
+
const obfContent = JSON.stringify(obfBoard, null, 2);
|
|
587
|
+
zip.addFile(`${page.id}.obf`, Buffer.from(obfContent, 'utf8'));
|
|
588
|
+
});
|
|
589
|
+
const zipBuffer = zip.toBuffer();
|
|
590
|
+
const { writeBinaryToPath } = await Promise.resolve().then(() => __importStar(require('../utils/io')));
|
|
591
|
+
writeBinaryToPath(outputPath, zipBuffer);
|
|
592
|
+
}
|
|
593
|
+
else {
|
|
594
|
+
const module = await Promise.resolve().then(() => __importStar(require('jszip')));
|
|
595
|
+
const JSZip = module.default || module;
|
|
596
|
+
const zip = new JSZip();
|
|
597
|
+
Object.values(tree.pages).forEach((page) => {
|
|
598
|
+
const obfBoard = this.createObfBoardFromPage(page, 'Board', tree.metadata);
|
|
599
|
+
const obfContent = JSON.stringify(obfBoard, null, 2);
|
|
600
|
+
zip.file(`${page.id}.obf`, obfContent);
|
|
601
|
+
});
|
|
602
|
+
const zipBuffer = await zip.generateAsync({ type: 'uint8array' });
|
|
603
|
+
const { writeBinaryToPath } = await Promise.resolve().then(() => __importStar(require('../utils/io')));
|
|
604
|
+
writeBinaryToPath(outputPath, zipBuffer);
|
|
605
|
+
}
|
|
622
606
|
}
|
|
623
607
|
}
|
|
624
608
|
/**
|
|
@@ -1,18 +1,12 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
-
};
|
|
5
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
3
|
exports.SnapProcessor = void 0;
|
|
7
4
|
const baseProcessor_1 = require("../core/baseProcessor");
|
|
8
5
|
const treeStructure_1 = require("../core/treeStructure");
|
|
9
6
|
const idGenerator_1 = require("../utilities/analytics/utils/idGenerator");
|
|
10
|
-
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
11
|
-
const path_1 = __importDefault(require("path"));
|
|
12
|
-
const fs_1 = __importDefault(require("fs"));
|
|
13
|
-
const crypto_1 = __importDefault(require("crypto"));
|
|
14
|
-
const os_1 = __importDefault(require("os"));
|
|
15
7
|
const snapValidator_1 = require("../validation/snapValidator");
|
|
8
|
+
const io_1 = require("../utils/io");
|
|
9
|
+
const sqlite_1 = require("../utils/sqlite");
|
|
16
10
|
/**
|
|
17
11
|
* Map Snap Visible value to AAC standard visibility
|
|
18
12
|
* Snap: 0 = hidden, 1 (or non-zero) = visible
|
|
@@ -56,22 +50,10 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
56
50
|
async loadIntoTree(filePathOrBuffer) {
|
|
57
51
|
await Promise.resolve();
|
|
58
52
|
const tree = new treeStructure_1.AACTree();
|
|
59
|
-
let
|
|
60
|
-
const filePath = typeof filePathOrBuffer !== 'string'
|
|
61
|
-
? (() => {
|
|
62
|
-
tempDir = fs_1.default.mkdtempSync(path_1.default.join(os_1.default.tmpdir(), 'snap-'));
|
|
63
|
-
return path_1.default.join(tempDir, 'input.spb');
|
|
64
|
-
})()
|
|
65
|
-
: filePathOrBuffer;
|
|
66
|
-
if (typeof filePathOrBuffer !== 'string') {
|
|
67
|
-
const buffer = Buffer.isBuffer(filePathOrBuffer)
|
|
68
|
-
? filePathOrBuffer
|
|
69
|
-
: Buffer.from(filePathOrBuffer);
|
|
70
|
-
fs_1.default.writeFileSync(filePath, buffer);
|
|
71
|
-
}
|
|
72
|
-
let db = null;
|
|
53
|
+
let dbResult = null;
|
|
73
54
|
try {
|
|
74
|
-
|
|
55
|
+
dbResult = await (0, sqlite_1.openSqliteDatabase)(filePathOrBuffer, { readonly: true });
|
|
56
|
+
const db = dbResult.db;
|
|
75
57
|
const getTableColumns = (tableName) => {
|
|
76
58
|
try {
|
|
77
59
|
const rows = db.prepare(`PRAGMA table_info(${tableName})`).all();
|
|
@@ -338,10 +320,12 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
338
320
|
${hasCommandSequence ? 'LEFT JOIN CommandSequence cs ON b.Id = cs.ButtonId' : ''}
|
|
339
321
|
WHERE er.PageId = ? ${selectedPageLayoutId ? 'AND ep.PageLayoutId = ?' : ''}
|
|
340
322
|
`;
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
323
|
+
if (selectedPageLayoutId) {
|
|
324
|
+
buttons = db.prepare(buttonQuery).all(pageRow.Id, selectedPageLayoutId);
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
buttons = db.prepare(buttonQuery).all(pageRow.Id);
|
|
328
|
+
}
|
|
345
329
|
}
|
|
346
330
|
catch (err) {
|
|
347
331
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
@@ -606,22 +590,18 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
606
590
|
}
|
|
607
591
|
}
|
|
608
592
|
finally {
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
db.close();
|
|
593
|
+
if (dbResult?.cleanup) {
|
|
594
|
+
dbResult.cleanup();
|
|
612
595
|
}
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
try {
|
|
616
|
-
fs_1.default.rmSync(tempDir, { recursive: true, force: true });
|
|
617
|
-
}
|
|
618
|
-
catch (e) {
|
|
619
|
-
console.warn('Failed to clean up temporary files:', e);
|
|
620
|
-
}
|
|
596
|
+
else if (dbResult?.db) {
|
|
597
|
+
dbResult.db.close();
|
|
621
598
|
}
|
|
622
599
|
}
|
|
623
600
|
}
|
|
624
601
|
async processTexts(filePathOrBuffer, translations, outputPath) {
|
|
602
|
+
if (!(0, io_1.isNodeRuntime)()) {
|
|
603
|
+
throw new Error('processTexts is only supported in Node.js environments for Snap files.');
|
|
604
|
+
}
|
|
625
605
|
// Load the tree, apply translations, and save to new file
|
|
626
606
|
const tree = await this.loadIntoTree(filePathOrBuffer);
|
|
627
607
|
// Apply translations to all text content
|
|
@@ -651,19 +631,26 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
651
631
|
});
|
|
652
632
|
// Save the translated tree and return its content
|
|
653
633
|
await this.saveFromTree(tree, outputPath);
|
|
654
|
-
|
|
634
|
+
const fs = (0, io_1.getFs)();
|
|
635
|
+
return fs.readFileSync(outputPath);
|
|
655
636
|
}
|
|
656
637
|
async saveFromTree(tree, outputPath) {
|
|
638
|
+
if (!(0, io_1.isNodeRuntime)()) {
|
|
639
|
+
throw new Error('saveFromTree is only supported in Node.js environments for Snap files.');
|
|
640
|
+
}
|
|
657
641
|
await Promise.resolve();
|
|
658
|
-
const
|
|
659
|
-
|
|
660
|
-
|
|
642
|
+
const fs = (0, io_1.getFs)();
|
|
643
|
+
const path = (0, io_1.getPath)();
|
|
644
|
+
const outputDir = path.dirname(outputPath);
|
|
645
|
+
if (!fs.existsSync(outputDir)) {
|
|
646
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
661
647
|
}
|
|
662
|
-
if (
|
|
663
|
-
|
|
648
|
+
if (fs.existsSync(outputPath)) {
|
|
649
|
+
fs.unlinkSync(outputPath);
|
|
664
650
|
}
|
|
665
651
|
// Create a new SQLite database for Snap format
|
|
666
|
-
const
|
|
652
|
+
const Database = (0, sqlite_1.requireBetterSqlite3)();
|
|
653
|
+
const db = new Database(outputPath, { readonly: false });
|
|
667
654
|
try {
|
|
668
655
|
// Create basic Snap database schema (simplified)
|
|
669
656
|
db.exec(`
|
|
@@ -852,7 +839,12 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
852
839
|
*/
|
|
853
840
|
async addAudioToButton(dbPath, buttonId, audioData, metadata) {
|
|
854
841
|
await Promise.resolve();
|
|
855
|
-
|
|
842
|
+
if (!(0, io_1.isNodeRuntime)()) {
|
|
843
|
+
throw new Error('addAudioToButton is only supported in Node.js environments.');
|
|
844
|
+
}
|
|
845
|
+
const Database = (0, sqlite_1.requireBetterSqlite3)();
|
|
846
|
+
const crypto = (0, io_1.getNodeRequire)()('crypto');
|
|
847
|
+
const db = new Database(dbPath, { fileMustExist: true });
|
|
856
848
|
try {
|
|
857
849
|
// Ensure PageSetData table exists
|
|
858
850
|
db.exec(`
|
|
@@ -863,7 +855,7 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
863
855
|
);
|
|
864
856
|
`);
|
|
865
857
|
// Generate SHA1 hash for the identifier
|
|
866
|
-
const sha1Hash =
|
|
858
|
+
const sha1Hash = crypto.createHash('sha1').update(audioData).digest('hex');
|
|
867
859
|
const identifier = `SND:${sha1Hash}`;
|
|
868
860
|
// Check if audio with this identifier already exists
|
|
869
861
|
let audioId;
|
|
@@ -894,8 +886,12 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
894
886
|
* Create a copy of the pageset with audio recordings added
|
|
895
887
|
*/
|
|
896
888
|
async createAudioEnhancedPageset(sourceDbPath, targetDbPath, audioMappings) {
|
|
889
|
+
if (!(0, io_1.isNodeRuntime)()) {
|
|
890
|
+
throw new Error('createAudioEnhancedPageset is only supported in Node.js environments.');
|
|
891
|
+
}
|
|
892
|
+
const fs = (0, io_1.getFs)();
|
|
897
893
|
// Copy the source database to target
|
|
898
|
-
|
|
894
|
+
fs.copyFileSync(sourceDbPath, targetDbPath);
|
|
899
895
|
// Add audio recordings to the copy
|
|
900
896
|
for (const [buttonId, audioInfo] of audioMappings.entries()) {
|
|
901
897
|
await this.addAudioToButton(targetDbPath, buttonId, audioInfo.audioData, audioInfo.metadata);
|
|
@@ -905,7 +901,11 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
905
901
|
* Extract buttons from a specific page that need audio recordings
|
|
906
902
|
*/
|
|
907
903
|
extractButtonsForAudio(dbPath, pageUniqueId) {
|
|
908
|
-
|
|
904
|
+
if (!(0, io_1.isNodeRuntime)()) {
|
|
905
|
+
throw new Error('extractButtonsForAudio is only supported in Node.js environments.');
|
|
906
|
+
}
|
|
907
|
+
const Database = (0, sqlite_1.requireBetterSqlite3)();
|
|
908
|
+
const db = new Database(dbPath, { readonly: true });
|
|
909
909
|
try {
|
|
910
910
|
// Find the page by UniqueId
|
|
911
911
|
const page = db.prepare('SELECT * FROM Page WHERE UniqueId = ?').get(pageUniqueId);
|
|
@@ -962,13 +962,19 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
962
962
|
* @returns Array of available PageLayouts with their dimensions
|
|
963
963
|
*/
|
|
964
964
|
getAvailablePageLayouts(filePath) {
|
|
965
|
-
|
|
965
|
+
if (!(0, io_1.isNodeRuntime)()) {
|
|
966
|
+
throw new Error('getAvailablePageLayouts is only supported in Node.js environments.');
|
|
967
|
+
}
|
|
968
|
+
const fs = (0, io_1.getFs)();
|
|
969
|
+
const path = (0, io_1.getPath)();
|
|
970
|
+
const dbPath = typeof filePath === 'string' ? filePath : path.join(process.cwd(), 'temp.spb');
|
|
966
971
|
if (Buffer.isBuffer(filePath)) {
|
|
967
|
-
|
|
972
|
+
fs.writeFileSync(dbPath, filePath);
|
|
968
973
|
}
|
|
969
974
|
let db = null;
|
|
970
975
|
try {
|
|
971
|
-
|
|
976
|
+
const Database = (0, sqlite_1.requireBetterSqlite3)();
|
|
977
|
+
db = new Database(dbPath, { readonly: true });
|
|
972
978
|
// Get unique PageLayouts based on PageLayoutSetting (dimensions)
|
|
973
979
|
const pageLayouts = db
|
|
974
980
|
.prepare(`
|
|
@@ -1014,9 +1020,9 @@ class SnapProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
1014
1020
|
db.close();
|
|
1015
1021
|
}
|
|
1016
1022
|
// Clean up temporary file if created from buffer
|
|
1017
|
-
if (Buffer.isBuffer(filePath) &&
|
|
1023
|
+
if (Buffer.isBuffer(filePath) && fs.existsSync(dbPath)) {
|
|
1018
1024
|
try {
|
|
1019
|
-
|
|
1025
|
+
fs.unlinkSync(dbPath);
|
|
1020
1026
|
}
|
|
1021
1027
|
catch (e) {
|
|
1022
1028
|
console.warn('Failed to clean up temporary file:', e);
|