tracky-mouse 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -102,13 +102,16 @@ TrackyMouse.loadDependencies().then(function() {
102
102
 
103
103
  Set this to the path to the folder where you installed tracky-mouse, without a trailing slash.
104
104
 
105
- ### `TrackyMouse.loadDependencies()`
105
+ ### `TrackyMouse.loadDependencies([options])`
106
106
 
107
107
  This loads dependencies needed *for head tracking*. (It is not needed for dwell clicking.)
108
108
 
109
+ If you pass an options object, it can have the following properties:
110
+ - `statsJs` (optional): a boolean, whether to load stats.js for performance monitoring. Default is `false`.
111
+
109
112
  Returns a promise that resolves when the dependencies are loaded.
110
113
 
111
- ### `TrackyMouse.init([element])`
114
+ ### `TrackyMouse.init([element, options])`
112
115
 
113
116
  `TrackyMouse.init` initializes the library *for head tracking*. (It is not needed for dwell clicking.)
114
117
 
@@ -118,6 +121,13 @@ or using, and modifying, and existing element.
118
121
  If you pass an element, it should be an empty `<div>` element.
119
122
  It will add `class="tracky-mouse-ui"` directly to the element if it doesn't already have it.
120
123
 
124
+ If you pass an options object, it can have the following properties:
125
+ - `statsJs` (optional): a boolean, whether to include the stats.js performance monitor. Default is `false`.
126
+
127
+ Returns an object with a `dispose` method that you can call to remove the UI and clean up the web worker and camera stream.
128
+
129
+ *(Search keywords: disposal, destroy, teardown, cleanup, clean-up, clean up, deinitialize, de-initialize, remove, stop, end)* - see return value
130
+
121
131
  ### `TrackyMouse.useCamera()`
122
132
 
123
133
  This requests permission to use the camera, and starts the camera stream.
@@ -154,8 +164,8 @@ Arguments:
154
164
 
155
165
  Returns an object with the following properties:
156
166
  - `paused`: a getter/setter for whether dwell clicking is paused. Use this to implement a pause/resume button, in conjunction with `config.dwellClickEvenIfPaused`.
157
- - (that's all for now)
158
-
167
+ - `dispose`: a method to clean up the dwell clicker.
168
+ *(Search keywords: disposal, destroy, teardown, cleanup, clean-up, clean up, deinitialize, de-initialize, remove, stop, end)*
159
169
 
160
170
  Example:
161
171
  ```javascript
@@ -248,7 +258,9 @@ const config = {
248
258
  beforePointerDownDispatch: () => { window.pointers = []; },
249
259
  afterReleaseDrag: () => { window.pointers = []; },
250
260
  };
251
- TrackyMouse.initDwellClicking(config);
261
+ const dwellClicker = TrackyMouse.initDwellClicking(config);
262
+ // dwellClicker.paused = !dwellClicker.paused; // toggle
263
+ // dwellClicker.dispose(); // clean up
252
264
 
253
265
  // Source: https://stackoverflow.com/a/54492696/2624876
254
266
  function getCurrentRotation(el) {
@@ -269,6 +281,8 @@ function getCurrentRotation(el) {
269
281
 
270
282
  ### `TrackyMouse.cleanupDwellClicking()`
271
283
 
284
+ **Deprecated**: instead call `dispose()` on the object returned from `initDwellClicking()`.
285
+
272
286
  This stops the dwell clicker.
273
287
 
274
288
  ## Changelog
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tracky-mouse",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "Add facial mouse accessibility to JavaScript applications",
5
5
  "license": "MIT",
6
6
  "author": {
package/tracky-mouse.js CHANGED
@@ -3,7 +3,7 @@ const TrackyMouse = {
3
3
  dependenciesRoot: "./tracky-mouse",
4
4
  };
5
5
 
6
- TrackyMouse.loadDependencies = function () {
6
+ TrackyMouse.loadDependencies = function ({ statsJs = false } = {}) {
7
7
  TrackyMouse.dependenciesRoot = TrackyMouse.dependenciesRoot.replace(/\/+$/, "");
8
8
  const loadScript = src => {
9
9
  return new Promise((resolve, reject) => {
@@ -25,8 +25,10 @@ TrackyMouse.loadDependencies = function () {
25
25
  const scriptFiles = [
26
26
  `${TrackyMouse.dependenciesRoot}/lib/no-eval.js`, // generated with eval-is-evil.html, this instruments clmtrackr.js so I don't need unsafe-eval in the CSP
27
27
  `${TrackyMouse.dependenciesRoot}/lib/clmtrackr.js`,
28
- `${TrackyMouse.dependenciesRoot}/lib/stats.js`,
29
28
  ];
29
+ if (statsJs) {
30
+ scriptFiles.push(`${TrackyMouse.dependenciesRoot}/lib/stats.js`);
31
+ }
30
32
  // TODO: figure out how to preload worker-context dependencies that use `importScripts`.
31
33
  // `<link rel="preload">` can be injected at runtime,
32
34
  // which wouldn't make sense for the main thread's dependencies, since we're injecting all the scripts at once anyway,
@@ -46,7 +48,9 @@ const is_selector_valid = ((dummy_element) =>
46
48
  return true;
47
49
  })(document.createDocumentFragment());
48
50
 
49
- let clean_up_dwell_clicking = () => { };
51
+
52
+ const dwell_clickers = [];
53
+
50
54
  const init_dwell_clicking = (config) => {
51
55
  /*
52
56
  Arguments:
@@ -525,7 +529,7 @@ const init_dwell_clicking = (config) => {
525
529
  };
526
530
  raf_id = requestAnimationFrame(animate);
527
531
 
528
- clean_up_dwell_clicking = () => {
532
+ const dispose = () => {
529
533
  cancelAnimationFrame(raf_id);
530
534
  halo.remove();
531
535
  dwell_indicator.remove();
@@ -536,7 +540,6 @@ const init_dwell_clicking = (config) => {
536
540
  window.removeEventListener("blur", on_blur);
537
541
  document.removeEventListener("mouseleave", on_mouse_leave_page);
538
542
  document.removeEventListener("mouseenter", on_mouse_enter_page);
539
- clean_up_dwell_clicking = () => { };
540
543
  };
541
544
 
542
545
  const dwellClicker = {
@@ -545,8 +548,10 @@ const init_dwell_clicking = (config) => {
545
548
  },
546
549
  set paused(value) {
547
550
  paused = value;
548
- }
551
+ },
552
+ dispose,
549
553
  };
554
+ dwell_clickers.push(dwellClicker);
550
555
  return dwellClicker;
551
556
  };
552
557
 
@@ -554,10 +559,12 @@ TrackyMouse.initDwellClicking = function (config) {
554
559
  return init_dwell_clicking(config);
555
560
  };
556
561
  TrackyMouse.cleanupDwellClicking = function () {
557
- clean_up_dwell_clicking();
562
+ for (const dwell_clicker of dwell_clickers) {
563
+ dwell_clicker.dispose();
564
+ }
558
565
  };
559
566
 
560
- TrackyMouse.init = function (div) {
567
+ TrackyMouse.init = function (div, { statsJs = false } = {}) {
561
568
 
562
569
  var uiContainer = div || document.createElement("div");
563
570
  uiContainer.classList.add("tracky-mouse-ui");
@@ -706,7 +713,7 @@ TrackyMouse.init = function (div) {
706
713
  // required to work in iOS 11 & up:
707
714
  cameraVideo.setAttribute('playsinline', '');
708
715
 
709
- if (typeof Stats !== 'undefined') {
716
+ if (statsJs) {
710
717
  var stats = new Stats();
711
718
  stats.domElement.style.position = 'absolute';
712
719
  stats.domElement.style.top = '0px';
@@ -1834,13 +1841,60 @@ TrackyMouse.init = function (div) {
1834
1841
  if (window.electronAPI) {
1835
1842
  window.electronAPI.onShortcut(handleShortcut);
1836
1843
  }
1837
- addEventListener("keydown", (event) => {
1844
+ const handleKeydown = (event) => {
1838
1845
  // Same shortcut as the global shortcut in the electron app
1839
1846
  if (!event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey && event.key === "F9") {
1840
1847
  handleShortcut("toggle-tracking");
1841
1848
  }
1842
- });
1849
+ };
1850
+ addEventListener("keydown", handleKeydown);
1851
+
1852
+ return {
1853
+ dispose() {
1854
+ // TODO: re-structure so that cleanup can succeed even if initialization fails
1855
+ // OOP would help with this, by storing references in an object, but it doesn't necessarily
1856
+ // need to be converted to a class, it could just be an object, with a try-finally used for returning the API with a `dispose` method.
1857
+ // Wouldn't need to change the API that way.
1858
+ // (Would also be easy to maintain backwards compatibility while switching to using a class,
1859
+ // returning an instance of the class from `TrackyMouse.init` but deprecating it in favor of constructing the class.)
1860
+
1861
+ // clean up camera stream
1862
+ if (cameraVideo.srcObject) {
1863
+ for (const track of cameraVideo.srcObject.getTracks()) {
1864
+ track.stop();
1865
+ }
1866
+ }
1867
+ cameraVideo.srcObject = null; // probably pointless
1843
1868
 
1869
+ // not sure this helps
1870
+ reset();
1871
+ // just in case there's any async code looking at whether it's paused
1872
+ paused = true;
1873
+
1874
+ if (facemeshWorker) {
1875
+ facemeshWorker.terminate();
1876
+ }
1877
+ if (clmTracker) {
1878
+ // not sure this helps clean up any resources
1879
+ clmTracker.reset();
1880
+ }
1881
+
1882
+ pointerEl.remove();
1883
+
1884
+ stats?.domElement.remove(); // there is no dispose method but this may be all that it would need to do https://github.com/mrdoob/stats.js/pull/96
1885
+
1886
+ removeEventListener("keydown", handleKeydown);
1887
+
1888
+ // This is a little awkward, reversing the initialization based on a possibly-preexisting element
1889
+ // Could save and restore innerHTML but that won't restore event listeners, references, etc.
1890
+ // and may not even be desired if the HTML was placeholder text mentioning it not yet being initialized for example.
1891
+ uiContainer.classList.remove("tracky-mouse-ui");
1892
+ uiContainer.innerHTML = "";
1893
+ if (!div) {
1894
+ uiContainer.remove();
1895
+ }
1896
+ },
1897
+ };
1844
1898
  };
1845
1899
 
1846
1900
  // CommonJS export is untested. Script tag usage recommended.