remote-calibrator 0.3.0-rc.3 → 0.5.0-beta.10
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/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
|
|