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.
Files changed (58) hide show
  1. package/CHANGELOG.md +35 -3
  2. package/README.md +34 -49
  3. package/homepage/example.css +4 -3
  4. package/homepage/example.js +42 -22
  5. package/homepage/index.html +19 -4
  6. package/i18n/fetch-languages-sheets.js +11 -1
  7. package/lib/RemoteCalibrator.min.js +1 -1
  8. package/lib/RemoteCalibrator.min.js.LICENSE.txt +19 -2
  9. package/lib/RemoteCalibrator.min.js.map +1 -1
  10. package/netlify.toml +1 -1
  11. package/package.json +25 -24
  12. package/src/WebGazer4RC/package-lock.json +198 -143
  13. package/src/WebGazer4RC/package.json +2 -2
  14. package/src/WebGazer4RC/src/index.mjs +161 -52
  15. package/src/WebGazer4RC/test/webgazer_test.js +1 -1
  16. package/src/check/checkScreenSize.js +84 -0
  17. package/src/components/buttons.js +21 -4
  18. package/src/components/input.js +82 -0
  19. package/src/components/keyBinder.js +5 -6
  20. package/src/components/language.js +5 -0
  21. package/src/components/mediaPermission.js +21 -0
  22. package/src/components/onCanvas.js +2 -2
  23. package/src/components/sound.js +30 -2
  24. package/src/components/swalOptions.js +6 -3
  25. package/src/components/utils.js +27 -1
  26. package/src/components/video.js +9 -6
  27. package/src/const.js +15 -0
  28. package/src/core.js +103 -48
  29. package/src/css/buttons.scss +34 -7
  30. package/src/css/components.scss +57 -0
  31. package/src/css/distance.scss +71 -1
  32. package/src/css/gaze.css +9 -5
  33. package/src/css/main.css +22 -6
  34. package/src/css/panel.scss +33 -3
  35. package/src/css/screenSize.css +6 -5
  36. package/src/css/swal.css +1 -1
  37. package/src/css/video.scss +19 -0
  38. package/src/distance/distance.js +194 -41
  39. package/src/distance/distanceCheck.js +241 -0
  40. package/src/distance/distanceTrack.js +165 -68
  41. package/src/{interpupillaryDistance.js → distance/interPupillaryDistance.js} +27 -19
  42. package/src/gaze/gaze.js +4 -7
  43. package/src/gaze/gazeAccuracy.js +9 -4
  44. package/src/gaze/gazeCalibration.js +14 -4
  45. package/src/gaze/gazeTracker.js +40 -9
  46. package/src/i18n.js +1 -1
  47. package/src/index.js +7 -2
  48. package/src/media/two-side-arrow.svg +1 -0
  49. package/src/media/two-sided-horizontal.svg +1 -0
  50. package/src/media/two-sided-vertical.svg +3 -0
  51. package/src/panel.js +130 -65
  52. package/src/screenSize.js +38 -5
  53. package/webpack.config.js +7 -4
  54. package/media/measureDistance.png +0 -0
  55. package/media/panel.png +0 -0
  56. package/media/screenSize.png +0 -0
  57. package/media/trackGaze.png +0 -0
  58. package/src/displaySize.js +0 -28
@@ -20,8 +20,8 @@
20
20
  ],
21
21
  "dependencies": {
22
22
  "@tensorflow-models/facemesh": "^0.0.5",
23
- "@tensorflow/tfjs": "^3.8.0",
24
- "localforage": "^1.9.0",
23
+ "@tensorflow/tfjs": "^3.11.0",
24
+ "localforage": "^1.10.0",
25
25
  "numeric": "1.2.6",
26
26
  "regression": "2.0.1"
27
27
  }
@@ -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
- paintCurrentFrame(videoElementCanvas, videoElementCanvas.width, videoElementCanvas.height);
295
-
296
- // [20200617 xk] TODO: this call should be made async somehow. will take some work.
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( latestGazeData ) {
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
- gazeDot.style.transform = `translate(-15px, -15px)` // Move out of the display
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.visibility = 'hidden';
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
- webgazer._begin = function (videoOnly) {
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
- stream = await navigator.mediaDevices.getUserMedia( webgazer.params.camConstraints );
688
- init(videoOnly ? 'video' : 'all', stream);
689
- //
690
- webgazer.params.videoIsOn = true
691
- //
692
- if (!videoOnly) resolve(webgazer);
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
- videoElement.style.visibility = val ? 'visible' : 'hidden';
841
- }
842
- if(videoContainerElement) {
843
- videoContainerElement.style.visibility = val ? 'visible' : 'hidden';
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 localprevious calibration data (from localforage) should be loaded.
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
- videoSettings = videoTrack.getSettings();
936
- setInternalVideoBufferSizes( videoSettings.width, videoSettings.height );
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( err );
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 preditions
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 = (RCL, parent, { go, cancel }, showCancelButton) => {
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 = 'rc-buttons'
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('keydown', bindingFunctions)
14
-
13
+ document.body.addEventListener(eventType, bindingFunctions)
15
14
  return bindingFunctions
16
15
  }
17
16
 
18
- export function unbindKeys(event) {
19
- document.body.removeEventListener('keydown', event)
17
+ export function unbindKeys(event, eventType = 'keydown') {
18
+ document.body.removeEventListener(eventType, event)
20
19
  }
@@ -29,3 +29,8 @@ function constructLangData(lang) {
29
29
  timestamp: new Date(),
30
30
  }
31
31
  }
32
+
33
+ export function spaceForLanguage(L) {
34
+ console.log(phrases.EE_languageUseSpace[L])
35
+ return phrases.EE_languageUseSpace[L] === '1'
36
+ }
@@ -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.DARK_RED
31
+ if (!sparkle) ctx.fillStyle = RC._CONST.COLOR.RED
32
32
  else {
33
- if (frameCount % 4 < 2) ctx.fillStyle = RC._CONST.COLOR.DARK_RED
33
+ if (frameCount % 4 < 2) ctx.fillStyle = RC._CONST.COLOR.RED
34
34
  else ctx.fillStyle = '#fff'
35
35
  }
36
36