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.
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