@willwade/aac-processors 0.1.0 → 0.1.1
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/dist/browser/processors/gridset/password.js +12 -6
- package/dist/browser/processors/gridset/symbols.js +62 -19
- package/dist/browser/validation/gridsetValidator.js +3 -2
- package/dist/processors/gridset/password.js +12 -9
- package/dist/processors/gridset/symbols.js +63 -46
- package/dist/validation/gridsetValidator.js +3 -2
- package/docs/PAGESET_GETTING_STARTED.md +185 -0
- package/examples/vitedemo/QUICKSTART.md +1 -0
- package/examples/vitedemo/index.html +160 -5
- package/examples/vitedemo/src/main.ts +367 -15
- package/package.json +2 -1
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
|
|
1
|
+
function getExtension(source) {
|
|
2
|
+
const index = source.lastIndexOf('.');
|
|
3
|
+
if (index === -1)
|
|
4
|
+
return '';
|
|
5
|
+
return source.slice(index);
|
|
6
|
+
}
|
|
2
7
|
/**
|
|
3
8
|
* Resolve the password to use for Grid3 archives.
|
|
4
9
|
* Preference order:
|
|
@@ -8,17 +13,18 @@ import path from 'path';
|
|
|
8
13
|
export function resolveGridsetPassword(options, source) {
|
|
9
14
|
if (options?.gridsetPassword)
|
|
10
15
|
return options.gridsetPassword;
|
|
11
|
-
|
|
12
|
-
|
|
16
|
+
const envPassword = typeof process !== 'undefined' ? process.env?.GRIDSET_PASSWORD : undefined;
|
|
17
|
+
if (envPassword)
|
|
18
|
+
return envPassword;
|
|
13
19
|
if (typeof source === 'string') {
|
|
14
|
-
const ext =
|
|
20
|
+
const ext = getExtension(source).toLowerCase();
|
|
15
21
|
if (ext === '.gridsetx')
|
|
16
|
-
return
|
|
22
|
+
return envPassword;
|
|
17
23
|
}
|
|
18
24
|
return undefined;
|
|
19
25
|
}
|
|
20
26
|
export function resolveGridsetPasswordFromEnv() {
|
|
21
|
-
return process.env
|
|
27
|
+
return typeof process !== 'undefined' ? process.env?.GRIDSET_PASSWORD : undefined;
|
|
22
28
|
}
|
|
23
29
|
export function getZipEntriesWithPassword(zip, password) {
|
|
24
30
|
const entries = [];
|
|
@@ -12,9 +12,7 @@
|
|
|
12
12
|
*
|
|
13
13
|
* This module provides symbol resolution and metadata extraction.
|
|
14
14
|
*/
|
|
15
|
-
import
|
|
16
|
-
import * as path from 'path';
|
|
17
|
-
import AdmZip from 'adm-zip';
|
|
15
|
+
import { getFs, getPath } from '../../utils/io';
|
|
18
16
|
/**
|
|
19
17
|
* Default Grid 3 installation paths by platform
|
|
20
18
|
*/
|
|
@@ -57,6 +55,37 @@ export const SYMBOL_LIBRARIES = {
|
|
|
57
55
|
* Default locale to use
|
|
58
56
|
*/
|
|
59
57
|
export const DEFAULT_LOCALE = 'en-GB';
|
|
58
|
+
function getNodeFs() {
|
|
59
|
+
try {
|
|
60
|
+
return getFs();
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
throw new Error('Symbol library access is not available in this environment.');
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
function getNodePath() {
|
|
67
|
+
try {
|
|
68
|
+
return getPath();
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new Error('Path utilities are not available in this environment.');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
let cachedAdmZip = null;
|
|
75
|
+
function getAdmZip() {
|
|
76
|
+
if (cachedAdmZip)
|
|
77
|
+
return cachedAdmZip;
|
|
78
|
+
try {
|
|
79
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
80
|
+
const module = require('adm-zip');
|
|
81
|
+
const resolved = module.default || module;
|
|
82
|
+
cachedAdmZip = resolved;
|
|
83
|
+
return resolved;
|
|
84
|
+
}
|
|
85
|
+
catch {
|
|
86
|
+
throw new Error('Symbol library access requires AdmZip in this environment.');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
60
89
|
/**
|
|
61
90
|
* Parse a symbol reference string
|
|
62
91
|
* @param reference - Symbol reference like "[widgit]/food/apple.png"
|
|
@@ -95,24 +124,30 @@ export function isSymbolReference(reference) {
|
|
|
95
124
|
* @returns Default Grid 3 path or empty string if not found
|
|
96
125
|
*/
|
|
97
126
|
export function getDefaultGrid3Path() {
|
|
98
|
-
const platform = process.platform;
|
|
127
|
+
const platform = (typeof process !== 'undefined' && process.platform ? process.platform : 'unknown');
|
|
99
128
|
const defaultPath = DEFAULT_GRID3_PATHS[platform] || '';
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
129
|
+
try {
|
|
130
|
+
const fs = getNodeFs();
|
|
131
|
+
if (defaultPath && fs.existsSync(defaultPath)) {
|
|
132
|
+
return defaultPath;
|
|
133
|
+
}
|
|
134
|
+
// Try to find Grid 3 in common locations
|
|
135
|
+
const commonPaths = [
|
|
136
|
+
'C:\\Program Files (x86)\\Smartbox\\Grid 3',
|
|
137
|
+
'C:\\Program Files\\Smartbox\\Grid 3',
|
|
138
|
+
'C:\\Program Files\\Smartbox\\Grid 3',
|
|
139
|
+
'/Applications/Grid 3.app',
|
|
140
|
+
'/opt/smartbox/grid3',
|
|
141
|
+
];
|
|
142
|
+
for (const testPath of commonPaths) {
|
|
143
|
+
if (fs.existsSync(testPath)) {
|
|
144
|
+
return testPath;
|
|
145
|
+
}
|
|
114
146
|
}
|
|
115
147
|
}
|
|
148
|
+
catch {
|
|
149
|
+
return '';
|
|
150
|
+
}
|
|
116
151
|
return '';
|
|
117
152
|
}
|
|
118
153
|
/**
|
|
@@ -122,6 +157,7 @@ export function getDefaultGrid3Path() {
|
|
|
122
157
|
* @returns Path to Symbol Libraries directory (e.g., "C:\...\Grid 3\Resources\Symbols")
|
|
123
158
|
*/
|
|
124
159
|
export function getSymbolLibrariesDir(grid3Path) {
|
|
160
|
+
const path = getNodePath();
|
|
125
161
|
return path.join(grid3Path, SYMBOLS_SUBDIR);
|
|
126
162
|
}
|
|
127
163
|
/**
|
|
@@ -132,6 +168,7 @@ export function getSymbolLibrariesDir(grid3Path) {
|
|
|
132
168
|
* @returns Path to symbol search indexes directory (e.g., "C:\...\Grid 3\Locale\en-GB\symbolsearch")
|
|
133
169
|
*/
|
|
134
170
|
export function getSymbolSearchIndexesDir(grid3Path, locale = DEFAULT_LOCALE) {
|
|
171
|
+
const path = getNodePath();
|
|
135
172
|
return path.join(grid3Path, SYMBOLSEARCH_SUBDIR, locale, 'symbolsearch');
|
|
136
173
|
}
|
|
137
174
|
/**
|
|
@@ -145,6 +182,7 @@ export function getAvailableSymbolLibraries(options = {}) {
|
|
|
145
182
|
return [];
|
|
146
183
|
}
|
|
147
184
|
const symbolsDir = getSymbolLibrariesDir(grid3Path);
|
|
185
|
+
const fs = getNodeFs();
|
|
148
186
|
if (!fs.existsSync(symbolsDir)) {
|
|
149
187
|
return [];
|
|
150
188
|
}
|
|
@@ -152,6 +190,7 @@ export function getAvailableSymbolLibraries(options = {}) {
|
|
|
152
190
|
const files = fs.readdirSync(symbolsDir);
|
|
153
191
|
for (const file of files) {
|
|
154
192
|
if (file.endsWith('.symbols')) {
|
|
193
|
+
const path = getNodePath();
|
|
155
194
|
const fullPath = path.join(symbolsDir, file);
|
|
156
195
|
const stats = fs.statSync(fullPath);
|
|
157
196
|
const libraryName = path.basename(file, '.symbols');
|
|
@@ -186,7 +225,9 @@ export function getSymbolLibraryInfo(libraryName, options = {}) {
|
|
|
186
225
|
libraryName + '.symbols',
|
|
187
226
|
];
|
|
188
227
|
for (const file of variations) {
|
|
228
|
+
const path = getNodePath();
|
|
189
229
|
const fullPath = path.join(symbolsDir, file);
|
|
230
|
+
const fs = getNodeFs();
|
|
190
231
|
if (fs.existsSync(fullPath)) {
|
|
191
232
|
const stats = fs.statSync(fullPath);
|
|
192
233
|
return {
|
|
@@ -233,6 +274,7 @@ export function resolveSymbolReference(reference, options = {}) {
|
|
|
233
274
|
}
|
|
234
275
|
try {
|
|
235
276
|
// .symbols files are ZIP archives
|
|
277
|
+
const AdmZip = getAdmZip();
|
|
236
278
|
const zip = new AdmZip(libraryInfo.pixFile);
|
|
237
279
|
// The path in the symbol reference becomes the path within the symbols/ folder
|
|
238
280
|
// e.g., [tawasl]/above bw.png becomes symbols/above bw.png
|
|
@@ -398,7 +440,8 @@ export function analyzeSymbolUsage(tree) {
|
|
|
398
440
|
*/
|
|
399
441
|
export function symbolReferenceToFilename(reference, cellX, cellY) {
|
|
400
442
|
const parsed = parseSymbolReference(reference);
|
|
401
|
-
const
|
|
443
|
+
const dotIndex = parsed.path.lastIndexOf('.');
|
|
444
|
+
const ext = dotIndex >= 0 ? parsed.path.slice(dotIndex) : '.png';
|
|
402
445
|
// Grid 3 format: {x}-{y}-0-text-0.{ext}
|
|
403
446
|
return `${cellX}-${cellY}-0-text-0${ext}`;
|
|
404
447
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/require-await */
|
|
2
2
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
3
3
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
4
|
-
import * as fs from 'fs';
|
|
5
|
-
import * as path from 'path';
|
|
6
4
|
import * as xml2js from 'xml2js';
|
|
7
5
|
import JSZip from 'jszip';
|
|
8
6
|
import { BaseValidator } from './baseValidator';
|
|
7
|
+
import { getFs, getPath } from '../utils/io';
|
|
9
8
|
/**
|
|
10
9
|
* Validator for Grid3/Smartbox Gridset files (.gridset, .gridsetx)
|
|
11
10
|
*/
|
|
@@ -18,6 +17,8 @@ export class GridsetValidator extends BaseValidator {
|
|
|
18
17
|
*/
|
|
19
18
|
static async validateFile(filePath) {
|
|
20
19
|
const validator = new GridsetValidator();
|
|
20
|
+
const fs = getFs();
|
|
21
|
+
const path = getPath();
|
|
21
22
|
const content = fs.readFileSync(filePath);
|
|
22
23
|
const stats = fs.statSync(filePath);
|
|
23
24
|
return validator.validate(content, path.basename(filePath), stats.size);
|
|
@@ -1,12 +1,14 @@
|
|
|
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.resolveGridsetPassword = resolveGridsetPassword;
|
|
7
4
|
exports.resolveGridsetPasswordFromEnv = resolveGridsetPasswordFromEnv;
|
|
8
5
|
exports.getZipEntriesWithPassword = getZipEntriesWithPassword;
|
|
9
|
-
|
|
6
|
+
function getExtension(source) {
|
|
7
|
+
const index = source.lastIndexOf('.');
|
|
8
|
+
if (index === -1)
|
|
9
|
+
return '';
|
|
10
|
+
return source.slice(index);
|
|
11
|
+
}
|
|
10
12
|
/**
|
|
11
13
|
* Resolve the password to use for Grid3 archives.
|
|
12
14
|
* Preference order:
|
|
@@ -16,17 +18,18 @@ const path_1 = __importDefault(require("path"));
|
|
|
16
18
|
function resolveGridsetPassword(options, source) {
|
|
17
19
|
if (options?.gridsetPassword)
|
|
18
20
|
return options.gridsetPassword;
|
|
19
|
-
|
|
20
|
-
|
|
21
|
+
const envPassword = typeof process !== 'undefined' ? process.env?.GRIDSET_PASSWORD : undefined;
|
|
22
|
+
if (envPassword)
|
|
23
|
+
return envPassword;
|
|
21
24
|
if (typeof source === 'string') {
|
|
22
|
-
const ext =
|
|
25
|
+
const ext = getExtension(source).toLowerCase();
|
|
23
26
|
if (ext === '.gridsetx')
|
|
24
|
-
return
|
|
27
|
+
return envPassword;
|
|
25
28
|
}
|
|
26
29
|
return undefined;
|
|
27
30
|
}
|
|
28
31
|
function resolveGridsetPasswordFromEnv() {
|
|
29
|
-
return process.env
|
|
32
|
+
return typeof process !== 'undefined' ? process.env?.GRIDSET_PASSWORD : undefined;
|
|
30
33
|
}
|
|
31
34
|
function getZipEntriesWithPassword(zip, password) {
|
|
32
35
|
const entries = [];
|
|
@@ -13,32 +13,6 @@
|
|
|
13
13
|
*
|
|
14
14
|
* This module provides symbol resolution and metadata extraction.
|
|
15
15
|
*/
|
|
16
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
17
|
-
if (k2 === undefined) k2 = k;
|
|
18
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
19
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
20
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
21
|
-
}
|
|
22
|
-
Object.defineProperty(o, k2, desc);
|
|
23
|
-
}) : (function(o, m, k, k2) {
|
|
24
|
-
if (k2 === undefined) k2 = k;
|
|
25
|
-
o[k2] = m[k];
|
|
26
|
-
}));
|
|
27
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
28
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
29
|
-
}) : function(o, v) {
|
|
30
|
-
o["default"] = v;
|
|
31
|
-
});
|
|
32
|
-
var __importStar = (this && this.__importStar) || function (mod) {
|
|
33
|
-
if (mod && mod.__esModule) return mod;
|
|
34
|
-
var result = {};
|
|
35
|
-
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
|
|
36
|
-
__setModuleDefault(result, mod);
|
|
37
|
-
return result;
|
|
38
|
-
};
|
|
39
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
40
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
41
|
-
};
|
|
42
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
43
17
|
exports.DEFAULT_LOCALE = exports.SYMBOL_LIBRARIES = void 0;
|
|
44
18
|
exports.parseSymbolReference = parseSymbolReference;
|
|
@@ -59,9 +33,7 @@ exports.analyzeSymbolUsage = analyzeSymbolUsage;
|
|
|
59
33
|
exports.symbolReferenceToFilename = symbolReferenceToFilename;
|
|
60
34
|
exports.getSymbolsDir = getSymbolsDir;
|
|
61
35
|
exports.getSymbolSearchDir = getSymbolSearchDir;
|
|
62
|
-
const
|
|
63
|
-
const path = __importStar(require("path"));
|
|
64
|
-
const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
36
|
+
const io_1 = require("../../utils/io");
|
|
65
37
|
/**
|
|
66
38
|
* Default Grid 3 installation paths by platform
|
|
67
39
|
*/
|
|
@@ -104,6 +76,37 @@ exports.SYMBOL_LIBRARIES = {
|
|
|
104
76
|
* Default locale to use
|
|
105
77
|
*/
|
|
106
78
|
exports.DEFAULT_LOCALE = 'en-GB';
|
|
79
|
+
function getNodeFs() {
|
|
80
|
+
try {
|
|
81
|
+
return (0, io_1.getFs)();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
throw new Error('Symbol library access is not available in this environment.');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
function getNodePath() {
|
|
88
|
+
try {
|
|
89
|
+
return (0, io_1.getPath)();
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
throw new Error('Path utilities are not available in this environment.');
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
let cachedAdmZip = null;
|
|
96
|
+
function getAdmZip() {
|
|
97
|
+
if (cachedAdmZip)
|
|
98
|
+
return cachedAdmZip;
|
|
99
|
+
try {
|
|
100
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
101
|
+
const module = require('adm-zip');
|
|
102
|
+
const resolved = module.default || module;
|
|
103
|
+
cachedAdmZip = resolved;
|
|
104
|
+
return resolved;
|
|
105
|
+
}
|
|
106
|
+
catch {
|
|
107
|
+
throw new Error('Symbol library access requires AdmZip in this environment.');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
107
110
|
/**
|
|
108
111
|
* Parse a symbol reference string
|
|
109
112
|
* @param reference - Symbol reference like "[widgit]/food/apple.png"
|
|
@@ -142,23 +145,29 @@ function isSymbolReference(reference) {
|
|
|
142
145
|
* @returns Default Grid 3 path or empty string if not found
|
|
143
146
|
*/
|
|
144
147
|
function getDefaultGrid3Path() {
|
|
145
|
-
const platform = process.platform;
|
|
148
|
+
const platform = (typeof process !== 'undefined' && process.platform ? process.platform : 'unknown');
|
|
146
149
|
const defaultPath = DEFAULT_GRID3_PATHS[platform] || '';
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
const commonPaths = [
|
|
152
|
-
'C:\\Program Files (x86)\\Smartbox\\Grid 3',
|
|
153
|
-
'C:\\Program Files\\Smartbox\\Grid 3',
|
|
154
|
-
'C:\\Program Files\\Smartbox\\Grid 3',
|
|
155
|
-
'/Applications/Grid 3.app',
|
|
156
|
-
'/opt/smartbox/grid3',
|
|
157
|
-
];
|
|
158
|
-
for (const testPath of commonPaths) {
|
|
159
|
-
if (fs.existsSync(testPath)) {
|
|
160
|
-
return testPath;
|
|
150
|
+
try {
|
|
151
|
+
const fs = getNodeFs();
|
|
152
|
+
if (defaultPath && fs.existsSync(defaultPath)) {
|
|
153
|
+
return defaultPath;
|
|
161
154
|
}
|
|
155
|
+
// Try to find Grid 3 in common locations
|
|
156
|
+
const commonPaths = [
|
|
157
|
+
'C:\\Program Files (x86)\\Smartbox\\Grid 3',
|
|
158
|
+
'C:\\Program Files\\Smartbox\\Grid 3',
|
|
159
|
+
'C:\\Program Files\\Smartbox\\Grid 3',
|
|
160
|
+
'/Applications/Grid 3.app',
|
|
161
|
+
'/opt/smartbox/grid3',
|
|
162
|
+
];
|
|
163
|
+
for (const testPath of commonPaths) {
|
|
164
|
+
if (fs.existsSync(testPath)) {
|
|
165
|
+
return testPath;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
catch {
|
|
170
|
+
return '';
|
|
162
171
|
}
|
|
163
172
|
return '';
|
|
164
173
|
}
|
|
@@ -169,6 +178,7 @@ function getDefaultGrid3Path() {
|
|
|
169
178
|
* @returns Path to Symbol Libraries directory (e.g., "C:\...\Grid 3\Resources\Symbols")
|
|
170
179
|
*/
|
|
171
180
|
function getSymbolLibrariesDir(grid3Path) {
|
|
181
|
+
const path = getNodePath();
|
|
172
182
|
return path.join(grid3Path, SYMBOLS_SUBDIR);
|
|
173
183
|
}
|
|
174
184
|
/**
|
|
@@ -179,6 +189,7 @@ function getSymbolLibrariesDir(grid3Path) {
|
|
|
179
189
|
* @returns Path to symbol search indexes directory (e.g., "C:\...\Grid 3\Locale\en-GB\symbolsearch")
|
|
180
190
|
*/
|
|
181
191
|
function getSymbolSearchIndexesDir(grid3Path, locale = exports.DEFAULT_LOCALE) {
|
|
192
|
+
const path = getNodePath();
|
|
182
193
|
return path.join(grid3Path, SYMBOLSEARCH_SUBDIR, locale, 'symbolsearch');
|
|
183
194
|
}
|
|
184
195
|
/**
|
|
@@ -192,6 +203,7 @@ function getAvailableSymbolLibraries(options = {}) {
|
|
|
192
203
|
return [];
|
|
193
204
|
}
|
|
194
205
|
const symbolsDir = getSymbolLibrariesDir(grid3Path);
|
|
206
|
+
const fs = getNodeFs();
|
|
195
207
|
if (!fs.existsSync(symbolsDir)) {
|
|
196
208
|
return [];
|
|
197
209
|
}
|
|
@@ -199,6 +211,7 @@ function getAvailableSymbolLibraries(options = {}) {
|
|
|
199
211
|
const files = fs.readdirSync(symbolsDir);
|
|
200
212
|
for (const file of files) {
|
|
201
213
|
if (file.endsWith('.symbols')) {
|
|
214
|
+
const path = getNodePath();
|
|
202
215
|
const fullPath = path.join(symbolsDir, file);
|
|
203
216
|
const stats = fs.statSync(fullPath);
|
|
204
217
|
const libraryName = path.basename(file, '.symbols');
|
|
@@ -233,7 +246,9 @@ function getSymbolLibraryInfo(libraryName, options = {}) {
|
|
|
233
246
|
libraryName + '.symbols',
|
|
234
247
|
];
|
|
235
248
|
for (const file of variations) {
|
|
249
|
+
const path = getNodePath();
|
|
236
250
|
const fullPath = path.join(symbolsDir, file);
|
|
251
|
+
const fs = getNodeFs();
|
|
237
252
|
if (fs.existsSync(fullPath)) {
|
|
238
253
|
const stats = fs.statSync(fullPath);
|
|
239
254
|
return {
|
|
@@ -280,7 +295,8 @@ function resolveSymbolReference(reference, options = {}) {
|
|
|
280
295
|
}
|
|
281
296
|
try {
|
|
282
297
|
// .symbols files are ZIP archives
|
|
283
|
-
const
|
|
298
|
+
const AdmZip = getAdmZip();
|
|
299
|
+
const zip = new AdmZip(libraryInfo.pixFile);
|
|
284
300
|
// The path in the symbol reference becomes the path within the symbols/ folder
|
|
285
301
|
// e.g., [tawasl]/above bw.png becomes symbols/above bw.png
|
|
286
302
|
const symbolPath = `symbols/${parsed.path}`;
|
|
@@ -445,7 +461,8 @@ function analyzeSymbolUsage(tree) {
|
|
|
445
461
|
*/
|
|
446
462
|
function symbolReferenceToFilename(reference, cellX, cellY) {
|
|
447
463
|
const parsed = parseSymbolReference(reference);
|
|
448
|
-
const
|
|
464
|
+
const dotIndex = parsed.path.lastIndexOf('.');
|
|
465
|
+
const ext = dotIndex >= 0 ? parsed.path.slice(dotIndex) : '.png';
|
|
449
466
|
// Grid 3 format: {x}-{y}-0-text-0.{ext}
|
|
450
467
|
return `${cellX}-${cellY}-0-text-0${ext}`;
|
|
451
468
|
}
|
|
@@ -30,11 +30,10 @@ exports.GridsetValidator = void 0;
|
|
|
30
30
|
/* eslint-disable @typescript-eslint/require-await */
|
|
31
31
|
/* eslint-disable @typescript-eslint/no-unsafe-argument */
|
|
32
32
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
33
|
-
const fs = __importStar(require("fs"));
|
|
34
|
-
const path = __importStar(require("path"));
|
|
35
33
|
const xml2js = __importStar(require("xml2js"));
|
|
36
34
|
const jszip_1 = __importDefault(require("jszip"));
|
|
37
35
|
const baseValidator_1 = require("./baseValidator");
|
|
36
|
+
const io_1 = require("../utils/io");
|
|
38
37
|
/**
|
|
39
38
|
* Validator for Grid3/Smartbox Gridset files (.gridset, .gridsetx)
|
|
40
39
|
*/
|
|
@@ -47,6 +46,8 @@ class GridsetValidator extends baseValidator_1.BaseValidator {
|
|
|
47
46
|
*/
|
|
48
47
|
static async validateFile(filePath) {
|
|
49
48
|
const validator = new GridsetValidator();
|
|
49
|
+
const fs = (0, io_1.getFs)();
|
|
50
|
+
const path = (0, io_1.getPath)();
|
|
50
51
|
const content = fs.readFileSync(filePath);
|
|
51
52
|
const stats = fs.statSync(filePath);
|
|
52
53
|
return validator.validate(content, path.basename(filePath), stats.size);
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# AAC Pageset Quickstart (Node + Browser)
|
|
2
|
+
|
|
3
|
+
This guide shows two simple ways to generate or convert AAC pagesets using `aac-processors`:
|
|
4
|
+
|
|
5
|
+
- Node.js: full conversion between formats (read + write)
|
|
6
|
+
- Browser: generate or export to OBF/OBZ in-memory (downloadable)
|
|
7
|
+
|
|
8
|
+
If you need lossless conversion in the browser, use a Node/worker service for the save step (file I/O is required for most formats).
|
|
9
|
+
|
|
10
|
+
## Node.js: Convert and Generate Pagesets
|
|
11
|
+
|
|
12
|
+
### Install
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
npm install aac-processors
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### Convert a Gridset to OBF
|
|
19
|
+
|
|
20
|
+
```ts
|
|
21
|
+
import { getProcessor, ObfProcessor } from 'aac-processors';
|
|
22
|
+
|
|
23
|
+
async function convertGridsetToObf() {
|
|
24
|
+
const sourcePath = './input/example.gridset';
|
|
25
|
+
const targetPath = './output/example.obf';
|
|
26
|
+
|
|
27
|
+
const sourceProcessor = getProcessor(sourcePath); // GridsetProcessor
|
|
28
|
+
const tree = await sourceProcessor.loadIntoTree(sourcePath);
|
|
29
|
+
|
|
30
|
+
const obf = new ObfProcessor();
|
|
31
|
+
await obf.saveFromTree(tree, targetPath);
|
|
32
|
+
|
|
33
|
+
console.log('Saved:', targetPath);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
convertGridsetToObf().catch(console.error);
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
### Generate a Simple Pageset and Save as OBZ
|
|
40
|
+
|
|
41
|
+
```ts
|
|
42
|
+
import { AACTree, AACPage, AACButton, ObfProcessor } from 'aac-processors';
|
|
43
|
+
|
|
44
|
+
async function generateObz() {
|
|
45
|
+
const tree = new AACTree();
|
|
46
|
+
tree.metadata = { name: 'Starter Demo', locale: 'en' };
|
|
47
|
+
|
|
48
|
+
const hello = new AACButton({ id: 'hello', label: 'Hello', message: 'Hello' });
|
|
49
|
+
const thanks = new AACButton({ id: 'thanks', label: 'Thanks', message: 'Thank you' });
|
|
50
|
+
|
|
51
|
+
const home = new AACPage({
|
|
52
|
+
id: 'home',
|
|
53
|
+
name: 'Home',
|
|
54
|
+
buttons: [hello, thanks],
|
|
55
|
+
grid: [[hello, thanks]],
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
tree.addPage(home);
|
|
59
|
+
tree.rootId = 'home';
|
|
60
|
+
|
|
61
|
+
const obf = new ObfProcessor();
|
|
62
|
+
await obf.saveFromTree(tree, './output/starter.obz');
|
|
63
|
+
|
|
64
|
+
console.log('Saved: ./output/starter.obz');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
generateObz().catch(console.error);
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Browser: Generate or Convert to OBF/OBZ
|
|
71
|
+
|
|
72
|
+
In the browser you can still parse files and build an `AACTree`, but most processors cannot write to disk (no `fs`).
|
|
73
|
+
The example below generates an `AACTree`, converts it to OBF JSON in-memory, and downloads it.
|
|
74
|
+
|
|
75
|
+
If you need full conversions in a browser app, do the save step in Node (server or worker).
|
|
76
|
+
|
|
77
|
+
### Generate a Pageset and Download as OBF
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
import { AACTree, AACPage, AACButton, ObfProcessor } from 'aac-processors';
|
|
81
|
+
|
|
82
|
+
function downloadBlob(data: BlobPart, filename: string, type: string) {
|
|
83
|
+
const blob = new Blob([data], { type });
|
|
84
|
+
const url = URL.createObjectURL(blob);
|
|
85
|
+
const a = document.createElement('a');
|
|
86
|
+
a.href = url;
|
|
87
|
+
a.download = filename;
|
|
88
|
+
a.click();
|
|
89
|
+
URL.revokeObjectURL(url);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
function buildSampleTree() {
|
|
93
|
+
const tree = new AACTree();
|
|
94
|
+
tree.metadata = { name: 'Browser Demo', locale: 'en' };
|
|
95
|
+
|
|
96
|
+
const hello = new AACButton({ id: 'hello', label: 'Hello', message: 'Hello' });
|
|
97
|
+
const yes = new AACButton({ id: 'yes', label: 'Yes', message: 'Yes' });
|
|
98
|
+
|
|
99
|
+
const home = new AACPage({
|
|
100
|
+
id: 'home',
|
|
101
|
+
name: 'Home',
|
|
102
|
+
buttons: [hello, yes],
|
|
103
|
+
grid: [[hello, yes]],
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
tree.addPage(home);
|
|
107
|
+
tree.rootId = 'home';
|
|
108
|
+
return tree;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
async function exportObf(tree: AACTree) {
|
|
112
|
+
// This mirrors the browser demo approach: create OBF JSON and download.
|
|
113
|
+
// ObfProcessor.saveFromTree writes to disk, so we build a board in-memory.
|
|
114
|
+
const obf = new ObfProcessor() as ObfProcessor & {
|
|
115
|
+
createObfBoardFromPage?: (page: AACPage, fallbackName: string, metadata?: AACTree['metadata']) => any;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const rootPage = tree.rootId ? tree.getPage(tree.rootId) : Object.values(tree.pages)[0];
|
|
119
|
+
const board = obf.createObfBoardFromPage
|
|
120
|
+
? obf.createObfBoardFromPage(rootPage!, 'Board', tree.metadata)
|
|
121
|
+
: {
|
|
122
|
+
format: 'open-board-0.1',
|
|
123
|
+
id: rootPage?.id ?? 'board',
|
|
124
|
+
name: rootPage?.name ?? 'Board',
|
|
125
|
+
locale: tree.metadata?.locale || 'en',
|
|
126
|
+
grid: { rows: 1, columns: rootPage?.buttons.length ?? 0, order: [] },
|
|
127
|
+
buttons: (rootPage?.buttons || []).map((button) => ({
|
|
128
|
+
id: button.id,
|
|
129
|
+
label: button.label,
|
|
130
|
+
vocalization: button.message || button.label,
|
|
131
|
+
})),
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
const json = JSON.stringify(board, null, 2);
|
|
135
|
+
downloadBlob(json, 'browser-demo.obf', 'application/json');
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const tree = buildSampleTree();
|
|
139
|
+
exportObf(tree);
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Convert an Uploaded Pageset to OBZ (Browser)
|
|
143
|
+
|
|
144
|
+
This uses the same idea as the Vite demo: parse any supported file into a tree,
|
|
145
|
+
then export OBF/OBZ in-memory and download.
|
|
146
|
+
|
|
147
|
+
```ts
|
|
148
|
+
import { getProcessor, ObfProcessor, type AACTree, type AACPage } from 'aac-processors';
|
|
149
|
+
import JSZip from 'jszip';
|
|
150
|
+
|
|
151
|
+
async function convertToObz(file: File) {
|
|
152
|
+
const extension = '.' + file.name.split('.').pop();
|
|
153
|
+
const processor = getProcessor(extension);
|
|
154
|
+
const buffer = await file.arrayBuffer();
|
|
155
|
+
const tree = await processor.loadIntoTree(buffer);
|
|
156
|
+
|
|
157
|
+
const obf = new ObfProcessor() as ObfProcessor & {
|
|
158
|
+
createObfBoardFromPage?: (page: AACPage, fallbackName: string, metadata?: AACTree['metadata']) => any;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
const zip = new JSZip();
|
|
162
|
+
Object.values(tree.pages).forEach((page) => {
|
|
163
|
+
const board = obf.createObfBoardFromPage
|
|
164
|
+
? obf.createObfBoardFromPage(page, 'Board', tree.metadata)
|
|
165
|
+
: { format: 'open-board-0.1', id: page.id, name: page.name, grid: { rows: 0, columns: 0, order: [] }, buttons: [] };
|
|
166
|
+
zip.file(`${page.id}.obf`, JSON.stringify(board, null, 2));
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
const data = await zip.generateAsync({ type: 'uint8array' });
|
|
170
|
+
const blob = new Blob([data], { type: 'application/zip' });
|
|
171
|
+
const url = URL.createObjectURL(blob);
|
|
172
|
+
const a = document.createElement('a');
|
|
173
|
+
a.href = url;
|
|
174
|
+
a.download = 'converted.obz';
|
|
175
|
+
a.click();
|
|
176
|
+
URL.revokeObjectURL(url);
|
|
177
|
+
}
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
## Tips
|
|
181
|
+
|
|
182
|
+
- Use `loadIntoTree()` to normalize different AAC formats into one structure.
|
|
183
|
+
- In Node, `saveFromTree()` lets you write to OBF, OBZ, Gridset, etc.
|
|
184
|
+
- In the browser, build the output in memory and offer it for download.
|
|
185
|
+
- For full-fidelity conversions in the browser, use a server-side endpoint to save files.
|
|
@@ -40,6 +40,7 @@ The `test-files/` folder contains example AAC files you can use:
|
|
|
40
40
|
- **Navigation**: Click NAVIGATE buttons to jump between pages
|
|
41
41
|
- **Stats**: See page/button/text counts and load time
|
|
42
42
|
- **Logs**: Watch the processing log in real-time
|
|
43
|
+
- **Pageset Lab**: Open the "Create & Convert" tab to generate a sample pageset or convert an upload to OBF/OBZ
|
|
43
44
|
|
|
44
45
|
## 🛠️ Development
|
|
45
46
|
|
|
@@ -63,6 +63,39 @@
|
|
|
63
63
|
border-bottom: 2px solid #667eea;
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
.tab-header {
|
|
67
|
+
display: flex;
|
|
68
|
+
gap: 8px;
|
|
69
|
+
margin-bottom: 15px;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.tab-btn {
|
|
73
|
+
flex: 1;
|
|
74
|
+
border: 1px solid #d6daf5;
|
|
75
|
+
background: #f7f8ff;
|
|
76
|
+
color: #3d4bb8;
|
|
77
|
+
padding: 8px 12px;
|
|
78
|
+
border-radius: 8px;
|
|
79
|
+
font-size: 13px;
|
|
80
|
+
font-weight: 600;
|
|
81
|
+
cursor: pointer;
|
|
82
|
+
transition: all 0.2s;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.tab-btn.active {
|
|
86
|
+
background: #667eea;
|
|
87
|
+
color: #fff;
|
|
88
|
+
border-color: #667eea;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.tab-content {
|
|
92
|
+
display: none;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
.tab-content.active {
|
|
96
|
+
display: block;
|
|
97
|
+
}
|
|
98
|
+
|
|
66
99
|
.upload-area {
|
|
67
100
|
border: 2px dashed #ccc;
|
|
68
101
|
border-radius: 8px;
|
|
@@ -290,6 +323,83 @@
|
|
|
290
323
|
font-size: 13px;
|
|
291
324
|
}
|
|
292
325
|
|
|
326
|
+
.demo-section {
|
|
327
|
+
background: #f8f9ff;
|
|
328
|
+
border-radius: 10px;
|
|
329
|
+
padding: 15px;
|
|
330
|
+
border: 1px solid #e4e7ff;
|
|
331
|
+
margin-bottom: 15px;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
.section-title {
|
|
335
|
+
font-size: 14px;
|
|
336
|
+
font-weight: 700;
|
|
337
|
+
color: #3942a3;
|
|
338
|
+
margin-bottom: 10px;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.field {
|
|
342
|
+
display: flex;
|
|
343
|
+
flex-direction: column;
|
|
344
|
+
gap: 6px;
|
|
345
|
+
margin-bottom: 10px;
|
|
346
|
+
font-size: 12px;
|
|
347
|
+
color: #4d4d4d;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
.field select,
|
|
351
|
+
.field input {
|
|
352
|
+
border: 1px solid #d7d9f5;
|
|
353
|
+
border-radius: 6px;
|
|
354
|
+
padding: 8px 10px;
|
|
355
|
+
font-size: 13px;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
.action-row {
|
|
359
|
+
display: flex;
|
|
360
|
+
gap: 10px;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
.action-row .btn {
|
|
364
|
+
margin-bottom: 0;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
.hint {
|
|
368
|
+
font-size: 12px;
|
|
369
|
+
color: #667;
|
|
370
|
+
margin-top: 8px;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
.status-pill {
|
|
374
|
+
margin-top: 10px;
|
|
375
|
+
font-size: 12px;
|
|
376
|
+
padding: 8px 10px;
|
|
377
|
+
border-radius: 8px;
|
|
378
|
+
background: #eef0ff;
|
|
379
|
+
color: #4a4a8a;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
.status-pill.success {
|
|
383
|
+
background: #e0f4e7;
|
|
384
|
+
color: #256d3b;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
.status-pill.warn {
|
|
388
|
+
background: #fff4d6;
|
|
389
|
+
color: #7a5a00;
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
.code-preview {
|
|
393
|
+
background: #0f172a;
|
|
394
|
+
color: #d7e3ff;
|
|
395
|
+
padding: 12px;
|
|
396
|
+
border-radius: 8px;
|
|
397
|
+
font-size: 12px;
|
|
398
|
+
max-height: 220px;
|
|
399
|
+
overflow: auto;
|
|
400
|
+
white-space: pre-wrap;
|
|
401
|
+
}
|
|
402
|
+
|
|
293
403
|
@media (max-width: 1024px) {
|
|
294
404
|
.main-grid {
|
|
295
405
|
grid-template-columns: 1fr;
|
|
@@ -361,11 +471,56 @@
|
|
|
361
471
|
|
|
362
472
|
<!-- Right Panel: Results -->
|
|
363
473
|
<div class="panel results-panel">
|
|
364
|
-
<div class="panel-title">📊
|
|
365
|
-
<div
|
|
366
|
-
<
|
|
367
|
-
|
|
368
|
-
|
|
474
|
+
<div class="panel-title">📊 AAC Pageset Lab</div>
|
|
475
|
+
<div class="tab-header">
|
|
476
|
+
<button class="tab-btn active" data-tab="inspectTab">Inspect</button>
|
|
477
|
+
<button class="tab-btn" data-tab="pagesetTab">Create & Convert</button>
|
|
478
|
+
</div>
|
|
479
|
+
<div class="tab-content active" id="inspectTab">
|
|
480
|
+
<div id="results">
|
|
481
|
+
<p style="color: #999; text-align: center; padding: 40px;">
|
|
482
|
+
Load a file to see its contents here
|
|
483
|
+
</p>
|
|
484
|
+
</div>
|
|
485
|
+
</div>
|
|
486
|
+
<div class="tab-content" id="pagesetTab">
|
|
487
|
+
<div class="demo-section">
|
|
488
|
+
<div class="section-title">✨ Create a Sample Pageset</div>
|
|
489
|
+
<div class="field">
|
|
490
|
+
<label for="templateSelect">Template</label>
|
|
491
|
+
<select id="templateSelect">
|
|
492
|
+
<option value="starter">Starter 2x2 + Feelings</option>
|
|
493
|
+
<option value="home">Home & Core 3x3</option>
|
|
494
|
+
</select>
|
|
495
|
+
</div>
|
|
496
|
+
<div class="field">
|
|
497
|
+
<label for="formatSelect">Output format</label>
|
|
498
|
+
<select id="formatSelect">
|
|
499
|
+
<option value="obf">OBF (.obf)</option>
|
|
500
|
+
<option value="obz">OBZ (.obz)</option>
|
|
501
|
+
</select>
|
|
502
|
+
</div>
|
|
503
|
+
<div class="action-row">
|
|
504
|
+
<button class="btn" id="createPagesetBtn">Generate & Download</button>
|
|
505
|
+
<button class="btn btn-secondary" id="previewPagesetBtn">Preview in Viewer</button>
|
|
506
|
+
</div>
|
|
507
|
+
<div class="hint">Creates a demo AACTree, then exports to OBF/OBZ.</div>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
<div class="demo-section">
|
|
511
|
+
<div class="section-title">🔁 Convert Loaded Pageset</div>
|
|
512
|
+
<div class="action-row">
|
|
513
|
+
<button class="btn" id="convertToObfBtn" disabled>Download .obf</button>
|
|
514
|
+
<button class="btn btn-secondary" id="convertToObzBtn" disabled>Download .obz</button>
|
|
515
|
+
</div>
|
|
516
|
+
<div class="hint">Upload and process a file first, then export it in another format.</div>
|
|
517
|
+
<div class="status-pill" id="conversionStatus">No pageset loaded yet.</div>
|
|
518
|
+
</div>
|
|
519
|
+
|
|
520
|
+
<div class="demo-section">
|
|
521
|
+
<div class="section-title">📄 Export Preview</div>
|
|
522
|
+
<pre class="code-preview" id="pagesetOutput">Generate or convert a pageset to preview the output JSON.</pre>
|
|
523
|
+
</div>
|
|
369
524
|
</div>
|
|
370
525
|
</div>
|
|
371
526
|
</div>
|
|
@@ -74,9 +74,9 @@ import {
|
|
|
74
74
|
GridsetProcessor,
|
|
75
75
|
ApplePanelsProcessor,
|
|
76
76
|
AstericsGridProcessor,
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
AACTree,
|
|
78
|
+
AACPage,
|
|
79
|
+
AACButton
|
|
80
80
|
} from 'aac-processors';
|
|
81
81
|
|
|
82
82
|
// UI Elements
|
|
@@ -93,11 +93,38 @@ const results = document.getElementById('results') as HTMLElement;
|
|
|
93
93
|
const logPanel = document.getElementById('logPanel') as HTMLElement;
|
|
94
94
|
const testResults = document.getElementById('testResults') as HTMLElement;
|
|
95
95
|
const testList = document.getElementById('testList') as HTMLElement;
|
|
96
|
+
const tabButtons = document.querySelectorAll('.tab-btn') as NodeListOf<HTMLButtonElement>;
|
|
97
|
+
const inspectTab = document.getElementById('inspectTab') as HTMLElement;
|
|
98
|
+
const pagesetTab = document.getElementById('pagesetTab') as HTMLElement;
|
|
99
|
+
const templateSelect = document.getElementById('templateSelect') as HTMLSelectElement;
|
|
100
|
+
const formatSelect = document.getElementById('formatSelect') as HTMLSelectElement;
|
|
101
|
+
const createPagesetBtn = document.getElementById('createPagesetBtn') as HTMLButtonElement;
|
|
102
|
+
const previewPagesetBtn = document.getElementById('previewPagesetBtn') as HTMLButtonElement;
|
|
103
|
+
const convertToObfBtn = document.getElementById('convertToObfBtn') as HTMLButtonElement;
|
|
104
|
+
const convertToObzBtn = document.getElementById('convertToObzBtn') as HTMLButtonElement;
|
|
105
|
+
const conversionStatus = document.getElementById('conversionStatus') as HTMLElement;
|
|
106
|
+
const pagesetOutput = document.getElementById('pagesetOutput') as HTMLElement;
|
|
96
107
|
|
|
97
108
|
// State
|
|
98
109
|
let currentFile: File | null = null;
|
|
99
110
|
let currentProcessor: any = null;
|
|
100
111
|
let currentTree: AACTree | null = null;
|
|
112
|
+
let currentSourceLabel = 'pageset';
|
|
113
|
+
|
|
114
|
+
// Tabs
|
|
115
|
+
function setActiveTab(tabId: string) {
|
|
116
|
+
tabButtons.forEach((btn) => {
|
|
117
|
+
btn.classList.toggle('active', btn.dataset.tab === tabId);
|
|
118
|
+
});
|
|
119
|
+
inspectTab.classList.toggle('active', tabId === 'inspectTab');
|
|
120
|
+
pagesetTab.classList.toggle('active', tabId === 'pagesetTab');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
tabButtons.forEach((btn) => {
|
|
124
|
+
btn.addEventListener('click', () => {
|
|
125
|
+
setActiveTab(btn.dataset.tab || 'inspectTab');
|
|
126
|
+
});
|
|
127
|
+
});
|
|
101
128
|
|
|
102
129
|
// Logging
|
|
103
130
|
function log(message: string, type: 'info' | 'success' | 'error' | 'warn' = 'info') {
|
|
@@ -109,6 +136,52 @@ function log(message: string, type: 'info' | 'success' | 'error' | 'warn' = 'inf
|
|
|
109
136
|
console.log(`[${type.toUpperCase()}]`, message);
|
|
110
137
|
}
|
|
111
138
|
|
|
139
|
+
function setConversionStatus(message: string, state: 'success' | 'warn' | 'info' = 'info') {
|
|
140
|
+
conversionStatus.textContent = message;
|
|
141
|
+
conversionStatus.classList.remove('success', 'warn');
|
|
142
|
+
if (state !== 'info') {
|
|
143
|
+
conversionStatus.classList.add(state);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function updateConvertButtons() {
|
|
148
|
+
const hasTree = !!currentTree;
|
|
149
|
+
convertToObfBtn.disabled = !hasTree;
|
|
150
|
+
convertToObzBtn.disabled = !hasTree;
|
|
151
|
+
if (!hasTree) {
|
|
152
|
+
setConversionStatus('No pageset loaded yet.', 'info');
|
|
153
|
+
} else {
|
|
154
|
+
setConversionStatus(`Ready to export: ${currentSourceLabel}`, 'success');
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function updateStatsForTree(tree: AACTree, textCount?: number, loadTimeMs?: number) {
|
|
159
|
+
const pageCount = Object.keys(tree.pages).length;
|
|
160
|
+
const buttonCount = Object.values(tree.pages).reduce(
|
|
161
|
+
(sum: number, page: AACPage) => sum + page.buttons.length,
|
|
162
|
+
0
|
|
163
|
+
);
|
|
164
|
+
|
|
165
|
+
document.getElementById('pageCount')!.textContent = pageCount.toString();
|
|
166
|
+
document.getElementById('buttonCount')!.textContent = buttonCount.toString();
|
|
167
|
+
document.getElementById('textCount')!.textContent = (textCount ?? 0).toString();
|
|
168
|
+
document.getElementById('loadTime')!.textContent =
|
|
169
|
+
loadTimeMs !== undefined ? `${loadTimeMs.toFixed(0)}ms` : '—';
|
|
170
|
+
stats.style.display = 'grid';
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function collectTextCount(tree: AACTree): number {
|
|
174
|
+
const texts = new Set<string>();
|
|
175
|
+
Object.values(tree.pages).forEach((page) => {
|
|
176
|
+
if (page.name) texts.add(page.name);
|
|
177
|
+
page.buttons.forEach((button) => {
|
|
178
|
+
if (button.label) texts.add(button.label);
|
|
179
|
+
if (button.message) texts.add(button.message);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
return texts.size;
|
|
183
|
+
}
|
|
184
|
+
|
|
112
185
|
// Get file extension
|
|
113
186
|
function getFileExtension(filename: string): string {
|
|
114
187
|
const match = filename.toLowerCase().match(/\.\w+$/);
|
|
@@ -146,6 +219,7 @@ function handleFile(file: File) {
|
|
|
146
219
|
fileDetails.textContent = `${file.name} • ${formatFileSize(file.size)}`;
|
|
147
220
|
fileInfo.style.display = 'block';
|
|
148
221
|
processBtn.disabled = false;
|
|
222
|
+
currentSourceLabel = file.name;
|
|
149
223
|
|
|
150
224
|
log(`Using processor: ${currentProcessor.constructor.name}`, 'success');
|
|
151
225
|
} catch (error) {
|
|
@@ -213,22 +287,13 @@ processBtn.addEventListener('click', async () => {
|
|
|
213
287
|
log(`Extracted ${texts.length} texts`, 'success');
|
|
214
288
|
|
|
215
289
|
// Update stats
|
|
216
|
-
|
|
217
|
-
const buttonCount = Object.values(currentTree.pages).reduce(
|
|
218
|
-
(sum: number, page: AACPage) => sum + page.buttons.length,
|
|
219
|
-
0
|
|
220
|
-
);
|
|
221
|
-
|
|
222
|
-
document.getElementById('pageCount')!.textContent = pageCount.toString();
|
|
223
|
-
document.getElementById('buttonCount')!.textContent = buttonCount.toString();
|
|
224
|
-
document.getElementById('textCount')!.textContent = texts.length.toString();
|
|
225
|
-
document.getElementById('loadTime')!.textContent = `${loadTime.toFixed(0)}ms`;
|
|
226
|
-
stats.style.display = 'grid';
|
|
290
|
+
updateStatsForTree(currentTree, texts.length, loadTime);
|
|
227
291
|
|
|
228
292
|
// Display results
|
|
229
293
|
displayResults(currentTree);
|
|
294
|
+
updateConvertButtons();
|
|
230
295
|
|
|
231
|
-
log(`✅ Successfully processed ${
|
|
296
|
+
log(`✅ Successfully processed ${Object.keys(currentTree.pages).length} pages`, 'success');
|
|
232
297
|
} catch (error) {
|
|
233
298
|
const errorMsg = (error as Error).message;
|
|
234
299
|
log(`❌ Error: ${errorMsg}`, 'error');
|
|
@@ -343,12 +408,299 @@ clearBtn.addEventListener('click', () => {
|
|
|
343
408
|
currentFile = null;
|
|
344
409
|
currentProcessor = null;
|
|
345
410
|
currentTree = null;
|
|
411
|
+
currentSourceLabel = 'pageset';
|
|
346
412
|
fileInput.value = '';
|
|
347
413
|
fileInfo.style.display = 'none';
|
|
348
414
|
stats.style.display = 'none';
|
|
349
415
|
results.innerHTML = '<p style="color: #999; text-align: center; padding: 40px;">Load a file to see its contents here</p>';
|
|
350
416
|
testResults.style.display = 'none';
|
|
351
417
|
logPanel.innerHTML = '<div class="log-entry log-info">Cleared. Ready to process files...</div>';
|
|
418
|
+
pagesetOutput.textContent = 'Generate or convert a pageset to preview the output JSON.';
|
|
419
|
+
updateConvertButtons();
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
function sanitizeFilename(name: string): string {
|
|
423
|
+
return name
|
|
424
|
+
.toLowerCase()
|
|
425
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
426
|
+
.replace(/(^-|-$)/g, '') || 'pageset';
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function buildSampleTree(template: string): AACTree {
|
|
430
|
+
const tree = new AACTree();
|
|
431
|
+
tree.metadata = {
|
|
432
|
+
name: template === 'home' ? 'Home & Core Demo' : 'Starter Demo',
|
|
433
|
+
description: 'Generated in the AAC Processors browser demo',
|
|
434
|
+
locale: 'en',
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
if (template === 'home') {
|
|
438
|
+
const hello = new AACButton({ id: 'hello', label: 'Hello', message: 'Hello', action: { type: 'SPEAK' } });
|
|
439
|
+
const want = new AACButton({ id: 'want', label: 'I want', message: 'I want', action: { type: 'SPEAK' } });
|
|
440
|
+
const help = new AACButton({ id: 'help', label: 'Help', message: 'Help', action: { type: 'SPEAK' } });
|
|
441
|
+
const more = new AACButton({
|
|
442
|
+
id: 'more',
|
|
443
|
+
label: 'More',
|
|
444
|
+
targetPageId: 'core',
|
|
445
|
+
action: { type: 'NAVIGATE', targetPageId: 'core' },
|
|
446
|
+
});
|
|
447
|
+
const yes = new AACButton({ id: 'yes', label: 'Yes', message: 'Yes', action: { type: 'SPEAK' } });
|
|
448
|
+
const no = new AACButton({ id: 'no', label: 'No', message: 'No', action: { type: 'SPEAK' } });
|
|
449
|
+
const stop = new AACButton({ id: 'stop', label: 'Stop', message: 'Stop', action: { type: 'SPEAK' } });
|
|
450
|
+
const go = new AACButton({ id: 'go', label: 'Go', message: 'Go', action: { type: 'SPEAK' } });
|
|
451
|
+
const food = new AACButton({
|
|
452
|
+
id: 'food',
|
|
453
|
+
label: 'Food',
|
|
454
|
+
targetPageId: 'food',
|
|
455
|
+
action: { type: 'NAVIGATE', targetPageId: 'food' },
|
|
456
|
+
});
|
|
457
|
+
|
|
458
|
+
const homePage = new AACPage({
|
|
459
|
+
id: 'home',
|
|
460
|
+
name: 'Home',
|
|
461
|
+
buttons: [hello, want, help, more, yes, no, stop, go, food],
|
|
462
|
+
grid: [
|
|
463
|
+
[hello, want, help],
|
|
464
|
+
[more, yes, no],
|
|
465
|
+
[stop, go, food],
|
|
466
|
+
],
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
const hungry = new AACButton({ id: 'hungry', label: 'Hungry', message: 'I am hungry', action: { type: 'SPEAK' } });
|
|
470
|
+
const drink = new AACButton({ id: 'drink', label: 'Drink', message: 'I want a drink', action: { type: 'SPEAK' } });
|
|
471
|
+
const snack = new AACButton({ id: 'snack', label: 'Snack', message: 'Snack', action: { type: 'SPEAK' } });
|
|
472
|
+
const backFood = new AACButton({
|
|
473
|
+
id: 'back-food',
|
|
474
|
+
label: 'Back',
|
|
475
|
+
targetPageId: 'home',
|
|
476
|
+
action: { type: 'NAVIGATE', targetPageId: 'home' },
|
|
477
|
+
});
|
|
478
|
+
|
|
479
|
+
const foodPage = new AACPage({
|
|
480
|
+
id: 'food',
|
|
481
|
+
name: 'Food',
|
|
482
|
+
buttons: [hungry, drink, snack, backFood],
|
|
483
|
+
grid: [
|
|
484
|
+
[hungry, drink],
|
|
485
|
+
[snack, backFood],
|
|
486
|
+
],
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
const coreYes = new AACButton({ id: 'core-yes', label: 'Yes', message: 'Yes', action: { type: 'SPEAK' } });
|
|
490
|
+
const coreNo = new AACButton({ id: 'core-no', label: 'No', message: 'No', action: { type: 'SPEAK' } });
|
|
491
|
+
const coreStop = new AACButton({ id: 'core-stop', label: 'Stop', message: 'Stop', action: { type: 'SPEAK' } });
|
|
492
|
+
const coreGo = new AACButton({ id: 'core-go', label: 'Go', message: 'Go', action: { type: 'SPEAK' } });
|
|
493
|
+
const backCore = new AACButton({
|
|
494
|
+
id: 'back-core',
|
|
495
|
+
label: 'Back',
|
|
496
|
+
targetPageId: 'home',
|
|
497
|
+
action: { type: 'NAVIGATE', targetPageId: 'home' },
|
|
498
|
+
});
|
|
499
|
+
|
|
500
|
+
const corePage = new AACPage({
|
|
501
|
+
id: 'core',
|
|
502
|
+
name: 'Core Words',
|
|
503
|
+
buttons: [coreYes, coreNo, coreStop, coreGo, backCore],
|
|
504
|
+
grid: [
|
|
505
|
+
[coreYes, coreNo],
|
|
506
|
+
[coreStop, coreGo],
|
|
507
|
+
[backCore, null],
|
|
508
|
+
],
|
|
509
|
+
});
|
|
510
|
+
|
|
511
|
+
tree.addPage(homePage);
|
|
512
|
+
tree.addPage(corePage);
|
|
513
|
+
tree.addPage(foodPage);
|
|
514
|
+
tree.rootId = 'home';
|
|
515
|
+
return tree;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const hello = new AACButton({ id: 'hello', label: 'Hello', message: 'Hello', action: { type: 'SPEAK' } });
|
|
519
|
+
const thanks = new AACButton({ id: 'thanks', label: 'Thanks', message: 'Thank you', action: { type: 'SPEAK' } });
|
|
520
|
+
const yes = new AACButton({ id: 'yes', label: 'Yes', message: 'Yes', action: { type: 'SPEAK' } });
|
|
521
|
+
const more = new AACButton({
|
|
522
|
+
id: 'more',
|
|
523
|
+
label: 'Feelings',
|
|
524
|
+
targetPageId: 'feelings',
|
|
525
|
+
action: { type: 'NAVIGATE', targetPageId: 'feelings' },
|
|
526
|
+
});
|
|
527
|
+
|
|
528
|
+
const homePage = new AACPage({
|
|
529
|
+
id: 'home',
|
|
530
|
+
name: 'Starter',
|
|
531
|
+
buttons: [hello, thanks, yes, more],
|
|
532
|
+
grid: [
|
|
533
|
+
[hello, thanks],
|
|
534
|
+
[yes, more],
|
|
535
|
+
],
|
|
536
|
+
});
|
|
537
|
+
|
|
538
|
+
const happy = new AACButton({ id: 'happy', label: 'Happy', message: 'I feel happy', action: { type: 'SPEAK' } });
|
|
539
|
+
const sad = new AACButton({ id: 'sad', label: 'Sad', message: 'I feel sad', action: { type: 'SPEAK' } });
|
|
540
|
+
const back = new AACButton({
|
|
541
|
+
id: 'back',
|
|
542
|
+
label: 'Back',
|
|
543
|
+
targetPageId: 'home',
|
|
544
|
+
action: { type: 'NAVIGATE', targetPageId: 'home' },
|
|
545
|
+
});
|
|
546
|
+
|
|
547
|
+
const feelingsPage = new AACPage({
|
|
548
|
+
id: 'feelings',
|
|
549
|
+
name: 'Feelings',
|
|
550
|
+
buttons: [happy, sad, back],
|
|
551
|
+
grid: [
|
|
552
|
+
[happy, sad],
|
|
553
|
+
[back, null],
|
|
554
|
+
],
|
|
555
|
+
});
|
|
556
|
+
|
|
557
|
+
tree.addPage(homePage);
|
|
558
|
+
tree.addPage(feelingsPage);
|
|
559
|
+
tree.rootId = 'home';
|
|
560
|
+
return tree;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
function buildFallbackObfBoard(page: AACPage, metadata?: AACTree['metadata']) {
|
|
564
|
+
const rows = page.grid.length || 1;
|
|
565
|
+
const columns = page.grid.reduce((max, row) => Math.max(max, row.length), 0) || page.buttons.length;
|
|
566
|
+
const order: (string | null)[][] = [];
|
|
567
|
+
const positions = new Map<string, number>();
|
|
568
|
+
|
|
569
|
+
if (page.grid.length) {
|
|
570
|
+
page.grid.forEach((row, rowIndex) => {
|
|
571
|
+
const orderRow: (string | null)[] = [];
|
|
572
|
+
for (let colIndex = 0; colIndex < columns; colIndex++) {
|
|
573
|
+
const cell = row[colIndex] || null;
|
|
574
|
+
if (cell) {
|
|
575
|
+
const id = String(cell.id ?? '');
|
|
576
|
+
orderRow.push(id);
|
|
577
|
+
positions.set(id, rowIndex * columns + colIndex);
|
|
578
|
+
} else {
|
|
579
|
+
orderRow.push(null);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
order.push(orderRow);
|
|
583
|
+
});
|
|
584
|
+
} else {
|
|
585
|
+
const fallbackRow = page.buttons.map((button, index) => {
|
|
586
|
+
const id = String(button.id ?? '');
|
|
587
|
+
positions.set(id, index);
|
|
588
|
+
return id;
|
|
589
|
+
});
|
|
590
|
+
order.push(fallbackRow);
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
return {
|
|
594
|
+
format: 'open-board-0.1',
|
|
595
|
+
id: page.id,
|
|
596
|
+
locale: metadata?.locale || page.locale || 'en',
|
|
597
|
+
name: page.name || metadata?.name || 'Board',
|
|
598
|
+
description_html: page.descriptionHtml || metadata?.description || '',
|
|
599
|
+
grid: { rows, columns, order },
|
|
600
|
+
buttons: page.buttons.map((button) => ({
|
|
601
|
+
id: button.id,
|
|
602
|
+
label: button.label,
|
|
603
|
+
vocalization: button.message || button.label,
|
|
604
|
+
load_board: button.targetPageId ? { path: button.targetPageId } : undefined,
|
|
605
|
+
box_id: positions.get(String(button.id ?? '')),
|
|
606
|
+
background_color: button.style?.backgroundColor,
|
|
607
|
+
border_color: button.style?.borderColor,
|
|
608
|
+
})),
|
|
609
|
+
};
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
async function buildObfExport(tree: AACTree, format: 'obf' | 'obz') {
|
|
613
|
+
const obfProcessor = new ObfProcessor();
|
|
614
|
+
const obfInternal = obfProcessor as ObfProcessor & {
|
|
615
|
+
createObfBoardFromPage?: (page: AACPage, fallbackName: string, metadata?: AACTree['metadata']) => any;
|
|
616
|
+
};
|
|
617
|
+
|
|
618
|
+
const boards = Object.values(tree.pages).map((page) => ({
|
|
619
|
+
pageId: page.id,
|
|
620
|
+
board: obfInternal.createObfBoardFromPage
|
|
621
|
+
? obfInternal.createObfBoardFromPage(page, 'Board', tree.metadata)
|
|
622
|
+
: buildFallbackObfBoard(page, tree.metadata),
|
|
623
|
+
}));
|
|
624
|
+
|
|
625
|
+
if (format === 'obf') {
|
|
626
|
+
const rootPage = tree.rootId ? tree.getPage(tree.rootId) : Object.values(tree.pages)[0];
|
|
627
|
+
const board =
|
|
628
|
+
boards.find((entry) => entry.pageId === rootPage?.id)?.board ?? boards[0]?.board ?? {};
|
|
629
|
+
const json = JSON.stringify(board, null, 2);
|
|
630
|
+
return { filename: `${sanitizeFilename(tree.metadata?.name || 'pageset')}.obf`, data: json };
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
const module = await import('jszip');
|
|
634
|
+
const JSZip = module.default || module;
|
|
635
|
+
const zip = new JSZip();
|
|
636
|
+
boards.forEach((entry) => {
|
|
637
|
+
zip.file(`${entry.pageId}.obf`, JSON.stringify(entry.board, null, 2));
|
|
638
|
+
});
|
|
639
|
+
const zipData = await zip.generateAsync({ type: 'uint8array' });
|
|
640
|
+
return { filename: `${sanitizeFilename(tree.metadata?.name || 'pageset')}.obz`, data: zipData };
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
function triggerDownload(data: Uint8Array | string, filename: string, mime: string) {
|
|
644
|
+
const blob = new Blob([data], { type: mime });
|
|
645
|
+
const url = URL.createObjectURL(blob);
|
|
646
|
+
const a = document.createElement('a');
|
|
647
|
+
a.href = url;
|
|
648
|
+
a.download = filename;
|
|
649
|
+
a.click();
|
|
650
|
+
URL.revokeObjectURL(url);
|
|
651
|
+
}
|
|
652
|
+
|
|
653
|
+
createPagesetBtn.addEventListener('click', async () => {
|
|
654
|
+
const template = templateSelect.value;
|
|
655
|
+
const format = formatSelect.value === 'obz' ? 'obz' : 'obf';
|
|
656
|
+
const tree = buildSampleTree(template);
|
|
657
|
+
currentTree = tree;
|
|
658
|
+
currentSourceLabel = `${tree.metadata?.name || 'sample pageset'}`;
|
|
659
|
+
updateConvertButtons();
|
|
660
|
+
|
|
661
|
+
const exportData = await buildObfExport(tree, format);
|
|
662
|
+
const isObf = typeof exportData.data === 'string';
|
|
663
|
+
triggerDownload(
|
|
664
|
+
exportData.data,
|
|
665
|
+
exportData.filename,
|
|
666
|
+
isObf ? 'application/json' : 'application/zip'
|
|
667
|
+
);
|
|
668
|
+
|
|
669
|
+
pagesetOutput.textContent = isObf
|
|
670
|
+
? exportData.data
|
|
671
|
+
: `Generated OBZ with ${Object.keys(tree.pages).length} boards.`;
|
|
672
|
+
|
|
673
|
+
log(`Created sample pageset and exported ${exportData.filename}`, 'success');
|
|
674
|
+
setConversionStatus(`Exported ${exportData.filename}`, 'success');
|
|
675
|
+
});
|
|
676
|
+
|
|
677
|
+
previewPagesetBtn.addEventListener('click', () => {
|
|
678
|
+
const tree = buildSampleTree(templateSelect.value);
|
|
679
|
+
currentTree = tree;
|
|
680
|
+
currentSourceLabel = `${tree.metadata?.name || 'sample pageset'}`;
|
|
681
|
+
displayResults(tree);
|
|
682
|
+
updateStatsForTree(tree, collectTextCount(tree));
|
|
683
|
+
updateConvertButtons();
|
|
684
|
+
setActiveTab('inspectTab');
|
|
685
|
+
log('Previewing sample pageset in viewer', 'info');
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
convertToObfBtn.addEventListener('click', async () => {
|
|
689
|
+
if (!currentTree) return;
|
|
690
|
+
const exportData = await buildObfExport(currentTree, 'obf');
|
|
691
|
+
triggerDownload(exportData.data, exportData.filename, 'application/json');
|
|
692
|
+
pagesetOutput.textContent = exportData.data as string;
|
|
693
|
+
log(`Converted ${currentSourceLabel} to ${exportData.filename}`, 'success');
|
|
694
|
+
setConversionStatus(`Exported ${exportData.filename}`, 'success');
|
|
695
|
+
});
|
|
696
|
+
|
|
697
|
+
convertToObzBtn.addEventListener('click', async () => {
|
|
698
|
+
if (!currentTree) return;
|
|
699
|
+
const exportData = await buildObfExport(currentTree, 'obz');
|
|
700
|
+
triggerDownload(exportData.data, exportData.filename, 'application/zip');
|
|
701
|
+
pagesetOutput.textContent = `Generated OBZ with ${Object.keys(currentTree.pages).length} boards.`;
|
|
702
|
+
log(`Converted ${currentSourceLabel} to ${exportData.filename}`, 'success');
|
|
703
|
+
setConversionStatus(`Exported ${exportData.filename}`, 'success');
|
|
352
704
|
});
|
|
353
705
|
|
|
354
706
|
// Run compatibility tests
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@willwade/aac-processors",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.1",
|
|
4
4
|
"description": "A comprehensive TypeScript library for processing AAC (Augmentative and Alternative Communication) file formats with translation support",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"browser": "dist/browser/index.browser.js",
|
|
@@ -93,6 +93,7 @@
|
|
|
93
93
|
"lint:fix": "eslint \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" --fix",
|
|
94
94
|
"format": "prettier --write \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" \"*.{js,ts,json,md}\"",
|
|
95
95
|
"format:check": "prettier --check \"src/**/*.{js,ts}\" \"test/**/*.{js,ts}\" \"*.{js,ts,json,md}\"",
|
|
96
|
+
"smoke:browser": "node scripts/smoke-browser-bundle.js",
|
|
96
97
|
"test": "npm run build && jest",
|
|
97
98
|
"test:watch": "npm run build && jest --watch",
|
|
98
99
|
"test:coverage": "npm run build && jest --coverage",
|