@willwade/aac-processors 0.0.12 → 0.0.13
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 +44 -41
- package/dist/core/treeStructure.d.ts +35 -2
- package/dist/core/treeStructure.js +18 -3
- package/dist/optional/analytics/history.d.ts +12 -1
- package/dist/optional/analytics/index.d.ts +2 -0
- package/dist/optional/analytics/index.js +6 -1
- package/dist/optional/analytics/metrics/comparison.js +8 -4
- package/dist/optional/analytics/metrics/core.d.ts +9 -0
- package/dist/optional/analytics/metrics/core.js +190 -37
- package/dist/optional/analytics/metrics/effort.d.ts +10 -0
- package/dist/optional/analytics/metrics/effort.js +13 -0
- package/dist/optional/analytics/metrics/obl-types.d.ts +93 -0
- package/dist/optional/analytics/metrics/obl-types.js +7 -0
- package/dist/optional/analytics/metrics/obl.d.ts +40 -0
- package/dist/optional/analytics/metrics/obl.js +287 -0
- package/dist/optional/analytics/metrics/vocabulary.js +6 -4
- package/dist/optional/symbolTools.js +13 -16
- package/dist/processors/astericsGridProcessor.d.ts +15 -0
- package/dist/processors/astericsGridProcessor.js +17 -0
- package/dist/processors/gridset/helpers.d.ts +4 -1
- package/dist/processors/gridset/helpers.js +4 -0
- package/dist/processors/gridset/pluginTypes.js +51 -50
- package/dist/processors/gridset/symbolExtractor.js +3 -2
- package/dist/processors/gridset/symbolSearch.js +9 -7
- package/dist/processors/gridsetProcessor.js +57 -20
- package/dist/processors/obfProcessor.js +12 -0
- package/dist/processors/snap/helpers.d.ts +5 -1
- package/dist/processors/snap/helpers.js +5 -0
- package/dist/processors/snapProcessor.d.ts +2 -0
- package/dist/processors/snapProcessor.js +155 -4
- package/dist/processors/touchchatProcessor.js +24 -5
- package/dist/types/aac.d.ts +63 -0
- package/dist/types/aac.js +33 -0
- package/dist/validation/gridsetValidator.js +10 -0
- package/package.json +1 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.OblAnonymizer = exports.OblUtil = void 0;
|
|
4
|
+
const treeStructure_1 = require("../../../core/treeStructure");
|
|
5
|
+
/**
|
|
6
|
+
* .obl (Open Board Logging) Utility
|
|
7
|
+
*
|
|
8
|
+
* Provides parsing and generation support for the .obl format.
|
|
9
|
+
*/
|
|
10
|
+
class OblUtil {
|
|
11
|
+
/**
|
|
12
|
+
* Parse an OBL JSON string.
|
|
13
|
+
* Handles the optional /* notice * / at the start of the file.
|
|
14
|
+
*/
|
|
15
|
+
static parse(json) {
|
|
16
|
+
// Remove potential comment at the start
|
|
17
|
+
let cleanJson = json.trim();
|
|
18
|
+
if (cleanJson.startsWith('/*')) {
|
|
19
|
+
const endComment = cleanJson.indexOf('*/');
|
|
20
|
+
if (endComment !== -1) {
|
|
21
|
+
cleanJson = cleanJson.substring(endComment + 2).trim();
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return JSON.parse(cleanJson);
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Stringify an OBL file object.
|
|
28
|
+
* Optionally adds the recommended notice comment.
|
|
29
|
+
*/
|
|
30
|
+
static stringify(obl, includeNotice = true) {
|
|
31
|
+
const json = JSON.stringify(obl, null, 2);
|
|
32
|
+
if (includeNotice) {
|
|
33
|
+
return `/* NOTICE: The following information represents an individual's communication and should be treated respectfully and securely. */\n${json}`;
|
|
34
|
+
}
|
|
35
|
+
return json;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Convert an OBL file to internal HistoryEntry format.
|
|
39
|
+
*/
|
|
40
|
+
static toHistoryEntries(obl) {
|
|
41
|
+
const entries = [];
|
|
42
|
+
const source = obl.source || 'OBL';
|
|
43
|
+
// OBL is session-based and event-based.
|
|
44
|
+
// HistoryEntry is content-based with occurrences.
|
|
45
|
+
// We'll group events by content (label/text) to match HistoryEntry structure.
|
|
46
|
+
const contentMap = new Map();
|
|
47
|
+
for (const session of obl.sessions) {
|
|
48
|
+
for (const event of session.events) {
|
|
49
|
+
let content = '';
|
|
50
|
+
const evtAny = event;
|
|
51
|
+
const occurrence = {
|
|
52
|
+
timestamp: new Date(event.timestamp),
|
|
53
|
+
modeling: event.modeling,
|
|
54
|
+
pageId: evtAny.board_id || null,
|
|
55
|
+
latitude: event.geo?.[0] || null,
|
|
56
|
+
longitude: event.geo?.[1] || null,
|
|
57
|
+
type: event.type,
|
|
58
|
+
// Store all other OBL fields in the occurrence
|
|
59
|
+
buttonId: evtAny.button_id || null,
|
|
60
|
+
boardId: evtAny.board_id || null,
|
|
61
|
+
spoken: evtAny.spoken,
|
|
62
|
+
vocalization: evtAny.vocalization,
|
|
63
|
+
imageUrl: evtAny.image_url,
|
|
64
|
+
actions: evtAny.actions,
|
|
65
|
+
};
|
|
66
|
+
if (event.type === 'button') {
|
|
67
|
+
const btn = event;
|
|
68
|
+
content = btn.vocalization || btn.label;
|
|
69
|
+
}
|
|
70
|
+
else if (event.type === 'utterance') {
|
|
71
|
+
const utt = event;
|
|
72
|
+
content = utt.text;
|
|
73
|
+
}
|
|
74
|
+
else if (event.type === 'action') {
|
|
75
|
+
const act = event;
|
|
76
|
+
content = act.action;
|
|
77
|
+
}
|
|
78
|
+
else if (event.type === 'note') {
|
|
79
|
+
const note = event;
|
|
80
|
+
content = note.text;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const evtAny = event;
|
|
84
|
+
content = evtAny.label || evtAny.text || evtAny.action || 'unknown';
|
|
85
|
+
}
|
|
86
|
+
const occurrences = contentMap.get(content) || [];
|
|
87
|
+
occurrences.push(occurrence);
|
|
88
|
+
contentMap.set(content, occurrences);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
contentMap.forEach((occurrences, content) => {
|
|
92
|
+
entries.push({
|
|
93
|
+
id: `obl:${content}`,
|
|
94
|
+
source: source,
|
|
95
|
+
content: content,
|
|
96
|
+
occurrences: occurrences.sort((a, b) => a.timestamp.getTime() - b.timestamp.getTime()),
|
|
97
|
+
});
|
|
98
|
+
});
|
|
99
|
+
return entries;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Convert HistoryEntries to an OBL file object.
|
|
103
|
+
*/
|
|
104
|
+
static fromHistoryEntries(entries, userId, source) {
|
|
105
|
+
const events = [];
|
|
106
|
+
for (const entry of entries) {
|
|
107
|
+
for (const occ of entry.occurrences) {
|
|
108
|
+
const timestamp = occ.timestamp.toISOString();
|
|
109
|
+
const intent = occ.intent;
|
|
110
|
+
let oblType = occ.type || 'button';
|
|
111
|
+
let actionStr = undefined;
|
|
112
|
+
// Smart mapping based on AACSemanticIntent
|
|
113
|
+
if (intent === treeStructure_1.AACSemanticIntent.CLEAR_TEXT) {
|
|
114
|
+
oblType = 'action';
|
|
115
|
+
actionStr = ':clear';
|
|
116
|
+
}
|
|
117
|
+
else if (intent === treeStructure_1.AACSemanticIntent.GO_HOME) {
|
|
118
|
+
oblType = 'action';
|
|
119
|
+
actionStr = ':home';
|
|
120
|
+
}
|
|
121
|
+
else if (intent === treeStructure_1.AACSemanticIntent.NAVIGATE_TO) {
|
|
122
|
+
oblType = 'action';
|
|
123
|
+
actionStr = ':open_board';
|
|
124
|
+
}
|
|
125
|
+
else if (intent === treeStructure_1.AACSemanticIntent.GO_BACK) {
|
|
126
|
+
oblType = 'action';
|
|
127
|
+
actionStr = ':back';
|
|
128
|
+
}
|
|
129
|
+
else if (intent === treeStructure_1.AACSemanticIntent.DELETE_CHARACTER) {
|
|
130
|
+
oblType = 'action';
|
|
131
|
+
actionStr = ':backspace';
|
|
132
|
+
}
|
|
133
|
+
else if (intent === treeStructure_1.AACSemanticIntent.SPEAK_IMMEDIATE ||
|
|
134
|
+
intent === treeStructure_1.AACSemanticIntent.SPEAK_TEXT) {
|
|
135
|
+
// Speak could be a button or an utterance or an action
|
|
136
|
+
if (oblType !== 'utterance' && oblType !== 'button') {
|
|
137
|
+
oblType = 'action';
|
|
138
|
+
actionStr = ':speak';
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
const common = {
|
|
142
|
+
id: Math.random().toString(36).substring(2, 11),
|
|
143
|
+
timestamp,
|
|
144
|
+
modeling: occ.modeling,
|
|
145
|
+
type: oblType,
|
|
146
|
+
};
|
|
147
|
+
if (occ.latitude !== null &&
|
|
148
|
+
occ.latitude !== undefined &&
|
|
149
|
+
occ.longitude !== null &&
|
|
150
|
+
occ.longitude !== undefined) {
|
|
151
|
+
common.geo = [occ.latitude, occ.longitude];
|
|
152
|
+
}
|
|
153
|
+
if (oblType === 'utterance') {
|
|
154
|
+
events.push({
|
|
155
|
+
...common,
|
|
156
|
+
text: entry.content,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
else if (oblType === 'action') {
|
|
160
|
+
events.push({
|
|
161
|
+
...common,
|
|
162
|
+
action: actionStr || entry.content,
|
|
163
|
+
destination_board_id: occ.boardId || undefined,
|
|
164
|
+
text: intent === treeStructure_1.AACSemanticIntent.SPEAK_TEXT ? entry.content : undefined,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
else if (oblType === 'note') {
|
|
168
|
+
events.push({
|
|
169
|
+
...common,
|
|
170
|
+
text: entry.content,
|
|
171
|
+
});
|
|
172
|
+
}
|
|
173
|
+
else {
|
|
174
|
+
// Default to button
|
|
175
|
+
events.push({
|
|
176
|
+
...common,
|
|
177
|
+
type: 'button',
|
|
178
|
+
label: occ.vocalization ? entry.content : entry.content,
|
|
179
|
+
spoken: occ.spoken ??
|
|
180
|
+
occ.category === treeStructure_1.AACSemanticCategory.COMMUNICATION,
|
|
181
|
+
button_id: occ.buttonId || undefined,
|
|
182
|
+
board_id: occ.boardId || occ.pageId || undefined,
|
|
183
|
+
vocalization: occ.vocalization || undefined,
|
|
184
|
+
image_url: occ.imageUrl || undefined,
|
|
185
|
+
actions: occ.actions || undefined,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
// Sort events by timestamp
|
|
191
|
+
events.sort((a, b) => a.timestamp.localeCompare(b.timestamp));
|
|
192
|
+
const started = events.length > 0 ? events[0].timestamp : new Date().toISOString();
|
|
193
|
+
const ended = events.length > 0 ? events[events.length - 1].timestamp : new Date().toISOString();
|
|
194
|
+
const session = {
|
|
195
|
+
id: 'session-1',
|
|
196
|
+
type: 'log',
|
|
197
|
+
started,
|
|
198
|
+
ended,
|
|
199
|
+
events,
|
|
200
|
+
};
|
|
201
|
+
return {
|
|
202
|
+
format: 'open-board-log-0.1',
|
|
203
|
+
user_id: userId,
|
|
204
|
+
source: source || 'aac-processors',
|
|
205
|
+
sessions: [session],
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
exports.OblUtil = OblUtil;
|
|
210
|
+
/**
|
|
211
|
+
* .obl Anonymization Utility
|
|
212
|
+
*/
|
|
213
|
+
class OblAnonymizer {
|
|
214
|
+
/**
|
|
215
|
+
* Apply anonymization to an OBL file.
|
|
216
|
+
*/
|
|
217
|
+
static anonymize(obl, types) {
|
|
218
|
+
const newObl = JSON.parse(JSON.stringify(obl));
|
|
219
|
+
newObl.anonymized = true;
|
|
220
|
+
for (const session of newObl.sessions) {
|
|
221
|
+
session.anonymizations = session.anonymizations || [];
|
|
222
|
+
if (types.includes('timestamp_shift')) {
|
|
223
|
+
this.applyTimestampShift(session);
|
|
224
|
+
if (!session.anonymizations.includes('timestamp_shift'))
|
|
225
|
+
session.anonymizations.push('timestamp_shift');
|
|
226
|
+
}
|
|
227
|
+
if (types.includes('geolocation_masking')) {
|
|
228
|
+
this.applyGeolocationMasking(session);
|
|
229
|
+
if (!session.anonymizations.includes('geolocation_masking'))
|
|
230
|
+
session.anonymizations.push('geolocation_masking');
|
|
231
|
+
}
|
|
232
|
+
if (types.includes('url_stripping')) {
|
|
233
|
+
this.applyUrlStripping(session);
|
|
234
|
+
if (!session.anonymizations.includes('url_stripping'))
|
|
235
|
+
session.anonymizations.push('url_stripping');
|
|
236
|
+
}
|
|
237
|
+
if (types.includes('name_masking')) {
|
|
238
|
+
this.applyNameMasking(newObl, session);
|
|
239
|
+
if (!session.anonymizations.includes('name_masking'))
|
|
240
|
+
session.anonymizations.push('name_masking');
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return newObl;
|
|
244
|
+
}
|
|
245
|
+
static applyTimestampShift(session) {
|
|
246
|
+
if (session.events.length === 0)
|
|
247
|
+
return;
|
|
248
|
+
const firstEventTime = session.events.length > 0 ? new Date(session.events[0].timestamp).getTime() : Infinity;
|
|
249
|
+
const sessionStartTime = session.started ? new Date(session.started).getTime() : Infinity;
|
|
250
|
+
const firstTimestamp = Math.min(firstEventTime, sessionStartTime);
|
|
251
|
+
if (firstTimestamp === Infinity)
|
|
252
|
+
return;
|
|
253
|
+
const targetStart = new Date('2000-01-01T00:00:00.000Z').getTime();
|
|
254
|
+
const offset = targetStart - firstTimestamp;
|
|
255
|
+
session.started = new Date(new Date(session.started).getTime() + offset).toISOString();
|
|
256
|
+
session.ended = new Date(new Date(session.ended).getTime() + offset).toISOString();
|
|
257
|
+
for (const event of session.events) {
|
|
258
|
+
event.timestamp = new Date(new Date(event.timestamp).getTime() + offset).toISOString();
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
static applyGeolocationMasking(session) {
|
|
262
|
+
for (const event of session.events) {
|
|
263
|
+
delete event.geo;
|
|
264
|
+
delete event.location_id;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
static applyUrlStripping(session) {
|
|
268
|
+
for (const event of session.events) {
|
|
269
|
+
if (event.type === 'button') {
|
|
270
|
+
delete event.image_url;
|
|
271
|
+
}
|
|
272
|
+
if (event.type === 'note') {
|
|
273
|
+
delete event.author_url;
|
|
274
|
+
delete event.author_email;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
static applyNameMasking(obl, session) {
|
|
279
|
+
delete obl.user_name;
|
|
280
|
+
for (const event of session.events) {
|
|
281
|
+
if (event.type === 'note') {
|
|
282
|
+
delete event.author_name;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
exports.OblAnonymizer = OblAnonymizer;
|
|
@@ -22,12 +22,13 @@ class VocabularyAnalyzer {
|
|
|
22
22
|
const lowEffortThreshold = options?.lowEffortThreshold || 2.0;
|
|
23
23
|
// Load reference data
|
|
24
24
|
const coreLists = this.referenceLoader.loadCoreLists();
|
|
25
|
-
// Create word to effort map
|
|
25
|
+
// Create word to effort map (using lowercase keys for matching)
|
|
26
26
|
const wordEffortMap = new Map();
|
|
27
27
|
metrics.buttons.forEach((btn) => {
|
|
28
|
-
const
|
|
28
|
+
const word = btn.label.toLowerCase();
|
|
29
|
+
const existing = wordEffortMap.get(word);
|
|
29
30
|
if (!existing || btn.effort < existing) {
|
|
30
|
-
wordEffortMap.set(
|
|
31
|
+
wordEffortMap.set(word, btn.effort);
|
|
31
32
|
}
|
|
32
33
|
});
|
|
33
34
|
// Analyze each core list
|
|
@@ -79,7 +80,8 @@ class VocabularyAnalyzer {
|
|
|
79
80
|
const missing = [];
|
|
80
81
|
let totalEffort = 0;
|
|
81
82
|
list.words.forEach((word) => {
|
|
82
|
-
const
|
|
83
|
+
const lowerWord = word.toLowerCase();
|
|
84
|
+
const effort = wordEffortMap.get(lowerWord);
|
|
83
85
|
if (effort !== undefined) {
|
|
84
86
|
covered.push(word);
|
|
85
87
|
totalEffort += effort;
|
|
@@ -7,7 +7,7 @@ exports.TouchChatSymbolResolver = exports.TouchChatSymbolExtractor = exports.Gri
|
|
|
7
7
|
exports.resolveSymbol = resolveSymbol;
|
|
8
8
|
const path_1 = __importDefault(require("path"));
|
|
9
9
|
const fs_1 = __importDefault(require("fs"));
|
|
10
|
-
const
|
|
10
|
+
const symbols_1 = require("../processors/gridset/symbols");
|
|
11
11
|
// --- Base Classes ---
|
|
12
12
|
class SymbolExtractor {
|
|
13
13
|
}
|
|
@@ -61,10 +61,13 @@ exports.SnapSymbolResolver = SnapSymbolResolver;
|
|
|
61
61
|
let AdmZip = null;
|
|
62
62
|
let XMLParser = null;
|
|
63
63
|
try {
|
|
64
|
+
// Dynamic requires for optional dependencies
|
|
64
65
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
65
|
-
|
|
66
|
+
const admZipModule = require('adm-zip');
|
|
66
67
|
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
67
|
-
|
|
68
|
+
const fxpModule = require('fast-xml-parser');
|
|
69
|
+
AdmZip = admZipModule;
|
|
70
|
+
XMLParser = fxpModule.XMLParser;
|
|
68
71
|
}
|
|
69
72
|
catch {
|
|
70
73
|
AdmZip = null;
|
|
@@ -74,19 +77,13 @@ class Grid3SymbolExtractor extends SymbolExtractor {
|
|
|
74
77
|
getSymbolReferences(filePath) {
|
|
75
78
|
if (!AdmZip || !XMLParser)
|
|
76
79
|
throw new Error('adm-zip or fast-xml-parser not installed');
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
const
|
|
80
|
-
const
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
// Parse to validate XML structure (future: extract refs)
|
|
85
|
-
parser.parse(xmlBuffer.toString('utf8'));
|
|
86
|
-
// TODO: Extract symbol references from Grid 3 XML structure when needed
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
return Array.from(refs);
|
|
80
|
+
// Import GridsetProcessor dynamically to avoid circular dependencies
|
|
81
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
82
|
+
const { GridsetProcessor } = require('../processors/gridsetProcessor');
|
|
83
|
+
const proc = new GridsetProcessor();
|
|
84
|
+
const tree = proc.loadIntoTree(filePath);
|
|
85
|
+
// Use the existing extractSymbolReferences function from gridset/symbols.ts
|
|
86
|
+
return (0, symbols_1.extractSymbolReferences)(tree);
|
|
90
87
|
}
|
|
91
88
|
}
|
|
92
89
|
exports.Grid3SymbolExtractor = Grid3SymbolExtractor;
|
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import { BaseProcessor, ProcessorOptions, ExtractStringsResult, TranslatedString, SourceString } from '../core/baseProcessor';
|
|
2
2
|
import { AACTree } from '../core/treeStructure';
|
|
3
|
+
export declare function normalizeHexColor(hexColor: string): string | null;
|
|
4
|
+
export declare function adjustHexColor(hexColor: string, amount: number): string;
|
|
5
|
+
export declare function getHighContrastNeutralColor(backgroundColor: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Calculate relative luminance of a color using WCAG formula
|
|
8
|
+
* @param hexColor - Hex color string (e.g., "#1d90ff")
|
|
9
|
+
* @returns Relative luminance value between 0 and 1
|
|
10
|
+
*/
|
|
11
|
+
export declare function calculateLuminance(hexColor: string): number;
|
|
12
|
+
/**
|
|
13
|
+
* Choose white or black text color based on background luminance for optimal contrast
|
|
14
|
+
* @param backgroundColor - Background color hex string
|
|
15
|
+
* @returns "#FFFFFF" for dark backgrounds, "#000000" for light backgrounds
|
|
16
|
+
*/
|
|
17
|
+
export declare function getContrastingTextColor(backgroundColor: string): string;
|
|
3
18
|
declare class AstericsGridProcessor extends BaseProcessor {
|
|
4
19
|
private loadAudio;
|
|
5
20
|
constructor(options?: ProcessorOptions & {
|
|
@@ -4,6 +4,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.AstericsGridProcessor = void 0;
|
|
7
|
+
exports.normalizeHexColor = normalizeHexColor;
|
|
8
|
+
exports.adjustHexColor = adjustHexColor;
|
|
9
|
+
exports.getHighContrastNeutralColor = getHighContrastNeutralColor;
|
|
10
|
+
exports.calculateLuminance = calculateLuminance;
|
|
11
|
+
exports.getContrastingTextColor = getContrastingTextColor;
|
|
7
12
|
const baseProcessor_1 = require("../core/baseProcessor");
|
|
8
13
|
const treeStructure_1 = require("../core/treeStructure");
|
|
9
14
|
const fs_1 = __importDefault(require("fs"));
|
|
@@ -535,6 +540,17 @@ function getContrastingTextColor(backgroundColor) {
|
|
|
535
540
|
// WCAG threshold: use white text if luminance < 0.5, black otherwise
|
|
536
541
|
return luminance < 0.5 ? '#FFFFFF' : '#000000';
|
|
537
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* Map Asterics Grid hidden value to AAC standard visibility
|
|
545
|
+
* Asterics Grid: true = hidden, false = visible
|
|
546
|
+
* Maps to: 'Hidden' | 'Visible' | undefined
|
|
547
|
+
*/
|
|
548
|
+
function mapAstericsVisibility(hidden) {
|
|
549
|
+
if (hidden === undefined) {
|
|
550
|
+
return undefined; // Default to visible
|
|
551
|
+
}
|
|
552
|
+
return hidden ? 'Hidden' : 'Visible';
|
|
553
|
+
}
|
|
538
554
|
class AstericsGridProcessor extends baseProcessor_1.BaseProcessor {
|
|
539
555
|
constructor(options = {}) {
|
|
540
556
|
super(options);
|
|
@@ -979,6 +995,7 @@ class AstericsGridProcessor extends baseProcessor_1.BaseProcessor {
|
|
|
979
995
|
targetPageId: targetPageId || undefined,
|
|
980
996
|
semanticAction: semanticAction,
|
|
981
997
|
audioRecording: audioRecording,
|
|
998
|
+
visibility: mapAstericsVisibility(element.hidden),
|
|
982
999
|
image: imageName, // Store image filename/reference
|
|
983
1000
|
parameters: imageData
|
|
984
1001
|
? {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { AACTree } from '../../core/treeStructure';
|
|
1
|
+
import { AACTree, AACSemanticCategory, AACSemanticIntent } from '../../core/treeStructure';
|
|
2
2
|
/**
|
|
3
3
|
* Build a map of button IDs to resolved image entry paths for a specific page.
|
|
4
4
|
* Helpful when rewriting zip entry names or validating images referenced in a grid.
|
|
@@ -66,6 +66,9 @@ export interface Grid3HistoryEntry {
|
|
|
66
66
|
timestamp: Date;
|
|
67
67
|
latitude?: number | null;
|
|
68
68
|
longitude?: number | null;
|
|
69
|
+
type?: 'button' | 'action' | 'utterance' | 'note' | 'other';
|
|
70
|
+
intent?: AACSemanticIntent | string;
|
|
71
|
+
category?: AACSemanticCategory;
|
|
69
72
|
}>;
|
|
70
73
|
rawXml?: string;
|
|
71
74
|
}
|
|
@@ -44,6 +44,7 @@ exports.readGrid3HistoryForUser = readGrid3HistoryForUser;
|
|
|
44
44
|
exports.readAllGrid3History = readAllGrid3History;
|
|
45
45
|
const adm_zip_1 = __importDefault(require("adm-zip"));
|
|
46
46
|
const fast_xml_parser_1 = require("fast-xml-parser");
|
|
47
|
+
const treeStructure_1 = require("../../core/treeStructure");
|
|
47
48
|
const fs = __importStar(require("fs"));
|
|
48
49
|
const path = __importStar(require("path"));
|
|
49
50
|
const child_process_1 = require("child_process");
|
|
@@ -394,6 +395,9 @@ function readGrid3History(historyDbPath) {
|
|
|
394
395
|
timestamp: (0, dotnetTicks_1.dotNetTicksToDate)(BigInt(row.TickValue ?? 0)),
|
|
395
396
|
latitude: row.Latitude ?? null,
|
|
396
397
|
longitude: row.Longitude ?? null,
|
|
398
|
+
type: 'utterance',
|
|
399
|
+
intent: treeStructure_1.AACSemanticIntent.SPEAK_TEXT,
|
|
400
|
+
category: treeStructure_1.AACSemanticCategory.COMMUNICATION,
|
|
397
401
|
});
|
|
398
402
|
events.set(phraseId, entry);
|
|
399
403
|
}
|
|
@@ -106,10 +106,10 @@ function detectPluginCellType(content) {
|
|
|
106
106
|
if (!content) {
|
|
107
107
|
return { cellType: Grid3CellType.Regular };
|
|
108
108
|
}
|
|
109
|
-
const contentType = content.ContentType || content.contenttype
|
|
110
|
-
const contentSubType = content.ContentSubType || content.contentsubtype
|
|
109
|
+
const contentType = content.ContentType || content.contenttype;
|
|
110
|
+
const contentSubType = content.ContentSubType || content.contentsubtype;
|
|
111
111
|
// Workspace cells - full editing workspaces
|
|
112
|
-
if (contentType === 'Workspace') {
|
|
112
|
+
if (contentType === 'Workspace' || content.Style?.BasedOnStyle === 'Workspace') {
|
|
113
113
|
return {
|
|
114
114
|
cellType: Grid3CellType.Workspace,
|
|
115
115
|
subType: contentSubType || undefined,
|
|
@@ -118,7 +118,7 @@ function detectPluginCellType(content) {
|
|
|
118
118
|
};
|
|
119
119
|
}
|
|
120
120
|
// LiveCell detection - dynamic content displays
|
|
121
|
-
if (contentType === 'LiveCell') {
|
|
121
|
+
if (contentType === 'LiveCell' || content.Style?.BasedOnStyle === 'LiveCell') {
|
|
122
122
|
return {
|
|
123
123
|
cellType: Grid3CellType.LiveCell,
|
|
124
124
|
liveCellType: contentSubType || undefined,
|
|
@@ -127,7 +127,7 @@ function detectPluginCellType(content) {
|
|
|
127
127
|
};
|
|
128
128
|
}
|
|
129
129
|
// AutoContent detection - dynamic word/content suggestions
|
|
130
|
-
if (contentType === 'AutoContent') {
|
|
130
|
+
if (contentType === 'AutoContent' || content.Style?.BasedOnStyle === 'AutoContent') {
|
|
131
131
|
const autoContentType = extractAutoContentType(content);
|
|
132
132
|
return {
|
|
133
133
|
cellType: Grid3CellType.AutoContent,
|
|
@@ -172,38 +172,38 @@ function inferWorkspacePlugin(subType) {
|
|
|
172
172
|
return undefined;
|
|
173
173
|
const normalized = subType.toLowerCase();
|
|
174
174
|
if (normalized.includes('chat'))
|
|
175
|
-
return '
|
|
175
|
+
return 'Grid3.Chat';
|
|
176
176
|
if (normalized.includes('email') || normalized.includes('mail'))
|
|
177
|
-
return '
|
|
178
|
-
if (normalized.includes('word') || normalized.includes('
|
|
179
|
-
return '
|
|
177
|
+
return 'Grid3.Email';
|
|
178
|
+
if (normalized.includes('word') || normalized.includes('doc'))
|
|
179
|
+
return 'Grid3.WordProcessor';
|
|
180
180
|
if (normalized.includes('phone'))
|
|
181
|
-
return '
|
|
181
|
+
return 'Grid3.Phone';
|
|
182
182
|
if (normalized.includes('sms') || normalized.includes('text'))
|
|
183
|
-
return '
|
|
184
|
-
if (normalized.includes('
|
|
185
|
-
return '
|
|
186
|
-
if (normalized.includes('computer')
|
|
187
|
-
return '
|
|
188
|
-
if (normalized.includes('
|
|
189
|
-
return '
|
|
190
|
-
if (normalized.includes('timer')
|
|
191
|
-
return '
|
|
183
|
+
return 'Grid3.Sms';
|
|
184
|
+
if (normalized.includes('browser') || normalized.includes('web'))
|
|
185
|
+
return 'Grid3.WebBrowser';
|
|
186
|
+
if (normalized.includes('computer'))
|
|
187
|
+
return 'Grid3.ComputerControl';
|
|
188
|
+
if (normalized.includes('calc'))
|
|
189
|
+
return 'Grid3.Calculator';
|
|
190
|
+
if (normalized.includes('timer'))
|
|
191
|
+
return 'Grid3.Timer';
|
|
192
192
|
if (normalized.includes('music') || normalized.includes('video'))
|
|
193
|
-
return '
|
|
193
|
+
return 'Grid3.MusicVideo';
|
|
194
194
|
if (normalized.includes('photo') || normalized.includes('image'))
|
|
195
|
-
return '
|
|
195
|
+
return 'Grid3.Photos';
|
|
196
196
|
if (normalized.includes('contact'))
|
|
197
|
-
return '
|
|
197
|
+
return 'Grid3.Contacts';
|
|
198
198
|
if (normalized.includes('learning'))
|
|
199
|
-
return '
|
|
200
|
-
if (normalized.includes('message') && normalized.includes('
|
|
201
|
-
return '
|
|
202
|
-
if (normalized.includes('
|
|
203
|
-
return '
|
|
204
|
-
if (normalized.includes('
|
|
205
|
-
return '
|
|
206
|
-
return
|
|
199
|
+
return 'Grid3.InteractiveLearning';
|
|
200
|
+
if (normalized.includes('message') && normalized.includes('banking'))
|
|
201
|
+
return 'Grid3.MessageBanking';
|
|
202
|
+
if (normalized.includes('control'))
|
|
203
|
+
return 'Grid3.EnvironmentControl';
|
|
204
|
+
if (normalized.includes('settings'))
|
|
205
|
+
return 'Grid3.Settings';
|
|
206
|
+
return `Grid3.${subType}`;
|
|
207
207
|
}
|
|
208
208
|
/**
|
|
209
209
|
* Infer plugin ID from live cell type
|
|
@@ -212,24 +212,25 @@ function inferLiveCellPlugin(liveCellType) {
|
|
|
212
212
|
if (!liveCellType)
|
|
213
213
|
return undefined;
|
|
214
214
|
const normalized = liveCellType.toLowerCase();
|
|
215
|
-
if (normalized.includes('clock')
|
|
216
|
-
return '
|
|
217
|
-
|
|
215
|
+
if (normalized.includes('clock'))
|
|
216
|
+
return 'Grid3.Clock';
|
|
217
|
+
if (normalized.includes('date'))
|
|
218
|
+
return 'Grid3.Clock';
|
|
218
219
|
if (normalized.includes('volume'))
|
|
219
|
-
return '
|
|
220
|
+
return 'Grid3.Volume';
|
|
220
221
|
if (normalized.includes('speed'))
|
|
221
|
-
return '
|
|
222
|
+
return 'Grid3.Speed';
|
|
222
223
|
if (normalized.includes('voice'))
|
|
223
|
-
return '
|
|
224
|
+
return 'Grid3.Speech';
|
|
224
225
|
if (normalized.includes('message'))
|
|
225
|
-
return '
|
|
226
|
+
return 'Grid3.Chat';
|
|
226
227
|
if (normalized.includes('battery'))
|
|
227
|
-
return '
|
|
228
|
-
if (normalized.includes('wifi')
|
|
229
|
-
return '
|
|
228
|
+
return 'Grid3.Battery';
|
|
229
|
+
if (normalized.includes('wifi'))
|
|
230
|
+
return 'Grid3.Wifi';
|
|
230
231
|
if (normalized.includes('bluetooth'))
|
|
231
|
-
return '
|
|
232
|
-
return
|
|
232
|
+
return 'Grid3.Bluetooth';
|
|
233
|
+
return `Grid3.${liveCellType}`;
|
|
233
234
|
}
|
|
234
235
|
/**
|
|
235
236
|
* Infer plugin ID from auto content type
|
|
@@ -239,24 +240,24 @@ function inferAutoContentPlugin(autoContentType) {
|
|
|
239
240
|
return undefined;
|
|
240
241
|
const normalized = autoContentType.toLowerCase();
|
|
241
242
|
if (normalized.includes('voice') || normalized.includes('speed'))
|
|
242
|
-
return '
|
|
243
|
+
return 'Grid3.Speech';
|
|
243
244
|
if (normalized.includes('email') || normalized.includes('mail'))
|
|
244
|
-
return '
|
|
245
|
+
return 'Grid3.Email';
|
|
245
246
|
if (normalized.includes('phone'))
|
|
246
|
-
return '
|
|
247
|
+
return 'Grid3.Phone';
|
|
247
248
|
if (normalized.includes('sms') || normalized.includes('text'))
|
|
248
|
-
return '
|
|
249
|
+
return 'Grid3.Sms';
|
|
249
250
|
if (normalized.includes('web') ||
|
|
250
251
|
normalized.includes('favorite') ||
|
|
251
252
|
normalized.includes('history')) {
|
|
252
|
-
return '
|
|
253
|
+
return 'Grid3.WebBrowser';
|
|
253
254
|
}
|
|
254
255
|
if (normalized.includes('prediction'))
|
|
255
|
-
return '
|
|
256
|
+
return 'Grid3.Prediction';
|
|
256
257
|
if (normalized.includes('grammar'))
|
|
257
|
-
return '
|
|
258
|
+
return 'Grid3.Grammar';
|
|
258
259
|
if (normalized.includes('context'))
|
|
259
|
-
return '
|
|
260
|
+
return 'Grid3.AutoContent';
|
|
260
261
|
return undefined;
|
|
261
262
|
}
|
|
262
263
|
/**
|
|
@@ -154,10 +154,11 @@ function extractSymbolLibraryImage(reference, options = {}) {
|
|
|
154
154
|
};
|
|
155
155
|
}
|
|
156
156
|
// Successfully extracted!
|
|
157
|
-
const
|
|
157
|
+
const data = resolved.data;
|
|
158
|
+
const format = data ? detectImageFormat(data) : 'unknown';
|
|
158
159
|
return {
|
|
159
160
|
found: true,
|
|
160
|
-
data
|
|
161
|
+
data,
|
|
161
162
|
format,
|
|
162
163
|
source: 'symbol-library',
|
|
163
164
|
reference: reference,
|
|
@@ -126,13 +126,15 @@ function searchSymbols(searchTerm, options = {}) {
|
|
|
126
126
|
// Exact match first
|
|
127
127
|
if (index.searchTerms.has(lowerSearchTerm)) {
|
|
128
128
|
const symbolFilename = index.searchTerms.get(lowerSearchTerm);
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
129
|
+
if (symbolFilename) {
|
|
130
|
+
results.push({
|
|
131
|
+
searchTerm: lowerSearchTerm,
|
|
132
|
+
symbolFilename,
|
|
133
|
+
displayName: index.filenames.get(symbolFilename) || lowerSearchTerm,
|
|
134
|
+
library: libraryName,
|
|
135
|
+
exactMatch: true,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
136
138
|
}
|
|
137
139
|
// Fuzzy match if enabled
|
|
138
140
|
if (options.fuzzyMatch !== false) {
|