remote-calibrator 0.3.0-rc.3 → 0.5.0-beta.10
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +35 -3
- package/README.md +34 -49
- package/homepage/example.css +4 -3
- package/homepage/example.js +42 -22
- package/homepage/index.html +19 -4
- package/i18n/fetch-languages-sheets.js +11 -1
- package/lib/RemoteCalibrator.min.js +1 -1
- package/lib/RemoteCalibrator.min.js.LICENSE.txt +19 -2
- package/lib/RemoteCalibrator.min.js.map +1 -1
- package/netlify.toml +1 -1
- package/package.json +25 -24
- package/src/WebGazer4RC/package-lock.json +198 -143
- package/src/WebGazer4RC/package.json +2 -2
- package/src/WebGazer4RC/src/index.mjs +161 -52
- package/src/WebGazer4RC/test/webgazer_test.js +1 -1
- package/src/check/checkScreenSize.js +84 -0
- package/src/components/buttons.js +21 -4
- package/src/components/input.js +82 -0
- package/src/components/keyBinder.js +5 -6
- package/src/components/language.js +5 -0
- package/src/components/mediaPermission.js +21 -0
- package/src/components/onCanvas.js +2 -2
- package/src/components/sound.js +30 -2
- package/src/components/swalOptions.js +6 -3
- package/src/components/utils.js +27 -1
- package/src/components/video.js +9 -6
- package/src/const.js +15 -0
- package/src/core.js +103 -48
- package/src/css/buttons.scss +34 -7
- package/src/css/components.scss +57 -0
- package/src/css/distance.scss +71 -1
- package/src/css/gaze.css +9 -5
- package/src/css/main.css +22 -6
- package/src/css/panel.scss +33 -3
- package/src/css/screenSize.css +6 -5
- package/src/css/swal.css +1 -1
- package/src/css/video.scss +19 -0
- package/src/distance/distance.js +194 -41
- package/src/distance/distanceCheck.js +241 -0
- package/src/distance/distanceTrack.js +165 -68
- package/src/{interpupillaryDistance.js → distance/interPupillaryDistance.js} +27 -19
- package/src/gaze/gaze.js +4 -7
- package/src/gaze/gazeAccuracy.js +9 -4
- package/src/gaze/gazeCalibration.js +14 -4
- package/src/gaze/gazeTracker.js +40 -9
- package/src/i18n.js +1 -1
- package/src/index.js +7 -2
- package/src/media/two-side-arrow.svg +1 -0
- package/src/media/two-sided-horizontal.svg +1 -0
- package/src/media/two-sided-vertical.svg +3 -0
- package/src/panel.js +130 -65
- package/src/screenSize.js +38 -5
- package/webpack.config.js +7 -4
- package/media/measureDistance.png +0 -0
- package/media/panel.png +0 -0
- package/media/screenSize.png +0 -0
- package/media/trackGaze.png +0 -0
- package/src/displaySize.js +0 -28
@@ -53,14 +53,21 @@ webgazer.params.paused = false;
|
|
53
53
|
|
54
54
|
webgazer.params.greedyLearner = false;
|
55
55
|
webgazer.params.framerate = 60;
|
56
|
-
webgazer.params.showGazeDot = false
|
56
|
+
webgazer.params.showGazeDot = false;
|
57
|
+
|
58
|
+
webgazer.params.getLatestVideoFrameTimestamp = () => {};
|
59
|
+
webgazer.params.activeCamera = {
|
60
|
+
label: '',
|
61
|
+
id: '',
|
62
|
+
};
|
57
63
|
/* -------------------------------------------------------------------------- */
|
64
|
+
|
65
|
+
let videoInputs = []
|
66
|
+
|
58
67
|
// registered callback for loop
|
59
68
|
var nopCallback = function(data) {};
|
60
69
|
var callback = nopCallback;
|
61
70
|
|
62
|
-
let learning = false // Regression
|
63
|
-
|
64
71
|
//Types that regression systems should handle
|
65
72
|
//Describes the source of data so that regression systems may ignore or handle differently the various generating events
|
66
73
|
var eventTypes = ['click', 'move'];
|
@@ -280,6 +287,31 @@ var k = 0;
|
|
280
287
|
let _now = null
|
281
288
|
let _last = -1
|
282
289
|
|
290
|
+
// From getting the video frame to get data
|
291
|
+
let _oneLoopFinished = true
|
292
|
+
|
293
|
+
async function gazePrep(forcedPrep = false) {
|
294
|
+
paintCurrentFrame(videoElementCanvas, videoElementCanvas.width, videoElementCanvas.height);
|
295
|
+
|
296
|
+
// [20200617 xk] TODO: this call should be made async somehow. will take some work.
|
297
|
+
if (!webgazer.params.paused || forcedPrep) {
|
298
|
+
latestEyeFeatures = await getPupilFeatures(videoElementCanvas, videoElementCanvas.width, videoElementCanvas.height);
|
299
|
+
// console.log(videoElementCanvas, videoElementCanvas.width, videoElementCanvas.height);
|
300
|
+
}
|
301
|
+
|
302
|
+
// Draw face overlay
|
303
|
+
if (webgazer.params.showFaceOverlay) {
|
304
|
+
// Get tracker object
|
305
|
+
var tracker = webgazer.getTracker();
|
306
|
+
faceOverlay.getContext('2d').clearRect(0, 0, videoElement.videoWidth, videoElement.videoHeight);
|
307
|
+
tracker.drawFaceOverlay(faceOverlay.getContext('2d'), tracker.getPositions());
|
308
|
+
}
|
309
|
+
|
310
|
+
// Feedback box
|
311
|
+
// Check that the eyes are inside of the validation box
|
312
|
+
if (webgazer.params.showFaceFeedbackBox) checkEyesInValidationBox();
|
313
|
+
}
|
314
|
+
|
283
315
|
async function loop() {
|
284
316
|
_now = window.performance.now()
|
285
317
|
|
@@ -291,22 +323,11 @@ async function loop() {
|
|
291
323
|
// Paint the latest video frame into the canvas which will be analyzed by WebGazer
|
292
324
|
// [20180729 JT] Why do we need to do this? clmTracker does this itself _already_, which is just duplicating the work.
|
293
325
|
// Is it because other trackers need a canvas instead of an img/video element?
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
latestEyeFeatures = await getPupilFeatures(videoElementCanvas, videoElementCanvas.width, videoElementCanvas.height);
|
298
|
-
|
299
|
-
// Draw face overlay
|
300
|
-
if (webgazer.params.showFaceOverlay) {
|
301
|
-
// Get tracker object
|
302
|
-
var tracker = webgazer.getTracker();
|
303
|
-
faceOverlay.getContext('2d').clearRect(0, 0, videoElement.videoWidth, videoElement.videoHeight);
|
304
|
-
tracker.drawFaceOverlay(faceOverlay.getContext('2d'), tracker.getPositions());
|
326
|
+
if (_oneLoopFinished) {
|
327
|
+
_oneLoopFinished = false
|
328
|
+
webgazer.params.getLatestVideoFrameTimestamp(new Date())
|
305
329
|
}
|
306
|
-
|
307
|
-
// Feedback box
|
308
|
-
// Check that the eyes are inside of the validation box
|
309
|
-
if (webgazer.params.showFaceFeedbackBox) checkEyesInValidationBox();
|
330
|
+
await gazePrep()
|
310
331
|
}
|
311
332
|
|
312
333
|
if (!webgazer.params.paused) {
|
@@ -323,8 +344,9 @@ async function loop() {
|
|
323
344
|
|
324
345
|
// [20200623 xk] callback to function passed into setGazeListener(fn)
|
325
346
|
callback(latestGazeData);
|
347
|
+
_oneLoopFinished = true
|
326
348
|
|
327
|
-
if(
|
349
|
+
if (latestGazeData) {
|
328
350
|
// [20200608 XK] Smoothing across the most recent 4 predictions, do we need this with Kalman filter?
|
329
351
|
smoothingVals.push(latestGazeData);
|
330
352
|
var x = 0;
|
@@ -350,7 +372,9 @@ async function loop() {
|
|
350
372
|
|
351
373
|
}
|
352
374
|
} else {
|
353
|
-
|
375
|
+
try {
|
376
|
+
gazeDot.style.transform = `translate(-15px, -15px)` // Move out of the display
|
377
|
+
} catch (error) {}
|
354
378
|
}
|
355
379
|
|
356
380
|
requestAnimationFrame(loop);
|
@@ -503,7 +527,8 @@ async function init(initMode = 'all', stream) {
|
|
503
527
|
videoContainerElement = document.createElement('div');
|
504
528
|
videoContainerElement.id = webgazer.params.videoContainerId;
|
505
529
|
videoContainerElement.style.display = 'block';
|
506
|
-
videoContainerElement.style.visibility = webgazer.params.showVideo ? 'visible' : 'hidden';
|
530
|
+
// videoContainerElement.style.visibility = webgazer.params.showVideo ? 'visible' : 'hidden';
|
531
|
+
videoContainerElement.style.opacity = webgazer.params.showVideo ? 0.8 : 0;
|
507
532
|
// videoContainerElement.style.position = 'fixed';
|
508
533
|
videoContainerElement.style.left = '10px';
|
509
534
|
videoContainerElement.style.bottom = '10px';
|
@@ -516,7 +541,7 @@ async function init(initMode = 'all', stream) {
|
|
516
541
|
videoElement.srcObject = stream;
|
517
542
|
videoElement.autoplay = true;
|
518
543
|
videoElement.style.display = 'block';
|
519
|
-
videoElement.style.visibility = webgazer.params.showVideo ? 'visible' : 'hidden';
|
544
|
+
// videoElement.style.visibility = webgazer.params.showVideo ? 'visible' : 'hidden';
|
520
545
|
videoElement.style.position = 'absolute';
|
521
546
|
// We set these to stop the video appearing too large when it is added for the very first time
|
522
547
|
videoElement.style.width = webgazer.params.videoViewerWidth + 'px';
|
@@ -526,7 +551,8 @@ async function init(initMode = 'all', stream) {
|
|
526
551
|
videoElementCanvas = document.createElement('canvas');
|
527
552
|
videoElementCanvas.id = webgazer.params.videoElementCanvasId;
|
528
553
|
videoElementCanvas.style.display = 'block';
|
529
|
-
videoElementCanvas.style.
|
554
|
+
videoElementCanvas.style.opacity = 0;
|
555
|
+
// videoElementCanvas.style.visibility = 'hidden';
|
530
556
|
|
531
557
|
// Face overlay
|
532
558
|
// Shows the CLM tracking result
|
@@ -658,24 +684,72 @@ webgazer.begin = function(onFail) {
|
|
658
684
|
// loadGlobalData();
|
659
685
|
// }
|
660
686
|
|
661
|
-
onFail = onFail || function() {console.log('No stream')};
|
687
|
+
// onFail = onFail || function() {console.log('No stream')};
|
662
688
|
|
663
689
|
// if (debugVideoLoc) {
|
664
690
|
// init(debugVideoLoc);
|
665
691
|
// return webgazer;
|
666
692
|
// }
|
667
693
|
|
668
|
-
return webgazer._begin(false)
|
694
|
+
return webgazer._begin(false, onFail)
|
669
695
|
};
|
670
696
|
|
671
697
|
/**
|
672
698
|
* Start the video element.
|
673
699
|
*/
|
674
|
-
webgazer.beginVideo = function () {
|
675
|
-
webgazer._begin(true)
|
700
|
+
webgazer.beginVideo = function (onFail) {
|
701
|
+
webgazer._begin(true, onFail)
|
702
|
+
}
|
703
|
+
|
704
|
+
/* ------------------------------ Video switch ------------------------------ */
|
705
|
+
|
706
|
+
const _foldString = (str) => {
|
707
|
+
if (str.length < 8) return str
|
708
|
+
else return str.slice(0, 8) + '...'
|
709
|
+
}
|
710
|
+
|
711
|
+
const _setUpActiveCameraSwitch = (inputs) => {
|
712
|
+
const parent = videoContainerElement;
|
713
|
+
|
714
|
+
const selectElement = document.createElement('select')
|
715
|
+
selectElement.className = selectElement.id = 'webgazer-videoinput-select'
|
716
|
+
selectElement.name = 'videoinput'
|
717
|
+
inputs.forEach((input, ind) => {
|
718
|
+
selectElement.innerHTML += `<option value="${input.deviceId + '%' + input.label}"${ind === 0 ? ' selected' : ''} name="1">${_foldString(input.label)}</option>`;
|
719
|
+
})
|
720
|
+
|
721
|
+
selectElement.onchange = e => {
|
722
|
+
const [id, label] = selectElement.value.split('%')
|
723
|
+
webgazer.params.activeCamera.label = label
|
724
|
+
webgazer.params.activeCamera.id = id
|
725
|
+
|
726
|
+
webgazer.setCameraConstraints(_setUpConstraints(webgazer.params.camConstraints));
|
727
|
+
}
|
728
|
+
|
729
|
+
parent.appendChild(selectElement);
|
730
|
+
}
|
731
|
+
|
732
|
+
const _gotSources = (sources) => {
|
733
|
+
videoInputs = []
|
734
|
+
sources.forEach(device => {
|
735
|
+
if (device.kind === 'videoinput') videoInputs.push(device)
|
736
|
+
});
|
737
|
+
|
738
|
+
webgazer.params.activeCamera.label = videoInputs[0].label
|
739
|
+
webgazer.params.activeCamera.id = videoInputs[0].deviceId
|
676
740
|
}
|
677
741
|
|
678
|
-
|
742
|
+
const _setUpConstraints = (originalConstraints) => {
|
743
|
+
if (!webgazer.params.activeCamera.id) return originalConstraints
|
744
|
+
return {
|
745
|
+
video: {
|
746
|
+
...originalConstraints.video,
|
747
|
+
deviceId: webgazer.params.activeCamera.id,
|
748
|
+
}
|
749
|
+
}
|
750
|
+
}
|
751
|
+
|
752
|
+
webgazer._begin = function (videoOnly, onVideoFail) {
|
679
753
|
// SETUP VIDEO ELEMENTS
|
680
754
|
// Sets .mediaDevices.getUserMedia depending on browser
|
681
755
|
if (!webgazer.params.videoIsOn) {
|
@@ -684,15 +758,31 @@ webgazer._begin = function (videoOnly) {
|
|
684
758
|
return new Promise(async (resolve, reject) => {
|
685
759
|
let stream;
|
686
760
|
try {
|
687
|
-
|
688
|
-
|
689
|
-
|
690
|
-
|
691
|
-
|
692
|
-
|
761
|
+
if (typeof navigator.mediaDevices !== 'undefined')
|
762
|
+
navigator.mediaDevices.enumerateDevices().then(async (sources) => {
|
763
|
+
_gotSources(sources);
|
764
|
+
if (videoInputs.length === 0) {
|
765
|
+
onVideoFail(videoInputs);
|
766
|
+
throw 'No camera';
|
767
|
+
}
|
768
|
+
|
769
|
+
try {
|
770
|
+
stream = await navigator.mediaDevices.getUserMedia( _setUpConstraints(webgazer.params.camConstraints) );
|
771
|
+
} catch (error) {
|
772
|
+
onVideoFail(videoInputs);
|
773
|
+
throw error;
|
774
|
+
}
|
775
|
+
|
776
|
+
init(videoOnly ? 'video' : 'all', stream).then(() => {
|
777
|
+
if (videoInputs.length > 1) _setUpActiveCameraSwitch(videoInputs)
|
778
|
+
});
|
779
|
+
////
|
780
|
+
webgazer.params.videoIsOn = true
|
781
|
+
////
|
782
|
+
if (!videoOnly) resolve(webgazer);
|
783
|
+
});
|
693
784
|
} catch(err) {
|
694
785
|
console.log(err);
|
695
|
-
onFail();
|
696
786
|
videoElement = null;
|
697
787
|
stream = null;
|
698
788
|
reject(err);
|
@@ -749,6 +839,7 @@ webgazer.resume = async function() {
|
|
749
839
|
return webgazer;
|
750
840
|
}
|
751
841
|
webgazer.params.paused = false;
|
842
|
+
_oneLoopFinished = true
|
752
843
|
await loop();
|
753
844
|
return webgazer;
|
754
845
|
};
|
@@ -834,13 +925,15 @@ webgazer.showVideoPreview = function(val) {
|
|
834
925
|
* @param {*} bool
|
835
926
|
* @return {webgazer} this
|
836
927
|
*/
|
837
|
-
webgazer.showVideo = function(val) {
|
928
|
+
webgazer.showVideo = function(val, opacity = 0.8) {
|
838
929
|
webgazer.params.showVideo = val;
|
839
|
-
if(videoElement) {
|
840
|
-
|
841
|
-
|
842
|
-
|
843
|
-
|
930
|
+
// if (videoElement) {
|
931
|
+
// videoElement.style.visibility = val ? 'visible' : 'hidden';
|
932
|
+
// videoElement.style.opacity = val ? 1 : 0;
|
933
|
+
// }
|
934
|
+
if (videoContainerElement) {
|
935
|
+
// videoContainerElement.style.visibility = val ? 'visible' : 'hidden';
|
936
|
+
videoContainerElement.style.opacity = val ? opacity : 0;
|
844
937
|
}
|
845
938
|
return webgazer;
|
846
939
|
};
|
@@ -886,7 +979,7 @@ webgazer.showPredictionPoints = function(val) {
|
|
886
979
|
};
|
887
980
|
|
888
981
|
/**
|
889
|
-
* Set whether
|
982
|
+
* Set whether previous calibration data (from localforage) should be loaded.
|
890
983
|
* Default true.
|
891
984
|
*
|
892
985
|
* NOTE: Should be called before webgazer.begin() -- see www/js/main.js for example
|
@@ -922,26 +1015,36 @@ webgazer.applyKalmanFilter = function(val) {
|
|
922
1015
|
* Warning: Setting a large video resolution will decrease performance, and may require
|
923
1016
|
*/
|
924
1017
|
webgazer.setCameraConstraints = async function(constraints) {
|
925
|
-
var videoTrack,videoSettings;
|
1018
|
+
// var videoTrack, videoSettings;
|
926
1019
|
webgazer.params.camConstraints = constraints;
|
927
1020
|
|
928
1021
|
// If the camera stream is already up...
|
929
1022
|
if(videoStream)
|
930
1023
|
{
|
931
1024
|
webgazer.pause();
|
932
|
-
videoTrack = videoStream.getVideoTracks()[0];
|
1025
|
+
// videoTrack = videoStream.getVideoTracks()[0];
|
933
1026
|
try {
|
934
|
-
await videoTrack.applyConstraints( webgazer.params.camConstraints );
|
935
|
-
|
936
|
-
|
1027
|
+
// await videoTrack.applyConstraints( webgazer.params.camConstraints );
|
1028
|
+
videoStream.getVideoTracks().forEach(track => {
|
1029
|
+
track.stop();
|
1030
|
+
})
|
1031
|
+
const stream = await navigator.mediaDevices.getUserMedia( webgazer.params.camConstraints );
|
1032
|
+
setTimeout(() => {
|
1033
|
+
const videoTrack = stream.getVideoTracks()[0];
|
1034
|
+
const videoSettings = videoTrack.getSettings();
|
1035
|
+
videoStream = stream;
|
1036
|
+
videoElement.srcObject = stream;
|
1037
|
+
setInternalVideoBufferSizes( videoSettings.width, videoSettings.height );
|
1038
|
+
console.log('New constraints applied');
|
1039
|
+
}, 1500);
|
937
1040
|
} catch(err) {
|
938
|
-
console.log(
|
1041
|
+
console.log(err);
|
939
1042
|
return;
|
940
1043
|
}
|
941
1044
|
// Reset and recompute sizes of the video viewer.
|
942
1045
|
// This is only to adjust the feedback box, say, if the aspect ratio of the video has changed.
|
943
|
-
webgazer.setVideoViewerSize( webgazer.params.videoViewerWidth, webgazer.params.videoViewerHeight )
|
944
|
-
webgazer.getTracker().reset();
|
1046
|
+
// webgazer.setVideoViewerSize( webgazer.params.videoViewerWidth, webgazer.params.videoViewerHeight )
|
1047
|
+
// webgazer.getTracker().reset();
|
945
1048
|
await webgazer.resume();
|
946
1049
|
}
|
947
1050
|
}
|
@@ -990,6 +1093,10 @@ webgazer.setVideoViewerSize = function(w, h) {
|
|
990
1093
|
videoElement.style.width = w + 'px';
|
991
1094
|
videoElement.style.height = h + 'px';
|
992
1095
|
|
1096
|
+
// Change video container
|
1097
|
+
videoContainerElement.style.width = w + 'px';
|
1098
|
+
videoContainerElement.style.height = h + 'px';
|
1099
|
+
|
993
1100
|
// Change the face overlay
|
994
1101
|
faceOverlay.style.width = w + 'px';
|
995
1102
|
faceOverlay.style.height = h + 'px';
|
@@ -1176,7 +1283,9 @@ webgazer.getRegression = function() {
|
|
1176
1283
|
* Requests an immediate prediction
|
1177
1284
|
* @return {object} prediction data object
|
1178
1285
|
*/
|
1179
|
-
webgazer.getCurrentPrediction = function(regIndex) {
|
1286
|
+
webgazer.getCurrentPrediction = async function(regIndex) {
|
1287
|
+
webgazer.params.getLatestVideoFrameTimestamp(new Date())
|
1288
|
+
await gazePrep(true)
|
1180
1289
|
return getPrediction(regIndex);
|
1181
1290
|
};
|
1182
1291
|
|
@@ -1204,7 +1313,7 @@ webgazer.getVideoPreviewToCameraResolutionRatio = function() {
|
|
1204
1313
|
}
|
1205
1314
|
|
1206
1315
|
/*
|
1207
|
-
* Gets the fifty most recent tracker
|
1316
|
+
* Gets the fifty most recent tracker predictions
|
1208
1317
|
*/
|
1209
1318
|
webgazer.getStoredPoints = function() {
|
1210
1319
|
return [xPast50, yPast50];
|
@@ -51,7 +51,7 @@ describe('webgazer function', async() => {
|
|
51
51
|
assert.equal(videoAvailable,true);
|
52
52
|
assert.equal(isReady,true);
|
53
53
|
});
|
54
|
-
//modifying visibility params
|
54
|
+
// modifying visibility params
|
55
55
|
it('webgazerVideoFeed should display', async() => {
|
56
56
|
let video_display = await page.evaluate(async() => {
|
57
57
|
return document.getElementById('webgazerVideoFeed').style.display
|
@@ -0,0 +1,84 @@
|
|
1
|
+
import Swal from 'sweetalert2'
|
2
|
+
|
3
|
+
import RemoteCalibrator from '../core'
|
4
|
+
import { swalInfoOptions } from '../components/swalOptions'
|
5
|
+
import { constructInstructions, safeExecuteFunc } from '../components/utils'
|
6
|
+
import { takeInput } from '../components/input'
|
7
|
+
|
8
|
+
RemoteCalibrator.prototype._checkScreenSize = async function (
|
9
|
+
screenSizeCallback,
|
10
|
+
screenSizeData
|
11
|
+
) {
|
12
|
+
const RC = this
|
13
|
+
this._addBackground()
|
14
|
+
|
15
|
+
if (this.equipment) {
|
16
|
+
// Asked
|
17
|
+
checkScreenSize(this, screenSizeCallback, screenSizeData)
|
18
|
+
////
|
19
|
+
} else {
|
20
|
+
const { CM, IN_D, IN_F } = RC._CONST.UNITS
|
21
|
+
const haveEquipmentOptions = {}
|
22
|
+
haveEquipmentOptions[CM] = 'cm'
|
23
|
+
haveEquipmentOptions[IN_D] = 'in (Decimal, e.g. 11.5 in)'
|
24
|
+
haveEquipmentOptions[IN_F] = 'in (Fractional, e.g. 12 3/16 in)'
|
25
|
+
|
26
|
+
const { value: result } = await Swal.fire({
|
27
|
+
...swalInfoOptions(RC, {
|
28
|
+
showIcon: false,
|
29
|
+
}),
|
30
|
+
title: 'Check the calibration',
|
31
|
+
input: 'select',
|
32
|
+
inputOptions: {
|
33
|
+
'I have an appropriate measuring device in units': haveEquipmentOptions,
|
34
|
+
"I don't have an appropriate measuring device": {
|
35
|
+
none: 'No device',
|
36
|
+
},
|
37
|
+
},
|
38
|
+
inputPlaceholder: 'Select an option',
|
39
|
+
// showCancelButton: true,
|
40
|
+
inputValidator: value => {
|
41
|
+
return new Promise(resolve => {
|
42
|
+
const hasEquipment = value !== 'none'
|
43
|
+
|
44
|
+
RC.newEquipmentData = {
|
45
|
+
value: {
|
46
|
+
has: hasEquipment,
|
47
|
+
unit: hasEquipment ? value : null,
|
48
|
+
equipment: hasEquipment ? '' : null,
|
49
|
+
},
|
50
|
+
timestamp: new Date(),
|
51
|
+
}
|
52
|
+
|
53
|
+
resolve()
|
54
|
+
})
|
55
|
+
},
|
56
|
+
})
|
57
|
+
|
58
|
+
if (result) checkScreenSize(this, screenSizeCallback, screenSizeData)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
const checkScreenSize = async (RC, screenSizeCallback, screenSizeData) => {
|
63
|
+
const quit = () => {
|
64
|
+
RC._removeBackground()
|
65
|
+
safeExecuteFunc(screenSizeCallback, screenSizeData)
|
66
|
+
}
|
67
|
+
|
68
|
+
if (RC.equipment && RC.equipment.value.has) {
|
69
|
+
// ! Has equipment
|
70
|
+
RC._replaceBackground(
|
71
|
+
constructInstructions(
|
72
|
+
'📏 ' + 'Check Screen Size Measure',
|
73
|
+
'Measure the length of the arrow, and put your answer in the text box. You only need to put the numbers. When finished, press OK.'
|
74
|
+
)
|
75
|
+
)
|
76
|
+
|
77
|
+
const input = await takeInput(RC)
|
78
|
+
|
79
|
+
if (input) {
|
80
|
+
window.console.log(input)
|
81
|
+
}
|
82
|
+
}
|
83
|
+
quit()
|
84
|
+
}
|
@@ -1,12 +1,19 @@
|
|
1
1
|
import { phrases } from '../i18n'
|
2
2
|
import '../css/buttons.scss'
|
3
3
|
|
4
|
-
export const addButtons = (
|
4
|
+
export const addButtons = (
|
5
|
+
RCL,
|
6
|
+
parent,
|
7
|
+
{ go, cancel, custom },
|
8
|
+
showCancelButton,
|
9
|
+
absolutePositioning = true
|
10
|
+
) => {
|
5
11
|
const buttons = document.createElement('div')
|
6
|
-
buttons.className =
|
12
|
+
buttons.className =
|
13
|
+
'rc-buttons' + (absolutePositioning ? ' rc-absolute-buttons' : '')
|
7
14
|
buttons.id = 'rc-buttons'
|
8
15
|
|
9
|
-
let goButton, cancelButton
|
16
|
+
let goButton, cancelButton, customButton
|
10
17
|
|
11
18
|
if (go) {
|
12
19
|
goButton = document.createElement('button')
|
@@ -24,9 +31,19 @@ export const addButtons = (RCL, parent, { go, cancel }, showCancelButton) => {
|
|
24
31
|
buttons.appendChild(cancelButton)
|
25
32
|
}
|
26
33
|
|
34
|
+
if (custom) {
|
35
|
+
const { callback, content } = custom
|
36
|
+
|
37
|
+
customButton = document.createElement('button')
|
38
|
+
customButton.className = 'rc-button rc-custom-button'
|
39
|
+
customButton.onclick = callback
|
40
|
+
customButton.innerHTML = content
|
41
|
+
buttons.appendChild(customButton)
|
42
|
+
}
|
43
|
+
|
27
44
|
parent.appendChild(buttons)
|
28
45
|
|
29
|
-
return [buttons, goButton, cancelButton]
|
46
|
+
return [buttons, goButton, cancelButton, customButton]
|
30
47
|
}
|
31
48
|
|
32
49
|
export const removeButtons = parent => {
|
@@ -0,0 +1,82 @@
|
|
1
|
+
import { addButtons } from './buttons'
|
2
|
+
|
3
|
+
import Arrow from '../media/two-sided-horizontal.svg'
|
4
|
+
|
5
|
+
const _exampleMeasure = {
|
6
|
+
cm: '22.5',
|
7
|
+
inDecimal: '11.5',
|
8
|
+
inFractional: ['12', '3/16'],
|
9
|
+
}
|
10
|
+
|
11
|
+
export const takeInput = async RC => {
|
12
|
+
const unit = RC.equipment.value.unit
|
13
|
+
const unitDisplay = unit === RC._CONST.UNITS.CM ? 'cm' : 'in'
|
14
|
+
|
15
|
+
const formItem = `<div class="rc-form">
|
16
|
+
<div class="rc-form-inputs">
|
17
|
+
${
|
18
|
+
unit === RC._CONST.UNITS.IN_F
|
19
|
+
? ``
|
20
|
+
: `<input type="text" class="rc-form-input" placeholder="Your measure, e.g. ${_exampleMeasure[unit]}" /><span>${unitDisplay}</span>`
|
21
|
+
}
|
22
|
+
</div>
|
23
|
+
</div>`
|
24
|
+
// <button class="rc-form-submit rc-button rc-custom-button" disabled>OK</button>
|
25
|
+
|
26
|
+
const instruction = RC.background.querySelector('.calibration-instruction')
|
27
|
+
instruction.innerHTML += formItem
|
28
|
+
const formElement = instruction.querySelector('.rc-form')
|
29
|
+
const formInputElement = instruction.querySelector('.rc-form-input')
|
30
|
+
// const formSubmitElement = instruction.querySelector('.rc-form-submit')
|
31
|
+
|
32
|
+
const addedButtons = addButtons(
|
33
|
+
RC.L,
|
34
|
+
formElement,
|
35
|
+
{
|
36
|
+
go: () => {},
|
37
|
+
cancel: () => {},
|
38
|
+
},
|
39
|
+
true,
|
40
|
+
false
|
41
|
+
)
|
42
|
+
const goButton = addedButtons[1]
|
43
|
+
const cancelButton = addedButtons[2]
|
44
|
+
|
45
|
+
formInputElement.oninput = () => {
|
46
|
+
if (validInput(formInputElement.value)) {
|
47
|
+
formInputElement.classList.remove('rc-input-error')
|
48
|
+
goButton.disabled = false
|
49
|
+
} else {
|
50
|
+
formInputElement.classList.add('rc-input-error')
|
51
|
+
goButton.disabled = true
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
// Arrow
|
56
|
+
const arrow = document.createElement('div')
|
57
|
+
RC.background.appendChild(arrow)
|
58
|
+
arrow.outerHTML = Arrow
|
59
|
+
|
60
|
+
return new Promise(resolve => {
|
61
|
+
goButton.onclick = () => {
|
62
|
+
onFormSubmit(formInputElement.value, resolve)
|
63
|
+
}
|
64
|
+
cancelButton.onclick = () => {
|
65
|
+
resolve(null)
|
66
|
+
}
|
67
|
+
})
|
68
|
+
}
|
69
|
+
|
70
|
+
const onFormSubmit = (value, resolve) => {
|
71
|
+
if (validInput(value)) {
|
72
|
+
resolve(value)
|
73
|
+
}
|
74
|
+
}
|
75
|
+
|
76
|
+
// const onFormCancel = e => {}
|
77
|
+
|
78
|
+
/* -------------------------------------------------------------------------- */
|
79
|
+
|
80
|
+
const validInput = text => {
|
81
|
+
return !isNaN(text)
|
82
|
+
}
|
@@ -2,19 +2,18 @@
|
|
2
2
|
* Bind keys, e.g., SPACE (' '), ESC ('Escape')
|
3
3
|
* keys is an object of keys of the keys, pointing to its binding functions
|
4
4
|
*/
|
5
|
-
export function bindKeys(keys) {
|
5
|
+
export function bindKeys(keys, eventType = 'keydown') {
|
6
6
|
const bindingFunctions = e => {
|
7
7
|
if (e.key in keys) {
|
8
8
|
e.preventDefault()
|
9
|
-
keys[e.key]()
|
9
|
+
keys[e.key](e)
|
10
10
|
}
|
11
11
|
}
|
12
12
|
|
13
|
-
document.body.addEventListener(
|
14
|
-
|
13
|
+
document.body.addEventListener(eventType, bindingFunctions)
|
15
14
|
return bindingFunctions
|
16
15
|
}
|
17
16
|
|
18
|
-
export function unbindKeys(event) {
|
19
|
-
document.body.removeEventListener(
|
17
|
+
export function unbindKeys(event, eventType = 'keydown') {
|
18
|
+
document.body.removeEventListener(eventType, event)
|
20
19
|
}
|
@@ -0,0 +1,21 @@
|
|
1
|
+
export const checkPermissions = tasks => {
|
2
|
+
// if (navigator.permissions)
|
3
|
+
// navigator.permissions
|
4
|
+
// .query({ name: 'camera' })
|
5
|
+
// .then(permissionObj => {
|
6
|
+
// console.log(permissionObj.state)
|
7
|
+
// })
|
8
|
+
// .catch(error => {
|
9
|
+
// console.error(error)
|
10
|
+
// })
|
11
|
+
let now = Date.now()
|
12
|
+
navigator.mediaDevices
|
13
|
+
.getUserMedia({ video: true })
|
14
|
+
.then(function (stream) {
|
15
|
+
// Permitted
|
16
|
+
console.log('[GOT]', Date.now() - now)
|
17
|
+
})
|
18
|
+
.catch(function (err) {
|
19
|
+
console.error('[ERROR] ', Date.now() - now)
|
20
|
+
})
|
21
|
+
}
|
@@ -28,9 +28,9 @@ export function _circle(RC, ctx, x, y, frameCount, sparkle = true) {
|
|
28
28
|
ctx.arc(x, y, circleR >> 1, 0, Math.PI * 2)
|
29
29
|
ctx.closePath()
|
30
30
|
|
31
|
-
if (!sparkle) ctx.fillStyle = RC._CONST.COLOR.
|
31
|
+
if (!sparkle) ctx.fillStyle = RC._CONST.COLOR.RED
|
32
32
|
else {
|
33
|
-
if (frameCount % 4 < 2) ctx.fillStyle = RC._CONST.COLOR.
|
33
|
+
if (frameCount % 4 < 2) ctx.fillStyle = RC._CONST.COLOR.RED
|
34
34
|
else ctx.fillStyle = '#fff'
|
35
35
|
}
|
36
36
|
|