Skip to content

Commit 1696885

Browse files
authored
Merge pull request #258 from gbishop/main
Merge updates from gb
2 parents c07dca8 + f11f8be commit 1696885

File tree

10 files changed

+143
-45
lines changed

10 files changed

+143
-45
lines changed

Diff for: src/components/access/cues/index.js

+30-1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,13 @@ export class CueList extends DesignerPanel {
6868
return this.cueMap.get(cue);
6969
}
7070

71+
/** @param {string} key
72+
* @returns {Cue | undefined}
73+
*/
74+
keyToCue(key) {
75+
return this.children.find((child) => child.Key.value == key);
76+
}
77+
7178
/** @param {Object} obj */
7279
static upgrade(obj) {
7380
// update any CueCss entries to the new style interpolation
@@ -99,6 +106,19 @@ class Cue extends TreeBaseSwitchable {
99106
Key = new Props.UID();
100107
CueType = new Props.TypeSelect(CueTypes);
101108
Default = new Props.OneOfGroup(false, { group: "DefaultCue" });
109+
SpeechField = new Props.Field({
110+
placeholder: "None selected",
111+
notRequired: true,
112+
addedFields: ["#GroupName"],
113+
});
114+
voiceURI = new Props.Voice("", { label: "Voice" });
115+
pitch = new Props.Float(1);
116+
rate = new Props.Float(1);
117+
volume = new Props.Float(1);
118+
AudioField = new Props.Field({
119+
placeholder: "None selected",
120+
notRequired: true,
121+
});
102122

103123
settingsSummary() {
104124
return html`<h3>
@@ -110,7 +130,7 @@ class Cue extends TreeBaseSwitchable {
110130
return [
111131
html`<div class="Cue">
112132
${this.Name.input()} ${this.Default.input()} ${this.CueType.input()}
113-
${this.subTemplate()}
133+
${this.subTemplate()} ${this.audibleTemplate()}
114134
</div>`,
115135
];
116136
}
@@ -120,6 +140,15 @@ class Cue extends TreeBaseSwitchable {
120140
return [];
121141
}
122142

143+
/** @returns {Hole[]} */
144+
audibleTemplate() {
145+
return [
146+
html`${this.SpeechField.input()} ${this.voiceURI.input()}
147+
${this.volume.input()} ${this.rate.input()} ${this.pitch.input()}
148+
${this.AudioField.input()}`,
149+
];
150+
}
151+
123152
get css() {
124153
return "";
125154
}

Diff for: src/components/access/method/keyHandler.js

+30-21
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ export class KeyHandler extends Handler {
6767
}
6868

6969
// build the debounced key event stream
70-
const keyEvents$ = /** @type RxJs.Observable<KeyboardEvent> */ (
70+
let events$ = /** @type RxJs.Observable<KeyboardEvent> */ (
7171
// start with the key down stream
7272
keyDown$.pipe(
7373
// merge with the key up stream
@@ -78,6 +78,12 @@ export class KeyHandler extends Handler {
7878
RxJs.tap((e) => e.preventDefault()),
7979
// remove any repeats
8080
RxJs.filter((e) => !e.repeat),
81+
)
82+
);
83+
84+
// Only debounce when required
85+
if (debounceInterval > 0) {
86+
events$ = events$.pipe(
8187
// group by the key
8288
RxJs.groupBy((e) => e.key),
8389
// process each group and merge the results
@@ -91,26 +97,29 @@ export class KeyHandler extends Handler {
9197
RxJs.distinctUntilKeyChanged("type"),
9298
),
9399
),
94-
RxJs.map((e) => {
95-
// add context info to event for use in the conditions and response
96-
/** @type {EventLike} */
97-
let kw = {
98-
type: e.type,
99-
target: null,
100-
timeStamp: e.timeStamp,
101-
access: {
102-
key: e.key,
103-
altKey: e.altKey,
104-
ctrlKey: e.ctrlKey,
105-
metaKey: e.metaKey,
106-
shiftKey: e.shiftKey,
107-
eventType: e.type,
108-
...method.pattern.getCurrentAccess(),
109-
},
110-
};
111-
return kw;
112-
}),
113-
)
100+
);
101+
}
102+
103+
const keyEvents$ = events$.pipe(
104+
RxJs.map((e) => {
105+
// add context info to event for use in the conditions and response
106+
/** @type {EventLike} */
107+
let kw = {
108+
type: e.type,
109+
target: null,
110+
timeStamp: e.timeStamp,
111+
access: {
112+
key: e.key,
113+
altKey: e.altKey,
114+
ctrlKey: e.ctrlKey,
115+
metaKey: e.metaKey,
116+
shiftKey: e.shiftKey,
117+
eventType: e.type,
118+
...method.pattern.getCurrentAccess(),
119+
},
120+
};
121+
return kw;
122+
}),
114123
);
115124
method.streams[streamName] = keyEvents$;
116125
}

Diff for: src/components/access/method/pointerHandler.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ export class PointerHandler extends Handler {
147147
accumulators.set(over.target, sum);
148148
const threshold = inOutThreshold;
149149
// exceeding the threshold triggers production of events
150-
if (sum > threshold) {
150+
if (sum >= threshold) {
151151
// clamp it at the threshold value
152152
accumulators.set(over.target, threshold);
153153
if (over.target != current.target) {

Diff for: src/components/access/pattern/index.js

+24-2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { TreeBase } from "components/treebase";
66
import defaultPatterns from "./defaultPatterns";
77
import { DesignerPanel } from "components/designer";
88
import { toggleIndicator } from "app/components/helpers";
9+
import { speak } from "components/speech";
10+
import { playAudio } from "components/audio";
911

1012
// only run one animation at a time
1113
let animationNonce = 0;
@@ -15,6 +17,7 @@ let animationNonce = 0;
1517
* @param {boolean} isGroup
1618
* */
1719
export function cueTarget(target, defaultValue, isGroup = false) {
20+
let fields = {};
1821
if (target instanceof HTMLButtonElement) {
1922
target.setAttribute("cue", defaultValue);
2023
const video = target.querySelector("video");
@@ -29,8 +32,27 @@ export function cueTarget(target, defaultValue, isGroup = false) {
2932
});
3033
}
3134
}
35+
fields = target.dataset;
3236
} else if (target instanceof Group) {
3337
target.cue(defaultValue);
38+
fields = target.access;
39+
}
40+
const cue = Globals.cues.keyToCue(defaultValue);
41+
if (!isGroup && cue) {
42+
if (cue.SpeechField.value) {
43+
const message = fields[cue.SpeechField.value.slice(1)];
44+
speak(
45+
message,
46+
cue.voiceURI.value,
47+
cue.pitch.value,
48+
cue.rate.value,
49+
cue.volume.value,
50+
);
51+
}
52+
if (cue.AudioField.value) {
53+
const file = fields[cue.AudioField.value.slice(1)] || "";
54+
playAudio(file);
55+
}
3456
}
3557
}
3658

@@ -57,7 +79,7 @@ export class Group {
5779
constructor(members, props) {
5880
/** @type {Target[]} */
5981
this.members = members;
60-
this.access = props;
82+
this.access = { GroupName: props.Name, ...props };
6183
}
6284

6385
get length() {
@@ -351,7 +373,7 @@ export class PatternManager extends PatternBase {
351373
current.dispatchEvent(new Event("Activate"));
352374
} else {
353375
const name = current.dataset.ComponentName;
354-
Globals.actions.applyRules(name || "", "press", current.dataset);
376+
Globals.actions.applyRules(name || "", "press", { ...current.dataset });
355377
}
356378
}
357379
this.cue();

Diff for: src/components/audio.js

+8-7
Original file line numberDiff line numberDiff line change
@@ -5,19 +5,20 @@ import { html } from "uhtml";
55

66
import Globals from "app/globals";
77

8+
/** @param {string} filename */
9+
export async function playAudio(filename) {
10+
const sound = await db.getAudio(filename);
11+
sound.play();
12+
}
13+
814
class Audio extends TreeBase {
915
stateName = new Props.String("$Audio");
1016

11-
async playAudio() {
12-
const { state } = Globals;
13-
const fileName = state.get(this.stateName.value) || "";
14-
(await db.getAudio(fileName)).play();
15-
}
16-
1717
template() {
1818
const { state } = Globals;
1919
if (state.hasBeenUpdated(this.stateName.value)) {
20-
this.playAudio();
20+
const filename = state.get(this.stateName.value) || "";
21+
playAudio(filename);
2122
}
2223
return html`<div />`;
2324
}

Diff for: src/components/props.js

+9-3
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ import { getColor, isValidColor, styleString } from "./style";
2323
* @property {string} [datalist]
2424
* @property {number} [min]
2525
* @property {number} [max]
26+
* @property {boolean} [notRequired]
27+
* @property {string[]} [addedFields]
2628
*/
2729

2830
/**
@@ -303,14 +305,14 @@ export class Select extends Prop {
303305
return this.labeled(
304306
html`<select
305307
id=${this.id}
306-
required
308+
?required=${!this.options.notRequired}
307309
title=${this.options.title}
308310
@change=${({ target }) => {
309311
this._value = target.value;
310312
this.update();
311313
}}
312314
>
313-
<option value="" disabled ?selected=${!choices.has(this._value)}>
315+
<option value="" ?selected=${!choices.has(this._value)}>
314316
${this.options.placeholder || "Choose one..."}
315317
</option>
316318
${[...choices.entries()].map(
@@ -334,8 +336,12 @@ export class Field extends Select {
334336
* @param {PropOptions} options
335337
*/
336338
constructor(options = {}) {
339+
const addedFields = options.addedFields || [];
337340
super(
338-
() => toMap([...Globals.data.allFields, "#ComponentName"].sort()),
341+
() =>
342+
toMap(
343+
[...Globals.data.allFields, "#ComponentName", ...addedFields].sort(),
344+
),
339345
options,
340346
);
341347
}

Diff for: src/components/speech.js

+31-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ import * as Props from "./props";
55
import { toString } from "./slots";
66
import { cursor } from "./notes";
77

8+
/**
9+
* @param {string} message
10+
* @param {string} voiceURI
11+
* @param {number} pitch
12+
* @param {number} rate
13+
* @param {number} volume
14+
*/
15+
export async function speak(message, voiceURI, pitch, rate, volume) {
16+
if (!message) return;
17+
const voices = await getVoices();
18+
const voice = voiceURI && voices.find((voice) => voice.voiceURI == voiceURI);
19+
const utterance = new SpeechSynthesisUtterance(message);
20+
if (voice) {
21+
utterance.voice = voice;
22+
utterance.lang = voice.lang;
23+
}
24+
utterance.pitch = pitch;
25+
utterance.rate = rate;
26+
utterance.volume = volume;
27+
speechSynthesis.cancel();
28+
speechSynthesis.speak(utterance);
29+
}
30+
831
class Speech extends TreeBase {
932
stateName = new Props.String("$Speak");
1033
voiceURI = new Props.Voice("", { label: "Voice" });
@@ -53,7 +76,14 @@ class Speech extends TreeBase {
5376
template() {
5477
const { state } = Globals;
5578
if (state.hasBeenUpdated(this.stateName.value)) {
56-
this.speak();
79+
const message = toString(state.get(this.stateName.value));
80+
speak(
81+
message,
82+
this.voiceURI.value,
83+
this.pitch.value,
84+
this.rate.value,
85+
this.volume.value,
86+
);
5787
}
5888
return html`<div />`;
5989
}

Diff for: src/data.js

+1
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ export const comparators = {
1313
"starts with": (f, v) => f.toUpperCase().startsWith(v.toUpperCase()),
1414
empty: (f) => !f,
1515
contains: (f, v) => f.toLowerCase().includes(v.toLowerCase()),
16+
is_contained_in: (f, v) => v.toLowerCase().includes(f.toLowerCase()),
1617
"not empty": (f) => !!f,
1718
"less than": (f, v) => collatorNumber.compare(f, v) < 0,
1819
"greater than": (f, v) => collatorNumber.compare(f, v) > 0,

Diff for: src/eval.js

+4-4
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export function updateString(f) {
88
return function (value) {
99
/** @param {string | undefined} old */
1010
return function (old) {
11-
return f(old || "", value);
11+
return f(old || "", value || "");
1212
};
1313
};
1414
}
@@ -18,7 +18,7 @@ function updateNumber(f) {
1818
return function (value) {
1919
/** @param {number | undefined} old */
2020
return function (old) {
21-
return f(old || 0, value);
21+
return f(old || 0, value || 0);
2222
};
2323
};
2424
}
@@ -151,7 +151,7 @@ const variableHandler = {
151151
if (prop.startsWith("$")) {
152152
return Object.getOwnPropertyDescriptor(target.states, prop);
153153
} else if (prop.startsWith("_")) {
154-
return Object.getOwnPropertyDescriptor(target.data, prop.slice(1));
154+
return { configurable: true, enumerable: true };
155155
} else {
156156
return Object.getOwnPropertyDescriptor(Functions, prop);
157157
}
@@ -175,7 +175,7 @@ export function compileExpression(expression) {
175175
"states" in context
176176
? { ...Globals.state.values, ...context.states }
177177
: Globals.state.values;
178-
let data = context.data ?? [];
178+
let data = context.data ?? {};
179179
const r = exp(
180180
new Proxy(
181181
{

Diff for: src/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@
1313
"author": "",
1414
"license": "ISC",
1515
"devDependencies": {
16-
"vite": "^5.3.1",
17-
"typescript": "^5.5.2"
16+
"typescript": "^5.6.2",
17+
"vite": "^5.4.6"
1818
},
1919
"dependencies": {
20+
"@ungap/custom-elements": "^1.3.0",
2021
"angular-expressions": "^1.2.1",
2122
"browser-fs-access": "^0.35.0",
2223
"fflate": "^0.8.2",
@@ -25,9 +26,8 @@
2526
"rxjs": "^7.8.1",
2627
"stacktrace-js": "^2.0.2",
2728
"tracky-mouse": "^1.0.0",
28-
"uhtml": "^4.5.9",
29-
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz",
30-
"@ungap/custom-elements": "^1.3.0"
29+
"uhtml": "^4.5.11",
30+
"xlsx": "https://cdn.sheetjs.com/xlsx-0.19.3/xlsx-0.19.3.tgz"
3131
},
3232
"type": "module"
3333
}

0 commit comments

Comments
 (0)