virtual-keypad 5.12.2 → 5.13.0
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/LICENSE +21 -21
- package/README.md +45 -45
- package/dist/404.html +14 -14
- package/dist/fonts/Pelli-EyeChart.svg +494 -494
- package/dist/fonts/Sloan.svg +138 -138
- package/dist/index.html +22 -22
- package/dist/keypad.html +58 -58
- package/dist/main.js +1 -1
- package/dist/main.js.LICENSE.txt +8 -8
- package/dist/main.js.map +1 -1
- package/dist/receiver.html +146 -118
- package/dist/server.js +66 -66
- package/package.json +36 -36
- package/src/keypad.css +108 -108
- package/src/keypad.js +385 -383
- package/src/keypadPeer.js +189 -189
- package/src/main.js +4 -4
- package/src/maxKeySize.js +104 -104
- package/src/receiver.css +11 -11
- package/src/receiver.js +223 -223
- package/webpack.common.js +30 -30
- package/webpack.dev.js +9 -9
- package/webpack.prod.js +6 -6
package/src/keypadPeer.js
CHANGED
|
@@ -1,189 +1,189 @@
|
|
|
1
|
-
// require("peerjs");
|
|
2
|
-
// import { QRCode } from "qrcode";
|
|
3
|
-
import { Peer } from "peerjs";
|
|
4
|
-
|
|
5
|
-
export let keypadUrl;
|
|
6
|
-
if (process.env.NODE_ENV !== "production") {
|
|
7
|
-
keypadUrl = "http://localhost:8080/keypad.html?";
|
|
8
|
-
} else {
|
|
9
|
-
keypadUrl = "https://keypad.website/keypad?";
|
|
10
|
-
}
|
|
11
|
-
// -- Parameters --
|
|
12
|
-
|
|
13
|
-
export class KeypadPeer {
|
|
14
|
-
constructor(
|
|
15
|
-
parameters = {
|
|
16
|
-
keypadUrl: keypadUrl,
|
|
17
|
-
targetElementId: null,
|
|
18
|
-
}
|
|
19
|
-
) {
|
|
20
|
-
this.conn = null;
|
|
21
|
-
this.lastPeerId = null;
|
|
22
|
-
this.keypadUrl = parameters.hasOwnProperty("keypadUrl")
|
|
23
|
-
? parameters.keypadUrl
|
|
24
|
-
: keypadUrl;
|
|
25
|
-
this.targetElement = parameters.hasOwnProperty("targetElementId")
|
|
26
|
-
? parameters.targetElementId
|
|
27
|
-
: null;
|
|
28
|
-
// TODO add support for ttd (ms)
|
|
29
|
-
this.ttd = parameters.hasOwnProperty("ttd")
|
|
30
|
-
? parameters.hasOwnProperty("ttd")
|
|
31
|
-
: 8000;
|
|
32
|
-
// TODO add support for heartRate (ms)
|
|
33
|
-
this.heartbeatIntervalMs = parameters.hasOwnProperty("heartRate")
|
|
34
|
-
? parameters.hasOwnProperty("heartRate")
|
|
35
|
-
: 2000;
|
|
36
|
-
this.lastHeartbeat = performance.now();
|
|
37
|
-
this.heartBeatInterval = undefined;
|
|
38
|
-
this.heartCheckInterval = undefined;
|
|
39
|
-
|
|
40
|
-
/* Create the Peer object for our end of the connection. */
|
|
41
|
-
this.peer = new Peer(null, {
|
|
42
|
-
pingInterval: this.heartbeatIntervalMs,
|
|
43
|
-
debug: 2,
|
|
44
|
-
config: {
|
|
45
|
-
iceServers: [
|
|
46
|
-
{
|
|
47
|
-
urls: "stun:stun.relay.metered.ca:80",
|
|
48
|
-
},
|
|
49
|
-
{
|
|
50
|
-
urls: "turn:global.relay.metered.ca:80",
|
|
51
|
-
username: "de884cfc34189cdf1a5dd616",
|
|
52
|
-
credential: "IcOpouU9/TYBmpHU",
|
|
53
|
-
},
|
|
54
|
-
{
|
|
55
|
-
urls: "turn:global.relay.metered.ca:80?transport=tcp",
|
|
56
|
-
username: "de884cfc34189cdf1a5dd616",
|
|
57
|
-
credential: "IcOpouU9/TYBmpHU",
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
urls: "turn:global.relay.metered.ca:443",
|
|
61
|
-
username: "de884cfc34189cdf1a5dd616",
|
|
62
|
-
credential: "IcOpouU9/TYBmpHU",
|
|
63
|
-
},
|
|
64
|
-
{
|
|
65
|
-
urls: "turns:global.relay.metered.ca:443?transport=tcp",
|
|
66
|
-
username: "de884cfc34189cdf1a5dd616",
|
|
67
|
-
credential: "IcOpouU9/TYBmpHU",
|
|
68
|
-
},
|
|
69
|
-
],
|
|
70
|
-
},
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
this.alphabet = null;
|
|
74
|
-
this.font = null;
|
|
75
|
-
this.keypressFeedbackSound =
|
|
76
|
-
"data:audio/mpeg;base64,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VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSZIIP9BFGHYksMlIAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==";
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
displayUpdate = (message, append = false) => {
|
|
80
|
-
// If the specified elem exists, update that elem
|
|
81
|
-
if (!!document.getElementById(this.targetElement)) {
|
|
82
|
-
const displayElement = document.getElementById(this.targetElement);
|
|
83
|
-
if (append) {
|
|
84
|
-
const oldInnerText = displayElement.innerText;
|
|
85
|
-
displayElement.innerText = message + "\n" + oldInnerText;
|
|
86
|
-
} else {
|
|
87
|
-
displayElement.innerText = message;
|
|
88
|
-
}
|
|
89
|
-
} else {
|
|
90
|
-
console.log("MESSAGE: ", message);
|
|
91
|
-
}
|
|
92
|
-
};
|
|
93
|
-
onPeerDisconnected = () => {
|
|
94
|
-
this.displayUpdate("Connection lost. Please reconnect");
|
|
95
|
-
// Workaround for peer.reconnect deleting previous id
|
|
96
|
-
this.peer.id = this.lastPeerId;
|
|
97
|
-
this.peer._lastServerId = this.lastPeerId;
|
|
98
|
-
this.peer.reconnect();
|
|
99
|
-
};
|
|
100
|
-
onPeerClose = () => {
|
|
101
|
-
this.displayUpdate("Connection closed");
|
|
102
|
-
this.conn = null;
|
|
103
|
-
};
|
|
104
|
-
onPeerError = (err) => {
|
|
105
|
-
this.displayUpdate(err);
|
|
106
|
-
alert("" + err);
|
|
107
|
-
};
|
|
108
|
-
parseParams = (params) => {
|
|
109
|
-
const font = params.get("font");
|
|
110
|
-
const alphabet = this.checkAlphabet(
|
|
111
|
-
decodeURIComponent(params.get("alphabet")).split(",")
|
|
112
|
-
);
|
|
113
|
-
const peerId = params.get("peerID");
|
|
114
|
-
return { alphabet: alphabet, font: font, peerId: peerId };
|
|
115
|
-
};
|
|
116
|
-
queryStringFromObject = (params) => {
|
|
117
|
-
return Object.keys(params)
|
|
118
|
-
.map((key) => key + "=" + encodeURIComponent(params[key]))
|
|
119
|
-
.join("&");
|
|
120
|
-
};
|
|
121
|
-
checkAlphabet = (proposedAlphabet) => {
|
|
122
|
-
let validAlphabet;
|
|
123
|
-
if (Array.isArray(proposedAlphabet)) {
|
|
124
|
-
// ARRAY : good
|
|
125
|
-
// FUTURE verify that symbols are displayable in desired font
|
|
126
|
-
validAlphabet = proposedAlphabet;
|
|
127
|
-
} else if (typeof proposedAlphabet == "string") {
|
|
128
|
-
// STRING : ok
|
|
129
|
-
if (
|
|
130
|
-
proposedAlphabet.toUpperCase() === "SPACE" ||
|
|
131
|
-
proposedAlphabet.toUpperCase() == "RETURN"
|
|
132
|
-
) {
|
|
133
|
-
validAlphabet = [proposedAlphabet];
|
|
134
|
-
} else {
|
|
135
|
-
validAlphabet = proposedAlphabet.split("");
|
|
136
|
-
}
|
|
137
|
-
} else {
|
|
138
|
-
// SOMETHING ELSE : bad
|
|
139
|
-
console.error(
|
|
140
|
-
"Error! Alphabet must be specified as an array of symbols, including 'RETURN', 'SPACE'"
|
|
141
|
-
);
|
|
142
|
-
validAlphabet = [];
|
|
143
|
-
}
|
|
144
|
-
// Return unique elements, see: https://stackoverflow.com/questions/11246758/how-to-get-unique-values-in-an-array
|
|
145
|
-
const uniqueValidAlphabet = [...new Set(validAlphabet)];
|
|
146
|
-
|
|
147
|
-
// Order alphabet so that if 'SPACE' and 'RETURN' are in the list, they are correctly positioned
|
|
148
|
-
if ("SPACE" in uniqueValidAlphabet) {
|
|
149
|
-
uniqueValidAlphabet = moveElementToEndOfArray(
|
|
150
|
-
uniqueValidAlphabet,
|
|
151
|
-
"SPACE"
|
|
152
|
-
);
|
|
153
|
-
}
|
|
154
|
-
if ("RETURN" in uniqueValidAlphabet) {
|
|
155
|
-
uniqueValidAlphabet = moveElementToEndOfArray(
|
|
156
|
-
uniqueValidAlphabet,
|
|
157
|
-
"RETURN"
|
|
158
|
-
);
|
|
159
|
-
}
|
|
160
|
-
return uniqueValidAlphabet;
|
|
161
|
-
};
|
|
162
|
-
_setupHeartBeatIntervals = () => {
|
|
163
|
-
this.heartBeatInterval = setInterval(
|
|
164
|
-
() => this.conn?.send({ message: "Heartbeat" }),
|
|
165
|
-
this.heartbeatIntervalMs
|
|
166
|
-
);
|
|
167
|
-
|
|
168
|
-
this.heartCheckInterval = setInterval(() => {
|
|
169
|
-
const timeSinceHeartbeatMs = performance.now() - this.lastHeartbeat;
|
|
170
|
-
if (timeSinceHeartbeatMs > this.ttd) {
|
|
171
|
-
console.log("Closing connection due to lack of heartbeat.");
|
|
172
|
-
this.conn?.close();
|
|
173
|
-
this.conn = undefined;
|
|
174
|
-
clearInterval(this.heartBeatInterval);
|
|
175
|
-
clearInterval(this.heartCheckInterval);
|
|
176
|
-
this.heartBeatInterval = undefined;
|
|
177
|
-
this.heartCheckInterval = undefined;
|
|
178
|
-
}
|
|
179
|
-
}, this.ttd);
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const moveElementToEndOfArray = (array, element) => {
|
|
184
|
-
return [
|
|
185
|
-
...array.slice(0, array.indexOf(element)),
|
|
186
|
-
...array.slice(array.indexOf(element) + 1),
|
|
187
|
-
element,
|
|
188
|
-
];
|
|
189
|
-
};
|
|
1
|
+
// require("peerjs");
|
|
2
|
+
// import { QRCode } from "qrcode";
|
|
3
|
+
import { Peer } from "peerjs";
|
|
4
|
+
|
|
5
|
+
export let keypadUrl;
|
|
6
|
+
if (process.env.NODE_ENV !== "production") {
|
|
7
|
+
keypadUrl = "http://localhost:8080/keypad.html?";
|
|
8
|
+
} else {
|
|
9
|
+
keypadUrl = "https://keypad.website/keypad?";
|
|
10
|
+
}
|
|
11
|
+
// -- Parameters --
|
|
12
|
+
|
|
13
|
+
export class KeypadPeer {
|
|
14
|
+
constructor(
|
|
15
|
+
parameters = {
|
|
16
|
+
keypadUrl: keypadUrl,
|
|
17
|
+
targetElementId: null,
|
|
18
|
+
}
|
|
19
|
+
) {
|
|
20
|
+
this.conn = null;
|
|
21
|
+
this.lastPeerId = null;
|
|
22
|
+
this.keypadUrl = parameters.hasOwnProperty("keypadUrl")
|
|
23
|
+
? parameters.keypadUrl
|
|
24
|
+
: keypadUrl;
|
|
25
|
+
this.targetElement = parameters.hasOwnProperty("targetElementId")
|
|
26
|
+
? parameters.targetElementId
|
|
27
|
+
: null;
|
|
28
|
+
// TODO add support for ttd (ms)
|
|
29
|
+
this.ttd = parameters.hasOwnProperty("ttd")
|
|
30
|
+
? parameters.hasOwnProperty("ttd")
|
|
31
|
+
: 8000;
|
|
32
|
+
// TODO add support for heartRate (ms)
|
|
33
|
+
this.heartbeatIntervalMs = parameters.hasOwnProperty("heartRate")
|
|
34
|
+
? parameters.hasOwnProperty("heartRate")
|
|
35
|
+
: 2000;
|
|
36
|
+
this.lastHeartbeat = performance.now();
|
|
37
|
+
this.heartBeatInterval = undefined;
|
|
38
|
+
this.heartCheckInterval = undefined;
|
|
39
|
+
|
|
40
|
+
/* Create the Peer object for our end of the connection. */
|
|
41
|
+
this.peer = new Peer(null, {
|
|
42
|
+
pingInterval: this.heartbeatIntervalMs,
|
|
43
|
+
debug: 2,
|
|
44
|
+
config: {
|
|
45
|
+
iceServers: [
|
|
46
|
+
{
|
|
47
|
+
urls: "stun:stun.relay.metered.ca:80",
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
urls: "turn:global.relay.metered.ca:80",
|
|
51
|
+
username: "de884cfc34189cdf1a5dd616",
|
|
52
|
+
credential: "IcOpouU9/TYBmpHU",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
urls: "turn:global.relay.metered.ca:80?transport=tcp",
|
|
56
|
+
username: "de884cfc34189cdf1a5dd616",
|
|
57
|
+
credential: "IcOpouU9/TYBmpHU",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
urls: "turn:global.relay.metered.ca:443",
|
|
61
|
+
username: "de884cfc34189cdf1a5dd616",
|
|
62
|
+
credential: "IcOpouU9/TYBmpHU",
|
|
63
|
+
},
|
|
64
|
+
{
|
|
65
|
+
urls: "turns:global.relay.metered.ca:443?transport=tcp",
|
|
66
|
+
username: "de884cfc34189cdf1a5dd616",
|
|
67
|
+
credential: "IcOpouU9/TYBmpHU",
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
},
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
this.alphabet = null;
|
|
74
|
+
this.font = null;
|
|
75
|
+
this.keypressFeedbackSound =
|
|
76
|
+
"data:audio/mpeg;base64,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VVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVV//uSZIIP9BFGHYksMlIAAA0gAAABAAABpAAAACAAADSAAAAEVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVVQ==";
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
displayUpdate = (message, append = false) => {
|
|
80
|
+
// If the specified elem exists, update that elem
|
|
81
|
+
if (!!document.getElementById(this.targetElement)) {
|
|
82
|
+
const displayElement = document.getElementById(this.targetElement);
|
|
83
|
+
if (append) {
|
|
84
|
+
const oldInnerText = displayElement.innerText;
|
|
85
|
+
displayElement.innerText = message + "\n" + oldInnerText;
|
|
86
|
+
} else {
|
|
87
|
+
displayElement.innerText = message;
|
|
88
|
+
}
|
|
89
|
+
} else {
|
|
90
|
+
console.log("MESSAGE: ", message);
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
onPeerDisconnected = () => {
|
|
94
|
+
this.displayUpdate("Connection lost. Please reconnect");
|
|
95
|
+
// Workaround for peer.reconnect deleting previous id
|
|
96
|
+
this.peer.id = this.lastPeerId;
|
|
97
|
+
this.peer._lastServerId = this.lastPeerId;
|
|
98
|
+
this.peer.reconnect();
|
|
99
|
+
};
|
|
100
|
+
onPeerClose = () => {
|
|
101
|
+
this.displayUpdate("Connection closed");
|
|
102
|
+
this.conn = null;
|
|
103
|
+
};
|
|
104
|
+
onPeerError = (err) => {
|
|
105
|
+
this.displayUpdate(err);
|
|
106
|
+
alert("" + err);
|
|
107
|
+
};
|
|
108
|
+
parseParams = (params) => {
|
|
109
|
+
const font = params.get("font");
|
|
110
|
+
const alphabet = this.checkAlphabet(
|
|
111
|
+
decodeURIComponent(params.get("alphabet")).split(",")
|
|
112
|
+
);
|
|
113
|
+
const peerId = params.get("peerID");
|
|
114
|
+
return { alphabet: alphabet, font: font, peerId: peerId };
|
|
115
|
+
};
|
|
116
|
+
queryStringFromObject = (params) => {
|
|
117
|
+
return Object.keys(params)
|
|
118
|
+
.map((key) => key + "=" + encodeURIComponent(params[key]))
|
|
119
|
+
.join("&");
|
|
120
|
+
};
|
|
121
|
+
checkAlphabet = (proposedAlphabet) => {
|
|
122
|
+
let validAlphabet;
|
|
123
|
+
if (Array.isArray(proposedAlphabet)) {
|
|
124
|
+
// ARRAY : good
|
|
125
|
+
// FUTURE verify that symbols are displayable in desired font
|
|
126
|
+
validAlphabet = proposedAlphabet;
|
|
127
|
+
} else if (typeof proposedAlphabet == "string") {
|
|
128
|
+
// STRING : ok
|
|
129
|
+
if (
|
|
130
|
+
proposedAlphabet.toUpperCase() === "SPACE" ||
|
|
131
|
+
proposedAlphabet.toUpperCase() == "RETURN"
|
|
132
|
+
) {
|
|
133
|
+
validAlphabet = [proposedAlphabet];
|
|
134
|
+
} else {
|
|
135
|
+
validAlphabet = proposedAlphabet.split("");
|
|
136
|
+
}
|
|
137
|
+
} else {
|
|
138
|
+
// SOMETHING ELSE : bad
|
|
139
|
+
console.error(
|
|
140
|
+
"Error! Alphabet must be specified as an array of symbols, including 'RETURN', 'SPACE'"
|
|
141
|
+
);
|
|
142
|
+
validAlphabet = [];
|
|
143
|
+
}
|
|
144
|
+
// Return unique elements, see: https://stackoverflow.com/questions/11246758/how-to-get-unique-values-in-an-array
|
|
145
|
+
const uniqueValidAlphabet = [...new Set(validAlphabet)];
|
|
146
|
+
|
|
147
|
+
// Order alphabet so that if 'SPACE' and 'RETURN' are in the list, they are correctly positioned
|
|
148
|
+
if ("SPACE" in uniqueValidAlphabet) {
|
|
149
|
+
uniqueValidAlphabet = moveElementToEndOfArray(
|
|
150
|
+
uniqueValidAlphabet,
|
|
151
|
+
"SPACE"
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
if ("RETURN" in uniqueValidAlphabet) {
|
|
155
|
+
uniqueValidAlphabet = moveElementToEndOfArray(
|
|
156
|
+
uniqueValidAlphabet,
|
|
157
|
+
"RETURN"
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
return uniqueValidAlphabet;
|
|
161
|
+
};
|
|
162
|
+
_setupHeartBeatIntervals = () => {
|
|
163
|
+
this.heartBeatInterval = setInterval(
|
|
164
|
+
() => this.conn?.send({ message: "Heartbeat" }),
|
|
165
|
+
this.heartbeatIntervalMs
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
this.heartCheckInterval = setInterval(() => {
|
|
169
|
+
const timeSinceHeartbeatMs = performance.now() - this.lastHeartbeat;
|
|
170
|
+
if (timeSinceHeartbeatMs > this.ttd) {
|
|
171
|
+
console.log("Closing connection due to lack of heartbeat.");
|
|
172
|
+
this.conn?.close();
|
|
173
|
+
this.conn = undefined;
|
|
174
|
+
clearInterval(this.heartBeatInterval);
|
|
175
|
+
clearInterval(this.heartCheckInterval);
|
|
176
|
+
this.heartBeatInterval = undefined;
|
|
177
|
+
this.heartCheckInterval = undefined;
|
|
178
|
+
}
|
|
179
|
+
}, this.ttd);
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
const moveElementToEndOfArray = (array, element) => {
|
|
184
|
+
return [
|
|
185
|
+
...array.slice(0, array.indexOf(element)),
|
|
186
|
+
...array.slice(array.indexOf(element) + 1),
|
|
187
|
+
element,
|
|
188
|
+
];
|
|
189
|
+
};
|
package/src/main.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
// import './keypadPeer.js';
|
|
2
|
-
import { Keypad } from './keypad.js';
|
|
3
|
-
import { Receiver } from './receiver.js';
|
|
4
|
-
|
|
1
|
+
// import './keypadPeer.js';
|
|
2
|
+
import { Keypad } from './keypad.js';
|
|
3
|
+
import { Receiver } from './receiver.js';
|
|
4
|
+
|
|
5
5
|
export { Receiver, Keypad };
|
package/src/maxKeySize.js
CHANGED
|
@@ -1,105 +1,105 @@
|
|
|
1
|
-
const getKeysDimensions = (elem, n, aspect=0.5) => {
|
|
2
|
-
/**** translation of maxKeySize.m, original provided courtesy of Prof Denis Pelli ****/
|
|
3
|
-
let keyHeightPx;
|
|
4
|
-
|
|
5
|
-
// key = aspect*keyHeightPx × keyHeightPx
|
|
6
|
-
const widthPx = elem.clientWidth;
|
|
7
|
-
const heightPx = elem.clientHeight;
|
|
8
|
-
let screenArea=widthPx*heightPx
|
|
9
|
-
|
|
10
|
-
keyHeightPx = (-1+Math.sqrt(1+4*n*heightPx/widthPx))/(2*n/widthPx);
|
|
11
|
-
keyHeightPx = (-1+Math.sqrt(1+4*n*heightPx*aspect/widthPx))/(2*aspect*n/widthPx);
|
|
12
|
-
|
|
13
|
-
while (n > (Math.floor(heightPx/keyHeightPx - 1)*Math.floor(widthPx/(aspect*keyHeightPx)))){
|
|
14
|
-
// Round size down so that screen is a multiple.
|
|
15
|
-
const ss=[heightPx/Math.ceil(heightPx/keyHeightPx), widthPx/Math.ceil(widthPx/(aspect*keyHeightPx))];
|
|
16
|
-
for (let i=0; i<=ss.length; i++) {
|
|
17
|
-
// We’ve already rejected the current size, so only consider smaller sizes.
|
|
18
|
-
// This guarantees that sizePx will decrease on every loop iteration.
|
|
19
|
-
if (ss[i]>=keyHeightPx) ss[i]=0;
|
|
20
|
-
}
|
|
21
|
-
// We want the largest possible size so try the largest of the current options.
|
|
22
|
-
keyHeightPx=Math.max(...ss);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// Compute efficiency as area covered by keys divided by screen area.
|
|
26
|
-
// First term for n keys. Second term is for Space and Return, which fully
|
|
27
|
-
// occupy their row.
|
|
28
|
-
const areaOfKeys=aspect*keyHeightPx*keyHeightPx*n + widthPx*keyHeightPx;
|
|
29
|
-
screenArea=heightPx*widthPx;
|
|
30
|
-
const numKeysHorizontally = Math.floor(widthPx/(aspect*keyHeightPx));
|
|
31
|
-
const numKeysVertically = Math.floor(heightPx/keyHeightPx-1);
|
|
32
|
-
const efficiency = 100*areaOfKeys/screenArea;
|
|
33
|
-
|
|
34
|
-
return {keyHeightPx: keyHeightPx, cols: numKeysHorizontally, rows:numKeysVertically, widthPx: widthPx, heightPx: heightPx}
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
export const applyMaxKeySize = (numberOfKeys) => {
|
|
38
|
-
const aspect = 1;
|
|
39
|
-
let margin = 5;
|
|
40
|
-
const keysElem = document.getElementById("keypad");
|
|
41
|
-
const {keyHeightPx, cols, rows, widthPx, heightPx} = getKeysDimensions(keysElem, numberOfKeys, aspect);
|
|
42
|
-
const keyElems = [...keysElem.getElementsByClassName("response-button")];
|
|
43
|
-
const controlKeyElemsMask = keyElems.map(e => e.parentNode.id === "keypad-control-keys");
|
|
44
|
-
const gridCoords = keyElems.filter((k,i) => !controlKeyElemsMask[i]).map((k,i) => [Math.floor(i/cols), i%cols]);
|
|
45
|
-
const widthUsed = cols*(keyHeightPx*aspect);
|
|
46
|
-
const heightUsed = rows*keyHeightPx + keyHeightPx;
|
|
47
|
-
const freeHeight = heightPx - heightUsed;
|
|
48
|
-
const freeWidth = widthPx - widthUsed;
|
|
49
|
-
const verticalMarginOffset = Math.floor(freeHeight/2);
|
|
50
|
-
const horizontalMarginOffset = Math.floor(freeWidth/2);
|
|
51
|
-
|
|
52
|
-
let keyFontSize;
|
|
53
|
-
let j=0;
|
|
54
|
-
keyElems.forEach((k,i) => {
|
|
55
|
-
k.style.position = "fixed";
|
|
56
|
-
const controlKey = controlKeyElemsMask[i];
|
|
57
|
-
let top, left, width;
|
|
58
|
-
if (controlKey) {
|
|
59
|
-
top = heightPx-keyHeightPx;
|
|
60
|
-
width = widthPx/2 - horizontalMarginOffset - margin;
|
|
61
|
-
const m = horizontalMarginOffset + margin*0.5;
|
|
62
|
-
left = (k.id.toLowerCase().includes("space") ? m : m + width + margin);
|
|
63
|
-
|
|
64
|
-
const f = getLargeFontSize(k, width, keyHeightPx-margin);
|
|
65
|
-
k.style.fontSize = f + "px";
|
|
66
|
-
k.style.borderRadius = "25px";
|
|
67
|
-
k.style.height = (keyHeightPx - margin)+ "px";
|
|
68
|
-
} else {
|
|
69
|
-
const height = keyHeightPx - margin;
|
|
70
|
-
width = height*aspect;
|
|
71
|
-
const [y,x] = gridCoords[j];
|
|
72
|
-
j += 1;
|
|
73
|
-
top = y*height + ((y+1)*margin) + verticalMarginOffset - margin/2;
|
|
74
|
-
left = x*width + ((x+1)*margin) + horizontalMarginOffset - margin/2;
|
|
75
|
-
if (!keyFontSize) {
|
|
76
|
-
keyFontSize = getLargeFontSize(k, width, height);
|
|
77
|
-
}
|
|
78
|
-
k.style.height = height + "px";
|
|
79
|
-
k.style.fontSize = height/2 + "px";
|
|
80
|
-
}
|
|
81
|
-
k.style.width = width + "px";
|
|
82
|
-
k.style.top = top + "px";
|
|
83
|
-
k.style.left = left + "px";
|
|
84
|
-
k.style.visibility = "visible";
|
|
85
|
-
});
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
const getLargeFontSize = (k, width, height) => {
|
|
89
|
-
k.style.height = "auto";
|
|
90
|
-
k.style.width = "auto";
|
|
91
|
-
k.style.whiteSpace = "nowrap";
|
|
92
|
-
|
|
93
|
-
// Set to a nominal font size, s
|
|
94
|
-
const s = 20;
|
|
95
|
-
k.style.fontSize = s + "px";
|
|
96
|
-
// Measure width of elem, w
|
|
97
|
-
const w = k.getBoundingClientRect().width;
|
|
98
|
-
const h = k.getBoundingClientRect().height
|
|
99
|
-
const rW = 1/(w/width)
|
|
100
|
-
const rH = 1/(h/height)
|
|
101
|
-
const r = Math.min(rW, rH)
|
|
102
|
-
// Set font size to s*r
|
|
103
|
-
const f = Math.floor(s*r);
|
|
104
|
-
return f;
|
|
1
|
+
const getKeysDimensions = (elem, n, aspect=0.5) => {
|
|
2
|
+
/**** translation of maxKeySize.m, original provided courtesy of Prof Denis Pelli ****/
|
|
3
|
+
let keyHeightPx;
|
|
4
|
+
|
|
5
|
+
// key = aspect*keyHeightPx × keyHeightPx
|
|
6
|
+
const widthPx = elem.clientWidth;
|
|
7
|
+
const heightPx = elem.clientHeight;
|
|
8
|
+
let screenArea=widthPx*heightPx
|
|
9
|
+
|
|
10
|
+
keyHeightPx = (-1+Math.sqrt(1+4*n*heightPx/widthPx))/(2*n/widthPx);
|
|
11
|
+
keyHeightPx = (-1+Math.sqrt(1+4*n*heightPx*aspect/widthPx))/(2*aspect*n/widthPx);
|
|
12
|
+
|
|
13
|
+
while (n > (Math.floor(heightPx/keyHeightPx - 1)*Math.floor(widthPx/(aspect*keyHeightPx)))){
|
|
14
|
+
// Round size down so that screen is a multiple.
|
|
15
|
+
const ss=[heightPx/Math.ceil(heightPx/keyHeightPx), widthPx/Math.ceil(widthPx/(aspect*keyHeightPx))];
|
|
16
|
+
for (let i=0; i<=ss.length; i++) {
|
|
17
|
+
// We’ve already rejected the current size, so only consider smaller sizes.
|
|
18
|
+
// This guarantees that sizePx will decrease on every loop iteration.
|
|
19
|
+
if (ss[i]>=keyHeightPx) ss[i]=0;
|
|
20
|
+
}
|
|
21
|
+
// We want the largest possible size so try the largest of the current options.
|
|
22
|
+
keyHeightPx=Math.max(...ss);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Compute efficiency as area covered by keys divided by screen area.
|
|
26
|
+
// First term for n keys. Second term is for Space and Return, which fully
|
|
27
|
+
// occupy their row.
|
|
28
|
+
const areaOfKeys=aspect*keyHeightPx*keyHeightPx*n + widthPx*keyHeightPx;
|
|
29
|
+
screenArea=heightPx*widthPx;
|
|
30
|
+
const numKeysHorizontally = Math.floor(widthPx/(aspect*keyHeightPx));
|
|
31
|
+
const numKeysVertically = Math.floor(heightPx/keyHeightPx-1);
|
|
32
|
+
const efficiency = 100*areaOfKeys/screenArea;
|
|
33
|
+
|
|
34
|
+
return {keyHeightPx: keyHeightPx, cols: numKeysHorizontally, rows:numKeysVertically, widthPx: widthPx, heightPx: heightPx}
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export const applyMaxKeySize = (numberOfKeys) => {
|
|
38
|
+
const aspect = 1;
|
|
39
|
+
let margin = 5;
|
|
40
|
+
const keysElem = document.getElementById("keypad");
|
|
41
|
+
const {keyHeightPx, cols, rows, widthPx, heightPx} = getKeysDimensions(keysElem, numberOfKeys, aspect);
|
|
42
|
+
const keyElems = [...keysElem.getElementsByClassName("response-button")];
|
|
43
|
+
const controlKeyElemsMask = keyElems.map(e => e.parentNode.id === "keypad-control-keys");
|
|
44
|
+
const gridCoords = keyElems.filter((k,i) => !controlKeyElemsMask[i]).map((k,i) => [Math.floor(i/cols), i%cols]);
|
|
45
|
+
const widthUsed = cols*(keyHeightPx*aspect);
|
|
46
|
+
const heightUsed = rows*keyHeightPx + keyHeightPx;
|
|
47
|
+
const freeHeight = heightPx - heightUsed;
|
|
48
|
+
const freeWidth = widthPx - widthUsed;
|
|
49
|
+
const verticalMarginOffset = Math.floor(freeHeight/2);
|
|
50
|
+
const horizontalMarginOffset = Math.floor(freeWidth/2);
|
|
51
|
+
|
|
52
|
+
let keyFontSize;
|
|
53
|
+
let j=0;
|
|
54
|
+
keyElems.forEach((k,i) => {
|
|
55
|
+
k.style.position = "fixed";
|
|
56
|
+
const controlKey = controlKeyElemsMask[i];
|
|
57
|
+
let top, left, width;
|
|
58
|
+
if (controlKey) {
|
|
59
|
+
top = heightPx-keyHeightPx;
|
|
60
|
+
width = widthPx/2 - horizontalMarginOffset - margin;
|
|
61
|
+
const m = horizontalMarginOffset + margin*0.5;
|
|
62
|
+
left = (k.id.toLowerCase().includes("space") ? m : m + width + margin);
|
|
63
|
+
|
|
64
|
+
const f = getLargeFontSize(k, width, keyHeightPx-margin);
|
|
65
|
+
k.style.fontSize = f + "px";
|
|
66
|
+
k.style.borderRadius = "25px";
|
|
67
|
+
k.style.height = (keyHeightPx - margin)+ "px";
|
|
68
|
+
} else {
|
|
69
|
+
const height = keyHeightPx - margin;
|
|
70
|
+
width = height*aspect;
|
|
71
|
+
const [y,x] = gridCoords[j];
|
|
72
|
+
j += 1;
|
|
73
|
+
top = y*height + ((y+1)*margin) + verticalMarginOffset - margin/2;
|
|
74
|
+
left = x*width + ((x+1)*margin) + horizontalMarginOffset - margin/2;
|
|
75
|
+
if (!keyFontSize) {
|
|
76
|
+
keyFontSize = getLargeFontSize(k, width, height);
|
|
77
|
+
}
|
|
78
|
+
k.style.height = height + "px";
|
|
79
|
+
k.style.fontSize = height/2 + "px";
|
|
80
|
+
}
|
|
81
|
+
k.style.width = width + "px";
|
|
82
|
+
k.style.top = top + "px";
|
|
83
|
+
k.style.left = left + "px";
|
|
84
|
+
k.style.visibility = "visible";
|
|
85
|
+
});
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const getLargeFontSize = (k, width, height) => {
|
|
89
|
+
k.style.height = "auto";
|
|
90
|
+
k.style.width = "auto";
|
|
91
|
+
k.style.whiteSpace = "nowrap";
|
|
92
|
+
|
|
93
|
+
// Set to a nominal font size, s
|
|
94
|
+
const s = 20;
|
|
95
|
+
k.style.fontSize = s + "px";
|
|
96
|
+
// Measure width of elem, w
|
|
97
|
+
const w = k.getBoundingClientRect().width;
|
|
98
|
+
const h = k.getBoundingClientRect().height
|
|
99
|
+
const rW = 1/(w/width)
|
|
100
|
+
const rH = 1/(h/height)
|
|
101
|
+
const r = Math.min(rW, rH)
|
|
102
|
+
// Set font size to s*r
|
|
103
|
+
const f = Math.floor(s*r);
|
|
104
|
+
return f;
|
|
105
105
|
};
|
package/src/receiver.css
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
#display {
|
|
2
|
-
max-height: 70vh;
|
|
3
|
-
overflow-y: scroll;
|
|
4
|
-
min-height: 350px;
|
|
5
|
-
min-width: 350px;
|
|
6
|
-
padding-left: 3em;
|
|
7
|
-
}
|
|
8
|
-
#info {
|
|
9
|
-
max-width: 800px;
|
|
10
|
-
padding-left: 1em;
|
|
11
|
-
padding-top: 0.25em;
|
|
1
|
+
#display {
|
|
2
|
+
max-height: 70vh;
|
|
3
|
+
overflow-y: scroll;
|
|
4
|
+
min-height: 350px;
|
|
5
|
+
min-width: 350px;
|
|
6
|
+
padding-left: 3em;
|
|
7
|
+
}
|
|
8
|
+
#info {
|
|
9
|
+
max-width: 800px;
|
|
10
|
+
padding-left: 1em;
|
|
11
|
+
padding-top: 0.25em;
|
|
12
12
|
}
|