@willwade/aac-processors 0.3.0 → 0.3.2
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/analytics.d.ts +1 -0
- package/dist/analytics.js +1 -0
- package/dist/browser/processors/gotalkNow/nsKeyed.js +13 -6
- package/dist/browser/processors/gotalkNowProcessor.js +90 -15
- package/dist/browser/processors/gridsetProcessor.js +2 -2
- package/dist/browser/processors/obfProcessor.js +11 -4
- package/dist/browser/utilities/analytics/competence.js +510 -0
- package/dist/browser/utilities/analytics/history.js +22 -0
- package/dist/core/treeStructure.d.ts +7 -0
- package/dist/processors/gotalkNow/nsKeyed.js +13 -6
- package/dist/processors/gotalkNowProcessor.js +90 -15
- package/dist/processors/gridsetProcessor.js +2 -2
- package/dist/processors/obfProcessor.js +11 -4
- package/dist/utilities/analytics/competence.d.ts +281 -0
- package/dist/utilities/analytics/competence.js +520 -0
- package/dist/utilities/analytics/history.d.ts +9 -0
- package/dist/utilities/analytics/history.js +23 -0
- package/dist/utilities/analytics/index.d.ts +1 -0
- package/dist/utilities/analytics/index.js +2 -0
- package/package.json +1 -1
package/dist/analytics.d.ts
CHANGED
package/dist/analytics.js
CHANGED
|
@@ -21,3 +21,4 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
21
21
|
};
|
|
22
22
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
23
23
|
__exportStar(require("./utilities/analytics/history"), exports);
|
|
24
|
+
__exportStar(require("./utilities/analytics/competence"), exports);
|
|
@@ -44,16 +44,23 @@ export function decodeNsColor(buffer) {
|
|
|
44
44
|
if (!buffer)
|
|
45
45
|
return undefined;
|
|
46
46
|
const s = bufferToLatinString(buffer);
|
|
47
|
-
//
|
|
48
|
-
//
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
47
|
+
// Match RGB[A] component strings like "1 0.949 0.561" or "0.5 0.5 0.5 1".
|
|
48
|
+
// Allow integer components (e.g. "1") but require at least one decimal to
|
|
49
|
+
// avoid matching bplist header bytes (e.g. "00 00 00").
|
|
50
|
+
const candidates = s.match(/\d+(?:\.\d+)?\s+\d+(?:\.\d+)?\s+\d+(?:\.\d+)?(?:\s+\d+(?:\.\d+)?)?/g);
|
|
51
|
+
if (candidates) {
|
|
52
|
+
// Find the first candidate that looks like RGB (has at least one decimal point).
|
|
53
|
+
const rgbStr = candidates.find((c) => c.includes('.'));
|
|
54
|
+
if (rgbStr) {
|
|
55
|
+
const parts = rgbStr.split(/\s+/).map(Number);
|
|
56
|
+
return rgbToHex(parts[0], parts[1], parts[2]);
|
|
57
|
+
}
|
|
53
58
|
}
|
|
54
59
|
return undefined;
|
|
55
60
|
}
|
|
56
61
|
const FONT_KEYWORDS = new Set([
|
|
62
|
+
'bplist',
|
|
63
|
+
'bplist00',
|
|
57
64
|
'UIFont',
|
|
58
65
|
'UIFontName',
|
|
59
66
|
'UIFontPointSize',
|
|
@@ -35,6 +35,7 @@ function buildSemanticAction(rawButton, pageId, buttonIndex) {
|
|
|
35
35
|
const actionData = rawButton.ActionData || {};
|
|
36
36
|
const label = rawButton.ButtonText ?? '';
|
|
37
37
|
const ttsText = actionData.TTSText;
|
|
38
|
+
const spoken = ttsText && ttsText.length > 0 ? ttsText : label;
|
|
38
39
|
switch (buttonType) {
|
|
39
40
|
case 'Jump': {
|
|
40
41
|
const jumpTo = actionData.JumpTo;
|
|
@@ -45,7 +46,7 @@ function buildSemanticAction(rawButton, pageId, buttonIndex) {
|
|
|
45
46
|
intent: AACSemanticIntent.NAVIGATE_TO,
|
|
46
47
|
targetId: target,
|
|
47
48
|
platformData: {
|
|
48
|
-
gotalkNow: { buttonType: 'Jump', jumpTo },
|
|
49
|
+
gotalkNow: { buttonType: 'Jump', jumpTo, jumpBook: actionData.JumpBook },
|
|
49
50
|
},
|
|
50
51
|
fallback: { type: 'NAVIGATE', targetPageId: target },
|
|
51
52
|
},
|
|
@@ -53,25 +54,78 @@ function buildSemanticAction(rawButton, pageId, buttonIndex) {
|
|
|
53
54
|
targetPageId: target,
|
|
54
55
|
};
|
|
55
56
|
}
|
|
56
|
-
case 'Audio':
|
|
57
|
-
|
|
57
|
+
case 'Audio':
|
|
58
|
+
case 'Recorded': {
|
|
59
|
+
// Recorded audio: plays a .caf file named <pageId>-<buttonIndex>.caf.
|
|
60
|
+
// Recorded buttons may also have TrimStart/TrimEnd for audio trimming.
|
|
58
61
|
const audioLocation = `${pageId}-${buttonIndex}.caf`;
|
|
59
62
|
return {
|
|
60
63
|
semanticAction: {
|
|
61
64
|
category: AACSemanticCategory.MEDIA,
|
|
62
65
|
intent: AACSemanticIntent.PLAY_SOUND,
|
|
63
66
|
platformData: {
|
|
64
|
-
gotalkNow: {
|
|
67
|
+
gotalkNow: {
|
|
68
|
+
buttonType,
|
|
69
|
+
audioLocation,
|
|
70
|
+
trimStart: actionData.TrimStart,
|
|
71
|
+
trimEnd: actionData.TrimEnd,
|
|
72
|
+
},
|
|
65
73
|
},
|
|
66
74
|
fallback: { type: 'ACTION' },
|
|
67
75
|
},
|
|
68
|
-
message:
|
|
76
|
+
message: spoken,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
case 'URL': {
|
|
80
|
+
return {
|
|
81
|
+
semanticAction: {
|
|
82
|
+
category: AACSemanticCategory.SYSTEM_CONTROL,
|
|
83
|
+
intent: AACSemanticIntent.WEB_NAVIGATE,
|
|
84
|
+
text: actionData.URL,
|
|
85
|
+
platformData: {
|
|
86
|
+
gotalkNow: {
|
|
87
|
+
buttonType: 'URL',
|
|
88
|
+
url: actionData.URL,
|
|
89
|
+
openInternally: actionData.OpenInternally,
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
fallback: { type: 'ACTION' },
|
|
93
|
+
},
|
|
94
|
+
message: spoken,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
case 'GoVisual': {
|
|
98
|
+
return {
|
|
99
|
+
semanticAction: {
|
|
100
|
+
category: AACSemanticCategory.SYSTEM_CONTROL,
|
|
101
|
+
intent: AACSemanticIntent.PLATFORM_SPECIFIC,
|
|
102
|
+
platformData: {
|
|
103
|
+
gotalkNow: {
|
|
104
|
+
buttonType: 'GoVisual',
|
|
105
|
+
goVisual: actionData.GoVisual,
|
|
106
|
+
goVisualLink: actionData.GoVisualLink,
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
fallback: { type: 'ACTION' },
|
|
110
|
+
},
|
|
111
|
+
message: spoken,
|
|
69
112
|
};
|
|
70
113
|
}
|
|
71
|
-
case 'Express':
|
|
72
|
-
case 'Bookmark':
|
|
73
|
-
case 'URL':
|
|
74
114
|
case 'Video': {
|
|
115
|
+
return {
|
|
116
|
+
semanticAction: {
|
|
117
|
+
category: AACSemanticCategory.MEDIA,
|
|
118
|
+
intent: AACSemanticIntent.PLAY_VIDEO,
|
|
119
|
+
platformData: {
|
|
120
|
+
gotalkNow: { buttonType: 'Video' },
|
|
121
|
+
},
|
|
122
|
+
fallback: { type: 'ACTION' },
|
|
123
|
+
},
|
|
124
|
+
message: spoken,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
case 'Express':
|
|
128
|
+
case 'Bookmark': {
|
|
75
129
|
return {
|
|
76
130
|
semanticAction: {
|
|
77
131
|
category: AACSemanticCategory.SYSTEM_CONTROL,
|
|
@@ -81,20 +135,19 @@ function buildSemanticAction(rawButton, pageId, buttonIndex) {
|
|
|
81
135
|
},
|
|
82
136
|
fallback: { type: 'ACTION' },
|
|
83
137
|
},
|
|
84
|
-
message:
|
|
138
|
+
message: spoken,
|
|
85
139
|
};
|
|
86
140
|
}
|
|
87
141
|
case 'TTS':
|
|
88
142
|
default: {
|
|
89
|
-
//
|
|
90
|
-
const spoken = ttsText && ttsText.length > 0 ? ttsText : label;
|
|
143
|
+
// Empty/placeholder cells (no ButtonType or no label) get a SPEAK with empty text.
|
|
91
144
|
return {
|
|
92
145
|
semanticAction: {
|
|
93
146
|
category: AACSemanticCategory.COMMUNICATION,
|
|
94
147
|
intent: AACSemanticIntent.SPEAK_TEXT,
|
|
95
148
|
text: spoken,
|
|
96
149
|
platformData: {
|
|
97
|
-
gotalkNow: { buttonType: 'TTS' },
|
|
150
|
+
gotalkNow: { buttonType: buttonType === 'TTS' ? 'TTS' : 'Empty' },
|
|
98
151
|
},
|
|
99
152
|
fallback: { type: 'SPEAK', message: spoken },
|
|
100
153
|
},
|
|
@@ -302,17 +355,39 @@ class GotalkNowProcessor extends BaseProcessor {
|
|
|
302
355
|
if (first?.SourceImageName)
|
|
303
356
|
button.symbolPath = first.SourceImageName;
|
|
304
357
|
}
|
|
305
|
-
// Visibility: GoTalk buttons can be disabled.
|
|
306
|
-
if (rawButton.Enabled === false) {
|
|
358
|
+
// Visibility: GoTalk buttons can be disabled via either field.
|
|
359
|
+
if (rawButton.Enabled === false || rawButton.Disabled === true) {
|
|
307
360
|
button.visibility = 'Disabled';
|
|
308
361
|
}
|
|
309
|
-
//
|
|
362
|
+
// Auditory cue (played when scanning/highlighting).
|
|
363
|
+
if (rawButton.CueType && rawButton.CueType !== 'None') {
|
|
364
|
+
const cueText = rawButton.CueData?.TTSText;
|
|
365
|
+
if (cueText && cueText.length > 0) {
|
|
366
|
+
button.audioDescription = cueText;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
// Recorded audio reference.
|
|
370
|
+
const btnType = rawButton.ButtonType;
|
|
371
|
+
if (btnType === 'Audio' || btnType === 'Recorded') {
|
|
372
|
+
button.audioRecording = {
|
|
373
|
+
identifier: `${pageId}-${btnIndex}.caf`,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
// AfterAction: motor-planning "speak and return" behaviour.
|
|
377
|
+
// AfterAction is an enum (0=stay, 2=..., 50=jump-back) and
|
|
378
|
+
// AfterActionData has the same shape as ActionData (e.g. {JumpTo: 0}).
|
|
379
|
+
const afterActionJumpTo = rawButton.AfterActionData?.JumpTo;
|
|
380
|
+
// Preserve raw style + extended data opaquely for round-trip styling.
|
|
310
381
|
button.parameters = {
|
|
311
382
|
...(button.parameters || {}),
|
|
312
383
|
__gotalkNow: {
|
|
313
384
|
buttonType: rawButton.ButtonType,
|
|
314
385
|
textShadow: rawButton.TextShadow,
|
|
315
386
|
textCenter: rawButton.TextCenter,
|
|
387
|
+
cueType: rawButton.CueType,
|
|
388
|
+
afterAction: rawButton.AfterAction,
|
|
389
|
+
afterActionJumpTo: afterActionJumpTo !== undefined ? String(afterActionJumpTo) : undefined,
|
|
390
|
+
jumpBook: rawButton.ActionData?.JumpBook,
|
|
316
391
|
},
|
|
317
392
|
};
|
|
318
393
|
page._loadButton(button);
|
|
@@ -2960,8 +2960,8 @@ class GridsetProcessor extends BaseProcessor {
|
|
|
2960
2960
|
cellContent.CaptionAndImage = captionAndImage;
|
|
2961
2961
|
}
|
|
2962
2962
|
const cellData = {
|
|
2963
|
-
'@_X': position.x
|
|
2964
|
-
'@_Y': position.y + yOffset
|
|
2963
|
+
'@_X': position.x, // Grid 3 coordinates are 0-based (matching loader)
|
|
2964
|
+
'@_Y': position.y + yOffset,
|
|
2965
2965
|
'@_ColumnSpan': position.columnSpan,
|
|
2966
2966
|
'@_RowSpan': position.rowSpan,
|
|
2967
2967
|
Content: cellContent,
|
|
@@ -144,14 +144,21 @@ class ObfProcessor extends BaseProcessor {
|
|
|
144
144
|
const pageId = boardData?.id ? String(boardData.id) : _boardPath?.split(/[/\\]/).pop() || '';
|
|
145
145
|
const images = boardData.images;
|
|
146
146
|
const buttons = await Promise.all(sourceButtons.map(async (btn) => {
|
|
147
|
+
// Resolve load_board.path to a board ID (strip .obf suffix if present).
|
|
148
|
+
// The OBF spec says load_board.path is a file path, but internally we
|
|
149
|
+
// store the board ID as targetPageId.
|
|
150
|
+
const rawPath = btn.load_board?.path;
|
|
151
|
+
const boardRef = rawPath
|
|
152
|
+
? rawPath.replace(/\.obf$/i, '').replace(/\.obz$/i, '')
|
|
153
|
+
: undefined;
|
|
147
154
|
const semanticAction = btn.load_board
|
|
148
155
|
? {
|
|
149
156
|
category: AACSemanticCategory.NAVIGATION,
|
|
150
157
|
intent: AACSemanticIntent.NAVIGATE_TO,
|
|
151
|
-
targetId:
|
|
158
|
+
targetId: boardRef,
|
|
152
159
|
fallback: {
|
|
153
160
|
type: 'NAVIGATE',
|
|
154
|
-
targetPageId:
|
|
161
|
+
targetPageId: boardRef,
|
|
155
162
|
},
|
|
156
163
|
}
|
|
157
164
|
: {
|
|
@@ -199,7 +206,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
199
206
|
resolvedImageEntry: resolvedImage,
|
|
200
207
|
parameters: Object.keys(buttonParameters).length > 0 ? buttonParameters : undefined,
|
|
201
208
|
semanticAction,
|
|
202
|
-
targetPageId:
|
|
209
|
+
targetPageId: boardRef,
|
|
203
210
|
semantic_id: btn.semantic_id, // Extract semantic_id if present
|
|
204
211
|
});
|
|
205
212
|
}));
|
|
@@ -618,7 +625,7 @@ class ObfProcessor extends BaseProcessor {
|
|
|
618
625
|
vocalization: button.message || button.label,
|
|
619
626
|
load_board: button.semanticAction?.intent === AACSemanticIntent.NAVIGATE_TO && button.targetPageId
|
|
620
627
|
? {
|
|
621
|
-
path: button.targetPageId,
|
|
628
|
+
path: this.getPageFilename(button.targetPageId, metadata),
|
|
622
629
|
}
|
|
623
630
|
: undefined,
|
|
624
631
|
background_color: button.style?.backgroundColor,
|