From 7c218639c75e22e1270d4198fb940b439175deee Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 31 Aug 2023 11:50:14 -0400
Subject: reworked recording workspace UI and switched to recording window, not
 webcam

---
 src/client/views/nodes/ScreenshotBox.tsx | 3 +++
 1 file changed, 3 insertions(+)

(limited to 'src/client/views/nodes/ScreenshotBox.tsx')

diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index fa19caae1..4ebc93165 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -219,6 +219,9 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
         // }
         return null;
     }
+    Record = () => !this._screenCapture && this.toggleRecording();
+    Pause = () => this._screenCapture && this.toggleRecording();
+
     toggleRecording = async () => {
         if (!this._screenCapture) {
             this._audioRec = new MediaRecorder(await navigator.mediaDevices.getUserMedia({ audio: true }));
-- 
cgit v1.2.3-70-g09d2


From a60edcbd8fd724ef9c228262370a7ad98f7aadd1 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 31 Aug 2023 15:44:46 -0400
Subject: added dragging off of recordings and option for webcam vs screen

---
 package-lock.json                                  | 304 +++++++++------------
 package.json                                       |   2 +-
 src/client/util/CurrentUserUtils.ts                |   4 +-
 src/client/views/nodes/FontIconBox/FontIconBox.tsx |  15 +-
 .../views/nodes/RecordingBox/RecordingBox.tsx      |  44 +--
 src/client/views/nodes/ScreenshotBox.tsx           |  13 +-
 6 files changed, 185 insertions(+), 197 deletions(-)

(limited to 'src/client/views/nodes/ScreenshotBox.tsx')

diff --git a/package-lock.json b/package-lock.json
index c3190ddb4..aae7c022c 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -164,11 +164,11 @@
             },
             "dependencies": {
                 "@babel/code-frame": {
-                    "version": "7.22.10",
-                    "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
-                    "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
+                    "version": "7.22.13",
+                    "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+                    "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
                     "requires": {
-                        "@babel/highlight": "^7.22.10",
+                        "@babel/highlight": "^7.22.13",
                         "chalk": "^2.4.2"
                     }
                 },
@@ -184,9 +184,9 @@
                     }
                 },
                 "@babel/highlight": {
-                    "version": "7.22.10",
-                    "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
-                    "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
+                    "version": "7.22.13",
+                    "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
+                    "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
                     "requires": {
                         "@babel/helper-validator-identifier": "^7.22.5",
                         "chalk": "^2.4.2",
@@ -194,9 +194,9 @@
                     }
                 },
                 "@babel/parser": {
-                    "version": "7.22.11",
-                    "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz",
-                    "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g=="
+                    "version": "7.22.14",
+                    "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.14.tgz",
+                    "integrity": "sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ=="
                 },
                 "@babel/traverse": {
                     "version": "7.22.11",
@@ -579,11 +579,11 @@
             },
             "dependencies": {
                 "@babel/code-frame": {
-                    "version": "7.22.10",
-                    "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.10.tgz",
-                    "integrity": "sha512-/KKIMG4UEL35WmI9OlvMhurwtytjvXoFcGNrOvyG9zIzA8YmPjVtIZUf7b05+TPO7G7/GEmLHDaoCgACHl9hhA==",
+                    "version": "7.22.13",
+                    "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
+                    "integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
                     "requires": {
-                        "@babel/highlight": "^7.22.10",
+                        "@babel/highlight": "^7.22.13",
                         "chalk": "^2.4.2"
                     }
                 },
@@ -599,9 +599,9 @@
                     }
                 },
                 "@babel/highlight": {
-                    "version": "7.22.10",
-                    "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.10.tgz",
-                    "integrity": "sha512-78aUtVcT7MUscr0K5mIEnkwxPE0MaxkR5RxRwuHaQ+JuU5AmTPhY+do2mdzVTnIJJpyBglql2pehuBIWHug+WQ==",
+                    "version": "7.22.13",
+                    "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.13.tgz",
+                    "integrity": "sha512-C/BaXcnnvBCmHTpz/VGZ8jgtE2aYlW4hxDhseJAWZb7gqGM/qtCK6iZUb0TyKFf7BOUsBH7Q7fkRsDRhg1XklQ==",
                     "requires": {
                         "@babel/helper-validator-identifier": "^7.22.5",
                         "chalk": "^2.4.2",
@@ -609,9 +609,9 @@
                     }
                 },
                 "@babel/parser": {
-                    "version": "7.22.11",
-                    "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.11.tgz",
-                    "integrity": "sha512-R5zb8eJIBPJriQtbH/htEQy4k7E2dHWlD2Y2VT07JCzwYZHBxV5ZYtM0UhXSNMT74LyxuM+b1jdL7pSesXbC/g=="
+                    "version": "7.22.14",
+                    "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.14.tgz",
+                    "integrity": "sha512-1KucTHgOvaw/LzCVrEOAyXkr9rQlp0A1HiHRYnSUE9dmb8PvPW7o5sscg+5169r54n3vGlbx6GevTE/Iw/P3AQ=="
                 },
                 "@babel/traverse": {
                     "version": "7.22.11",
@@ -1822,9 +1822,9 @@
             }
         },
         "@babel/preset-env": {
-            "version": "7.22.10",
-            "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.10.tgz",
-            "integrity": "sha512-riHpLb1drNkpLlocmSyEg4oYJIQFeXAK/d7rI6mbD0XsvoTOOweXDmQPG/ErxsEhWk3rl3Q/3F6RFQlVFS8m0A==",
+            "version": "7.22.14",
+            "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.22.14.tgz",
+            "integrity": "sha512-daodMIoVo+ol/g+//c/AH+szBkFj4STQUikvBijRGL72Ph+w+AMTSh55DUETe8KJlPlDT1k/mp7NBfOuiWmoig==",
             "requires": {
                 "@babel/compat-data": "^7.22.9",
                 "@babel/helper-compilation-targets": "^7.22.10",
@@ -1852,41 +1852,41 @@
                 "@babel/plugin-syntax-top-level-await": "^7.14.5",
                 "@babel/plugin-syntax-unicode-sets-regex": "^7.18.6",
                 "@babel/plugin-transform-arrow-functions": "^7.22.5",
-                "@babel/plugin-transform-async-generator-functions": "^7.22.10",
+                "@babel/plugin-transform-async-generator-functions": "^7.22.11",
                 "@babel/plugin-transform-async-to-generator": "^7.22.5",
                 "@babel/plugin-transform-block-scoped-functions": "^7.22.5",
                 "@babel/plugin-transform-block-scoping": "^7.22.10",
                 "@babel/plugin-transform-class-properties": "^7.22.5",
-                "@babel/plugin-transform-class-static-block": "^7.22.5",
+                "@babel/plugin-transform-class-static-block": "^7.22.11",
                 "@babel/plugin-transform-classes": "^7.22.6",
                 "@babel/plugin-transform-computed-properties": "^7.22.5",
                 "@babel/plugin-transform-destructuring": "^7.22.10",
                 "@babel/plugin-transform-dotall-regex": "^7.22.5",
                 "@babel/plugin-transform-duplicate-keys": "^7.22.5",
-                "@babel/plugin-transform-dynamic-import": "^7.22.5",
+                "@babel/plugin-transform-dynamic-import": "^7.22.11",
                 "@babel/plugin-transform-exponentiation-operator": "^7.22.5",
-                "@babel/plugin-transform-export-namespace-from": "^7.22.5",
+                "@babel/plugin-transform-export-namespace-from": "^7.22.11",
                 "@babel/plugin-transform-for-of": "^7.22.5",
                 "@babel/plugin-transform-function-name": "^7.22.5",
-                "@babel/plugin-transform-json-strings": "^7.22.5",
+                "@babel/plugin-transform-json-strings": "^7.22.11",
                 "@babel/plugin-transform-literals": "^7.22.5",
-                "@babel/plugin-transform-logical-assignment-operators": "^7.22.5",
+                "@babel/plugin-transform-logical-assignment-operators": "^7.22.11",
                 "@babel/plugin-transform-member-expression-literals": "^7.22.5",
                 "@babel/plugin-transform-modules-amd": "^7.22.5",
-                "@babel/plugin-transform-modules-commonjs": "^7.22.5",
-                "@babel/plugin-transform-modules-systemjs": "^7.22.5",
+                "@babel/plugin-transform-modules-commonjs": "^7.22.11",
+                "@babel/plugin-transform-modules-systemjs": "^7.22.11",
                 "@babel/plugin-transform-modules-umd": "^7.22.5",
                 "@babel/plugin-transform-named-capturing-groups-regex": "^7.22.5",
                 "@babel/plugin-transform-new-target": "^7.22.5",
-                "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.5",
-                "@babel/plugin-transform-numeric-separator": "^7.22.5",
-                "@babel/plugin-transform-object-rest-spread": "^7.22.5",
+                "@babel/plugin-transform-nullish-coalescing-operator": "^7.22.11",
+                "@babel/plugin-transform-numeric-separator": "^7.22.11",
+                "@babel/plugin-transform-object-rest-spread": "^7.22.11",
                 "@babel/plugin-transform-object-super": "^7.22.5",
-                "@babel/plugin-transform-optional-catch-binding": "^7.22.5",
-                "@babel/plugin-transform-optional-chaining": "^7.22.10",
+                "@babel/plugin-transform-optional-catch-binding": "^7.22.11",
+                "@babel/plugin-transform-optional-chaining": "^7.22.12",
                 "@babel/plugin-transform-parameters": "^7.22.5",
                 "@babel/plugin-transform-private-methods": "^7.22.5",
-                "@babel/plugin-transform-private-property-in-object": "^7.22.5",
+                "@babel/plugin-transform-private-property-in-object": "^7.22.11",
                 "@babel/plugin-transform-property-literals": "^7.22.5",
                 "@babel/plugin-transform-regenerator": "^7.22.10",
                 "@babel/plugin-transform-reserved-words": "^7.22.5",
@@ -1900,7 +1900,7 @@
                 "@babel/plugin-transform-unicode-regex": "^7.22.5",
                 "@babel/plugin-transform-unicode-sets-regex": "^7.22.5",
                 "@babel/preset-modules": "0.1.6-no-external-plugins",
-                "@babel/types": "^7.22.10",
+                "@babel/types": "^7.22.11",
                 "babel-plugin-polyfill-corejs2": "^0.4.5",
                 "babel-plugin-polyfill-corejs3": "^0.8.3",
                 "babel-plugin-polyfill-regenerator": "^0.5.2",
@@ -2711,11 +2711,11 @@
             }
         },
         "@floating-ui/react-dom": {
-            "version": "2.0.1",
-            "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.1.tgz",
-            "integrity": "sha512-rZtAmSht4Lry6gdhAJDrCp/6rKN7++JnL1/Anbr/DdeyYXQPxvg/ivrbYvJulbRf4vL8b212suwMM2lxbv+RQA==",
+            "version": "2.0.2",
+            "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.2.tgz",
+            "integrity": "sha512-5qhlDvjaLmAst/rKb3VdlCinwTF4EYMiVxuuc/HVUjs46W0zgtbMmAZ1UTsDrRTxRmUEzl92mOtWbeeXL26lSQ==",
             "requires": {
-                "@floating-ui/dom": "^1.3.0"
+                "@floating-ui/dom": "^1.5.1"
             }
         },
         "@floating-ui/utils": {
@@ -3232,9 +3232,9 @@
             }
         },
         "@mui/styled-engine-sc": {
-            "version": "5.14.6",
-            "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.14.6.tgz",
-            "integrity": "sha512-7/KXXdDLjpQAmbmIhUs1x7nzqooEiHkidQOXCIH04NiVa4KRxP4v/bOWV/5GpgZi1Aky5ruf9IVyH3jxYIW3JA==",
+            "version": "5.14.7",
+            "resolved": "https://registry.npmjs.org/@mui/styled-engine-sc/-/styled-engine-sc-5.14.7.tgz",
+            "integrity": "sha512-so3X2N0SxpXjc61+d6s0aG11OVF9XTDrKusaXd8YE1d9fzEuIGEpvOXizaMJZThqkTqGzNKQuXFEpC55Vb0gdw==",
             "requires": {
                 "@babel/runtime": "^7.22.10",
                 "prop-types": "^15.8.1"
@@ -6537,9 +6537,9 @@
             }
         },
         "browndash-components": {
-            "version": "0.1.2",
-            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.2.tgz",
-            "integrity": "sha512-5ONrqd6qYYMjWuayhhdwU4tlrtr+7c0n9MAMXg4jccdd9OxnmdlWpjPzOBYl3slE+mSpKcycwKarwYND+eM0NA==",
+            "version": "0.1.21",
+            "resolved": "https://registry.npmjs.org/browndash-components/-/browndash-components-0.1.21.tgz",
+            "integrity": "sha512-xPHuliete1jNXCGYZrVxuKUDmlanleFLWjZm8dC3vII0BT7jNPxMRZyYTxgc/PjYMRgM/BY0FCUPFTrsiYRawQ==",
             "requires": {
                 "@emotion/react": "^11.11.1",
                 "@emotion/styled": "^11.11.0",
@@ -6657,15 +6657,15 @@
                     "integrity": "sha512-EsBwpc7hBUJWAsNPBmJy4hxWx12v6bshQsldrVmjxJoc3isbxhOrF2IcCpaXxfvq03NwkI7sbsOLXbYuqF/8Ww=="
                 },
                 "@mui/base": {
-                    "version": "5.0.0-beta.12",
-                    "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.12.tgz",
-                    "integrity": "sha512-tZjjXNAyUpwSDT1uRliZMhRQkWYzELJ8Qi61EuOMRpi36HIwnK2T7Nr4RI423Sv8G2EEikDAZj7je33eNd73NQ==",
+                    "version": "5.0.0-beta.13",
+                    "resolved": "https://registry.npmjs.org/@mui/base/-/base-5.0.0-beta.13.tgz",
+                    "integrity": "sha512-uC0l97pBspfDAp+iz2cJq8YZ8Sd9i73V77+WzUiOAckIVEyCm5dyVDZCCO2/phmzckVEeZCGcytybkjMQuhPQw==",
                     "requires": {
                         "@babel/runtime": "^7.22.10",
                         "@emotion/is-prop-valid": "^1.2.1",
                         "@floating-ui/react-dom": "^2.0.1",
                         "@mui/types": "^7.2.4",
-                        "@mui/utils": "^5.14.6",
+                        "@mui/utils": "^5.14.7",
                         "@popperjs/core": "^2.11.8",
                         "clsx": "^2.0.0",
                         "prop-types": "^15.8.1",
@@ -6683,21 +6683,21 @@
                     }
                 },
                 "@mui/core-downloads-tracker": {
-                    "version": "5.14.6",
-                    "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.6.tgz",
-                    "integrity": "sha512-QZEU3pyGWLuaHbxvOlShol7U1FVgzWBR0OH9H8D7L8w4/vto5N5jJVvlqFQS3T0zbR6YGHxFaiL6Ky87jQg7aw=="
+                    "version": "5.14.7",
+                    "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.14.7.tgz",
+                    "integrity": "sha512-sCWTUNElBPgB30iLvWe3PU7SIlTKZNf6/E/sko85iHVeHCM6WPkDw+y89CrZYjhFNmPqt2fIQM/pZu+rP2lFLA=="
                 },
                 "@mui/material": {
-                    "version": "5.14.6",
-                    "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.6.tgz",
-                    "integrity": "sha512-C3UgGrmtvcGkQkm0ONBU7bTdapTjQc2Se3b2354xMmU7lgSgW7VM6EP9wIH5XqqoJ60m9l/s9kbTWX0Y+EaWvA==",
+                    "version": "5.14.7",
+                    "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.14.7.tgz",
+                    "integrity": "sha512-jIZj9F7zMv6IlyaYDVv5M2Kp20jIX8c0kzuwteySHS/A0IvPVyomQEPtWc51MCbpDNCqzwoZUp3rQtA2lI8k7A==",
                     "requires": {
                         "@babel/runtime": "^7.22.10",
-                        "@mui/base": "5.0.0-beta.12",
-                        "@mui/core-downloads-tracker": "^5.14.6",
-                        "@mui/system": "^5.14.6",
+                        "@mui/base": "5.0.0-beta.13",
+                        "@mui/core-downloads-tracker": "^5.14.7",
+                        "@mui/system": "^5.14.7",
                         "@mui/types": "^7.2.4",
-                        "@mui/utils": "^5.14.6",
+                        "@mui/utils": "^5.14.7",
                         "@types/react-transition-group": "^4.4.6",
                         "clsx": "^2.0.0",
                         "csstype": "^3.1.2",
@@ -6717,12 +6717,12 @@
                     }
                 },
                 "@mui/private-theming": {
-                    "version": "5.14.6",
-                    "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.6.tgz",
-                    "integrity": "sha512-3VBLFGizBXfofyk33bwRg6t9L648aKnLmOKPfY1wFuiXq3AEYwobK65iDci/tHKxm/VKbZ6A7PFjLejvB3EvRQ==",
+                    "version": "5.14.7",
+                    "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-5.14.7.tgz",
+                    "integrity": "sha512-Y86+hmDnJab2Ka42PgxKpK3oL7EiacbeeX3X/lG9LGO0wSc45wZjHeTfIlVSkkUCkexiMKEJp5NlSjZhr27NRQ==",
                     "requires": {
                         "@babel/runtime": "^7.22.10",
-                        "@mui/utils": "^5.14.6",
+                        "@mui/utils": "^5.14.7",
                         "prop-types": "^15.8.1"
                     },
                     "dependencies": {
@@ -6737,9 +6737,9 @@
                     }
                 },
                 "@mui/styled-engine": {
-                    "version": "5.14.6",
-                    "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.6.tgz",
-                    "integrity": "sha512-I6zeu/OP1Hk4NsX1Oj85TiYl1dER0JMsLJVn76J1Ihl24A5EbiZQKJp3Mn+ufA79ypkdAvM9aQCAQyiVBFcUHg==",
+                    "version": "5.14.7",
+                    "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-5.14.7.tgz",
+                    "integrity": "sha512-hKBETEDsIAkL8/mBwPiQj/vw28OeIhMXC3Tvj4J2bb9snxAKpiZioR1PwqP+6P41twsC/GKBd0Vr9oaWYaHuMg==",
                     "requires": {
                         "@babel/runtime": "^7.22.10",
                         "@emotion/cache": "^11.11.0",
@@ -6758,15 +6758,15 @@
                     }
                 },
                 "@mui/system": {
-                    "version": "5.14.6",
-                    "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.6.tgz",
-                    "integrity": "sha512-/n0ae1MegWjiV1BpRU8jgg4E0zBjeB2VYsT/68ag/xaDuq3/TaDKJeT9REIvyBvwlG3CI3S2O+tRELktxCD1kg==",
+                    "version": "5.14.7",
+                    "resolved": "https://registry.npmjs.org/@mui/system/-/system-5.14.7.tgz",
+                    "integrity": "sha512-jeZtHglc+Pi6qjGoopT6O4RqYXVBMqHVOsjMGP0hxGSSPm1T4gsAu7jU8eqGx9YwwjvvJ0eotTjFqw7iJ6qE2Q==",
                     "requires": {
                         "@babel/runtime": "^7.22.10",
-                        "@mui/private-theming": "^5.14.6",
-                        "@mui/styled-engine": "^5.14.6",
+                        "@mui/private-theming": "^5.14.7",
+                        "@mui/styled-engine": "^5.14.7",
                         "@mui/types": "^7.2.4",
-                        "@mui/utils": "^5.14.6",
+                        "@mui/utils": "^5.14.7",
                         "clsx": "^2.0.0",
                         "csstype": "^3.1.2",
                         "prop-types": "^15.8.1"
@@ -6783,9 +6783,9 @@
                     }
                 },
                 "@mui/utils": {
-                    "version": "5.14.6",
-                    "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.6.tgz",
-                    "integrity": "sha512-AznpqLu6hrFnpHgcvsSSMCG+cDbkcCYfo+daUwBVReNYv4l+NQ8+wvBAF4aUMi155N7xWbbgh0cyKs6Wdsm3aA==",
+                    "version": "5.14.7",
+                    "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-5.14.7.tgz",
+                    "integrity": "sha512-RtheP/aBoPogVdi8vj8Vo2IFnRa4mZVmnD0RGlVZ49yF60rZs+xP4/KbpIrTr83xVs34QmHQ2aQ+IX7I0a0dDw==",
                     "requires": {
                         "@babel/runtime": "^7.22.10",
                         "@types/prop-types": "^15.7.5",
@@ -7094,97 +7094,12 @@
                                         "strip-ansi": "^7.0.1"
                                     }
                                 },
-                                "string-width-cjs": {
-                                    "version": "npm:string-width@4.2.3",
-                                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                                    "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                                    "requires": {
-                                        "emoji-regex": "^8.0.0",
-                                        "is-fullwidth-code-point": "^3.0.0",
-                                        "strip-ansi": "^6.0.1"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        },
-                                        "emoji-regex": {
-                                            "version": "8.0.0",
-                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                                        },
-                                        "strip-ansi": {
-                                            "version": "6.0.1",
-                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                            "requires": {
-                                                "ansi-regex": "^5.0.1"
-                                            }
-                                        }
-                                    }
-                                },
                                 "strip-ansi": {
                                     "version": "7.1.0",
                                     "bundled": true,
                                     "requires": {
                                         "ansi-regex": "^6.0.1"
                                     }
-                                },
-                                "strip-ansi-cjs": {
-                                    "version": "npm:strip-ansi@6.0.1",
-                                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                    "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                    "requires": {
-                                        "ansi-regex": "^5.0.1"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        }
-                                    }
-                                },
-                                "wrap-ansi-cjs": {
-                                    "version": "npm:wrap-ansi@7.0.0",
-                                    "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
-                                    "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
-                                    "requires": {
-                                        "ansi-styles": "^4.0.0",
-                                        "string-width": "^4.1.0",
-                                        "strip-ansi": "^6.0.0"
-                                    },
-                                    "dependencies": {
-                                        "ansi-regex": {
-                                            "version": "5.0.1",
-                                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
-                                            "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
-                                        },
-                                        "emoji-regex": {
-                                            "version": "8.0.0",
-                                            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
-                                            "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
-                                        },
-                                        "string-width": {
-                                            "version": "4.2.3",
-                                            "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
-                                            "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
-                                            "requires": {
-                                                "emoji-regex": "^8.0.0",
-                                                "is-fullwidth-code-point": "^3.0.0",
-                                                "strip-ansi": "^6.0.1"
-                                            }
-                                        },
-                                        "strip-ansi": {
-                                            "version": "6.0.1",
-                                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
-                                            "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
-                                            "requires": {
-                                                "ansi-regex": "^5.0.1"
-                                            }
-                                        }
-                                    }
                                 }
                             }
                         },
@@ -8689,7 +8604,7 @@
                             }
                         },
                         "string-width-cjs": {
-                            "version": "npm:string-width-cjs@4.2.3",
+                            "version": "npm:string-width@4.2.3",
                             "bundled": true,
                             "requires": {
                                 "emoji-regex": "^8.0.0",
@@ -8712,7 +8627,7 @@
                             }
                         },
                         "strip-ansi-cjs": {
-                            "version": "npm:strip-ansi-cjs@6.0.1",
+                            "version": "npm:strip-ansi@6.0.1",
                             "bundled": true,
                             "requires": {
                                 "ansi-regex": "^5.0.1"
@@ -8871,7 +8786,7 @@
                             }
                         },
                         "wrap-ansi-cjs": {
-                            "version": "npm:wrap-ansi-cjs@7.0.0",
+                            "version": "npm:wrap-ansi@7.0.0",
                             "bundled": true,
                             "requires": {
                                 "ansi-styles": "^4.0.0",
@@ -8903,9 +8818,9 @@
                     "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g=="
                 },
                 "postcss": {
-                    "version": "8.4.28",
-                    "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.28.tgz",
-                    "integrity": "sha512-Z7V5j0cq8oEKyejIKfpD8b4eBy9cwW2JWPk0+fB1HOAMsfHbnAXLLS+PfVWlzMSLQaWttKDt607I0XHmpE67Vw==",
+                    "version": "8.4.29",
+                    "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz",
+                    "integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==",
                     "requires": {
                         "nanoid": "^3.3.6",
                         "picocolors": "^1.0.0",
@@ -10081,14 +9996,14 @@
                     }
                 },
                 "caniuse-lite": {
-                    "version": "1.0.30001524",
-                    "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001524.tgz",
-                    "integrity": "sha512-Jj917pJtYg9HSJBF95HVX3Cdr89JUyLT4IZ8SvM5aDRni95swKgYi3TgYLH5hnGfPE/U1dg6IfZ50UsIlLkwSA=="
+                    "version": "1.0.30001525",
+                    "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001525.tgz",
+                    "integrity": "sha512-/3z+wB4icFt3r0USMwxujAqRvaD/B7rvGTsKhbhSQErVrJvkZCLhgNLJxU8MevahQVH6hCU9FsHdNUFbiwmE7Q=="
                 },
                 "electron-to-chromium": {
-                    "version": "1.4.503",
-                    "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.503.tgz",
-                    "integrity": "sha512-LF2IQit4B0VrUHFeQkWhZm97KuJSGF2WJqq1InpY+ECpFRkXd8yTIaTtJxsO0OKDmiBYwWqcrNaXOurn2T2wiA=="
+                    "version": "1.4.506",
+                    "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.506.tgz",
+                    "integrity": "sha512-xxGct4GPAKSRlrLBtJxJFYy74W11zX6PO9GyHgl/U+2s3Dp0ZEwAklDfNHXOWcvH7zWMpsmgbR0ggEuaYAVvHA=="
                 }
             }
         },
@@ -10445,6 +10360,16 @@
             "integrity": "sha1-WW6WmP0MgOEgOMK4LW6xs1tiJNk=",
             "dev": true
         },
+        "d": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
+            "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
+            "dev": true,
+            "requires": {
+                "es5-ext": "^0.10.50",
+                "type": "^1.0.1"
+            }
+        },
         "d3": {
             "version": "7.8.4",
             "resolved": "https://registry.npmjs.org/d3/-/d3-7.8.4.tgz",
@@ -11825,6 +11750,28 @@
                 "is-symbol": "^1.0.2"
             }
         },
+        "es5-ext": {
+            "version": "0.10.62",
+            "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.62.tgz",
+            "integrity": "sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA==",
+            "dev": true,
+            "requires": {
+                "es6-iterator": "^2.0.3",
+                "es6-symbol": "^3.1.3",
+                "next-tick": "^1.1.0"
+            }
+        },
+        "es6-iterator": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
+            "integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
+            "dev": true,
+            "requires": {
+                "d": "1",
+                "es5-ext": "^0.10.35",
+                "es6-symbol": "^3.1.1"
+            }
+        },
         "es6-promise": {
             "version": "3.2.1",
             "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-3.2.1.tgz",
@@ -11836,6 +11783,7 @@
             "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==",
             "dev": true,
             "requires": {
+                "d": "^1.0.1",
                 "ext": "^1.1.2"
             }
         },
@@ -27648,6 +27596,12 @@
             "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
             "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
         },
+        "type": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz",
+            "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==",
+            "dev": true
+        },
         "type-check": {
             "version": "0.4.0",
             "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
diff --git a/package.json b/package.json
index 5f7289719..de44b31f3 100644
--- a/package.json
+++ b/package.json
@@ -178,7 +178,7 @@
         "body-parser": "^1.19.2",
         "bootstrap": "^4.6.1",
         "brotli": "^1.3.3",
-        "browndash-components": "^0.1.02",
+        "browndash-components": "^0.1.21",
         "browser-assert": "^1.2.1",
         "bson": "^4.6.1",
         "canvas": "^2.9.3",
diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 539c62833..9f83f105e 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -773,8 +773,8 @@ export class CurrentUserUtils {
                             CurrentUserUtils.createToolButton(opts), scripts, funcs);
         
         const btnDescs = [// setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet
-            { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true},                                                                                               scripts: { onClick:  `snapshotDashboard()`}},
-            { opts: { title: "Recordings",            toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script:  `{ return replayWorkspace(value, _readOnly_); }`}},
+            { opts: { title: "Replicate",icon:"camera",toolTip: "Copy dashboard layout",btnType: ButtonType.ClickButton, expertMode: true},                                                                                              scripts: { onClick:  `snapshotDashboard()`}},
+            { opts: { title: "Recordings",            toolTip: "Workspace Recordings", btnType: ButtonType.DropdownList,expertMode: false, ignoreClick: true, width: 100}, funcs: {hidden: `false`, btnList:`getWorkspaceRecordings()`}, scripts: { script:  `{ return replayWorkspace(value, _readOnly_); }`, onDragScript: `{ return startRecordingDrag(value); }`}},
             { opts: { title: "Stop Rec",icon: "stop", toolTip: "Stop recording",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `!isWorkspaceRecording()`},                                                  scripts: { onClick: `stopWorkspaceRecording()`}},
             { opts: { title: "Play",    icon: "play", toolTip: "Play recording",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Paused}"`},                       scripts: { onClick: `resumeWorkspaceReplaying(getCurrentRecording())`}},
             { opts: { title: "Pause",   icon: "pause",toolTip: "Pause playback",       btnType: ButtonType.ClickButton, expertMode: false}, funcs: {hidden: `isWorkspaceReplaying() !== "${media_state.Playing}"`},                      scripts: { onClick: `pauseWorkspaceReplaying(getCurrentRecording())`}},
diff --git a/src/client/views/nodes/FontIconBox/FontIconBox.tsx b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
index 9bc9f2b52..ea7c2de82 100644
--- a/src/client/views/nodes/FontIconBox/FontIconBox.tsx
+++ b/src/client/views/nodes/FontIconBox/FontIconBox.tsx
@@ -7,7 +7,7 @@ import * as React from 'react';
 import { Doc, DocListCast, StrListCast } from '../../../../fields/Doc';
 import { ScriptField } from '../../../../fields/ScriptField';
 import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types';
-import { Utils } from '../../../../Utils';
+import { emptyFunction, setupMoveUpEvents, Utils } from '../../../../Utils';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { SelectionManager } from '../../../util/SelectionManager';
 import { SettingsManager } from '../../../util/SettingsManager';
@@ -176,6 +176,18 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
         );
     }
 
+    dropdownItemDown = (e: React.PointerEvent, value: string | number) => {
+        setupMoveUpEvents(
+            this,
+            e,
+            (e: PointerEvent) => {
+                return ScriptCast(this.rootDoc.onDragScript)?.script.run({ this: this.layoutDoc, self: this.rootDoc, value: { doc: value, e } }).result;
+            },
+            emptyFunction,
+            emptyFunction
+        );
+    };
+
     /**
      * Dropdown list
      */
@@ -240,6 +252,7 @@ export class FontIconBox extends DocComponent<ButtonProps>() {
                 background={SettingsManager.userVariantColor}
                 type={Type.TERT}
                 dropdownType={DropdownType.SELECT}
+                onItemDown={this.dropdownItemDown}
                 items={list}
                 tooltip={this.label}
                 fillWidth
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 3412b2dd2..1f113110b 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -1,10 +1,11 @@
 import { action, observable } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
+import { DateField } from '../../../../fields/DateField';
 import { Doc, DocListCast } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { listSpec } from '../../../../fields/Schema';
-import { BoolCast, Cast, DocCast, StrCast } from '../../../../fields/Types';
+import { List } from '../../../../fields/List';
+import { BoolCast, DocCast } from '../../../../fields/Types';
 import { VideoField } from '../../../../fields/URLField';
 import { Upload } from '../../../../server/SharedMediaTypes';
 import { Docs } from '../../../documents/Documents';
@@ -17,12 +18,10 @@ import { Presentation } from '../../../util/TrackMovements';
 import { undoBatch } from '../../../util/UndoManager';
 import { CollectionFreeFormView } from '../../collections/collectionFreeForm/CollectionFreeFormView';
 import { ViewBoxBaseComponent } from '../../DocComponent';
+import { media_state } from '../AudioBox';
 import { FieldView, FieldViewProps } from '../FieldView';
 import { VideoBox } from '../VideoBox';
 import { RecordingView } from './RecordingView';
-import { DateField } from '../../../../fields/DateField';
-import { media_state } from '../AudioBox';
-import { List } from '../../../../fields/List';
 
 @observer
 export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -87,15 +86,22 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
      */
     @undoBatch
     @action
-    public static WorkspaceStartRecording() {
-        const screengrabber = Docs.Create.ScreenshotDocument({
-            title: `${new DateField()}-${Doc.ActiveDashboard?.title ?? ''}`,
-            _width: 205,
-            _height: 115,
-        });
+    public static WorkspaceStartRecording(value: string) {
+        const screengrabber =
+            value === 'Record Workspace'
+                ? Docs.Create.ScreenshotDocument({
+                      title: `${new DateField()}-${Doc.ActiveDashboard?.title ?? ''}`,
+                      _width: 205,
+                      _height: 115,
+                  })
+                : Docs.Create.WebCamDocument(`${new DateField()}-${Doc.ActiveDashboard?.title ?? ''}`, {
+                      title: `${new DateField()}-${Doc.ActiveDashboard?.title ?? ''}`,
+                      _width: 205,
+                      _height: 115,
+                  });
         screengrabber.overlayX = 70; //was -400
         screengrabber.overlayY = 590; //was 0
-        screengrabber[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+        Doc.GetProto(screengrabber)[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
         Doc.AddToMyOverlay(screengrabber); //just adds doc to overlay
         DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
             RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
@@ -204,9 +210,6 @@ export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     static screengrabber: RecordingBox | undefined;
 }
 
-ScriptingGlobals.add(function startWorkspaceRecording() {
-    RecordingBox.WorkspaceStartRecording();
-});
 ScriptingGlobals.add(function stopWorkspaceRecording() {
     RecordingBox.WorkspaceStopRecording();
 });
@@ -222,7 +225,7 @@ ScriptingGlobals.add(function getCurrentRecording() {
     return Doc.UserDoc().currentRecording;
 });
 ScriptingGlobals.add(function getWorkspaceRecordings() {
-    return new List<any>(['Record Workspace', ...DocListCast(Doc.UserDoc().workspaceRecordings)]);
+    return new List<any>(['Record Workspace', `Record Webcam`, ...DocListCast(Doc.UserDoc().workspaceRecordings)]);
 });
 ScriptingGlobals.add(function isWorkspaceRecording() {
     return Doc.UserDoc().workspaceRecordingState === media_state.Recording;
@@ -232,7 +235,7 @@ ScriptingGlobals.add(function isWorkspaceReplaying() {
 });
 ScriptingGlobals.add(function replayWorkspace(value: Doc | string, _readOnly_: boolean) {
     if (_readOnly_) return DocCast(Doc.UserDoc().currentRecording) ?? 'Record Workspace';
-    if (typeof value === 'string') RecordingBox.WorkspaceStartRecording();
+    if (typeof value === 'string') RecordingBox.WorkspaceStartRecording(value);
     else RecordingBox.replayWorkspace(value);
 });
 ScriptingGlobals.add(function pauseWorkspaceReplaying(value: Doc, _readOnly_: boolean) {
@@ -242,6 +245,13 @@ ScriptingGlobals.add(function resumeWorkspaceReplaying(value: Doc, _readOnly_: b
     RecordingBox.resumeWorkspaceReplaying(value);
 });
 
+ScriptingGlobals.add(function startRecordingDrag(value: { doc: Doc | string; e: React.PointerEvent }) {
+    if (DocCast(value.doc)) {
+        DragManager.StartDocumentDrag([value.e.target as HTMLElement], new DragManager.DocumentDragData([DocCast(value.doc)], 'embed'), value.e.clientX, value.e.clientY);
+        value.e.preventDefault();
+        return true;
+    }
+});
 ScriptingGlobals.add(function renderDropdown() {
     if (!Doc.UserDoc().workspaceRecordings || DocListCast(Doc.UserDoc().workspaceRecordings).length === 0) {
         return true;
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 4ebc93165..26ad8b7bb 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -26,6 +26,7 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
+import { TrackMovements } from '../../util/TrackMovements';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -236,9 +237,19 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
             this._videoRef!.srcObject = await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
             this._videoRec = new MediaRecorder(this._videoRef!.srcObject);
             const vid_chunks: any = [];
-            this._videoRec.onstart = () => (this.dataDoc[this.props.fieldKey + '_recordingStart'] = new DateField(new Date()));
+            this._videoRec.onstart = () => {
+                if (this.dataDoc[this.props.fieldKey + '_trackScreen']) TrackMovements.Instance.start();
+                this.dataDoc[this.props.fieldKey + '_recordingStart'] = new DateField(new Date());
+            };
             this._videoRec.ondataavailable = (e: any) => vid_chunks.push(e.data);
             this._videoRec.onstop = async (e: any) => {
+                const presentation = TrackMovements.Instance.yieldPresentation();
+                if (presentation?.movements) {
+                    const presCopy = { ...presentation };
+                    presCopy.movements = presentation.movements.map(movement => ({ ...movement, doc: movement.doc[Id] })) as any;
+                    this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
+                }
+                TrackMovements.Instance.finish();
                 const file = new File(vid_chunks, `${this.rootDoc[Id]}.mkv`, { type: vid_chunks[0].type, lastModified: Date.now() });
                 const [{ result }] = await Networking.UploadFilesToServer({ file });
                 this.dataDoc[this.fieldKey + '_duration'] = (new Date().getTime() - this.recordingStart!) / 1000;
-- 
cgit v1.2.3-70-g09d2


From 7bc82b283bc7aa11a163c95607a6e192439bd601 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Fri, 8 Sep 2023 10:10:09 -0400
Subject: added cancel trim and revert buttons to audiobox

---
 src/client/documents/Documents.ts                  |  4 +-
 src/client/views/SidebarAnnos.tsx                  |  1 -
 src/client/views/collections/CollectionMenu.tsx    |  3 +-
 .../collections/CollectionStackedTimeline.tsx      |  4 ++
 src/client/views/nodes/AudioBox.tsx                | 56 ++++++++++++++++++----
 src/client/views/nodes/ScreenshotBox.tsx           |  3 +-
 .../views/nodes/formattedText/FormattedTextBox.tsx |  9 ++--
 7 files changed, 63 insertions(+), 17 deletions(-)

(limited to 'src/client/views/nodes/ScreenshotBox.tsx')

diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e413d4389..90c090f29 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -32,7 +32,7 @@ import { ContextMenu } from '../views/ContextMenu';
 import { ContextMenuProps } from '../views/ContextMenuItem';
 import { DFLT_IMAGE_NATIVE_DIM } from '../views/global/globalCssVariables.scss';
 import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth, ActiveIsInkMask, InkingStroke } from '../views/InkingStroke';
-import { AudioBox } from '../views/nodes/AudioBox';
+import { AudioBox, media_state } from '../views/nodes/AudioBox';
 import { ColorBox } from '../views/nodes/ColorBox';
 import { ComparisonBox } from '../views/nodes/ComparisonBox';
 import { DataVizBox } from '../views/nodes/DataVizBox/DataVizBox';
@@ -269,7 +269,7 @@ export class DocumentOptions {
     _label_maxFontSize?: NUMt = new NumInfo('maximum font size for labelBoxes', false);
     stroke_width?: NUMt = new NumInfo('width of an ink stroke', false);
     icon_label?: STRt = new StrInfo('label to use for a fontIcon doc (otherwise, the title is used)', false);
-    mediaState?: STRt = new StrInfo('status of audio/video media document: "pendingRecording", "recording", "paused", "playing"', false);
+    mediaState?: STRt = new StrInfo(`status of audio/video media document: ${media_state.PendingRecording}, ${media_state.Recording}, ${media_state.Paused}, ${media_state.Playing}`, false);
     recording?: BOOLt = new BoolInfo('whether WebCam is recording or not');
     autoPlayAnchors?: BOOLt = new BoolInfo('whether to play audio/video when an anchor is clicked in a stackedTimeline.');
     dontPlayLinkOnSelect?: BOOLt = new BoolInfo('whether an audio/video should start playing when a link is followed to it.');
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index f3452c780..ff347d65f 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -79,7 +79,6 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
             _layout_autoHeight: true,
             _text_fontSize: StrCast(Doc.UserDoc().fontSize),
             _text_fontFamily: StrCast(Doc.UserDoc().fontFamily),
-            target: 'HELLO' as any,
         });
         FormattedTextBox.SelectOnLoad = target[Id];
         FormattedTextBox.DontSelectInitialText = true;
diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx
index 5c9dd2058..78ab0797b 100644
--- a/src/client/views/collections/CollectionMenu.tsx
+++ b/src/client/views/collections/CollectionMenu.tsx
@@ -41,6 +41,7 @@ import { COLLECTION_BORDER_WIDTH } from './CollectionView';
 import { TabDocView } from './TabDocView';
 import { CollectionFreeFormView } from './collectionFreeForm';
 import { CollectionLinearView } from './collectionLinear';
+import { media_state } from '../nodes/AudioBox';
 
 interface CollectionMenuProps {
     panelHeight: () => number;
@@ -579,7 +580,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu
     @undoBatch
     @action
     startRecording = () => {
-        const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _layout_fitWidth: true, _width: 400, _height: 200, mediaState: 'pendingRecording' });
+        const doc = Docs.Create.ScreenshotDocument({ title: 'screen recording', _layout_fitWidth: true, _width: 400, _height: 200, mediaState: media_state.PendingRecording });
         CollectionDockingView.AddSplit(doc, OpenWhereMod.right);
     };
 
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 0a5a80936..ad3160a08 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -161,6 +161,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
         this.layoutDoc.clipEnd = this.trimEnd;
         this._trimming = TrimScope.None;
     }
+    @action
+    public CancelTrimming() {
+        this._trimming = TrimScope.None;
+    }
 
     @action
     public setZoom(zoom: number) {
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 50b2432d2..8d80f1364 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,9 +1,10 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { DateField } from '../../../fields/DateField';
-import { Doc, DocListCast } from '../../../fields/Doc';
+import { Doc } from '../../../fields/Doc';
 import { ComputedField } from '../../../fields/ScriptField';
 import { Cast, DateCast, NumCast } from '../../../fields/Types';
 import { AudioField, nullAudio } from '../../../fields/URLField';
@@ -18,9 +19,9 @@ import { ContextMenu } from '../ContextMenu';
 import { ContextMenuProps } from '../ContextMenuItem';
 import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from '../DocComponent';
 import './AudioBox.scss';
+import { DocFocusOptions } from './DocumentView';
 import { FieldView, FieldViewProps } from './FieldView';
 import { PinProps, PresBox } from './trails';
-import { DocFocusOptions } from './DocumentView';
 
 /**
  * AudioBox
@@ -67,7 +68,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     _stream: MediaStream | undefined; // passed to MediaRecorder, records device input audio
     _play: any = null; // timeout for playback
 
-    @observable _stackedTimeline: any; // CollectionStackedTimeline ref
+    @observable _stackedTimeline: CollectionStackedTimeline | null | undefined; // CollectionStackedTimeline ref
     @observable _finished: boolean = false; // has playback reached end of clip
     @observable _volume: number = 1;
     @observable _muted: boolean = false;
@@ -476,9 +477,35 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
         this.timeline?.StartTrimming(scope);
     };
 
+    // for trim button, double click displays full clip, single displays curr trim bounds
+    onResetPointerDown = (e: React.PointerEvent) => {
+        e.stopPropagation();
+        this.timeline &&
+            setupMoveUpEvents(
+                this,
+                e,
+                returnFalse,
+                returnFalse,
+                action(e => {
+                    if (this.timeline?.IsTrimming !== TrimScope.None) {
+                        this.timeline?.CancelTrimming();
+                    } else {
+                        this.beginEndtime = this.timeline?.trimEnd;
+                        this.beginStarttime = this.timeline?.trimStart;
+                        this.startTrim(TrimScope.All);
+                    }
+                })
+            );
+    };
+
+    beginEndtime: number | undefined;
+    beginStarttime: number | undefined;
+
     // for trim button, double click displays full clip, single displays curr trim bounds
     onClipPointerDown = (e: React.PointerEvent) => {
         e.stopPropagation();
+        this.beginEndtime = this.timeline?.trimEnd;
+        this.beginStarttime = this.timeline?.trimStart;
         this.timeline &&
             setupMoveUpEvents(
                 this,
@@ -529,7 +556,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 r,
                 (e, de) => {
                     const [xp, yp] = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
-                    de.complete.docDragData && this.timeline.internalDocDrop(e, de, de.complete.docDragData, xp);
+                    de.complete.docDragData && this.timeline?.internalDocDrop(e, de, de.complete.docDragData, xp);
                 },
                 this.layoutDoc,
                 undefined
@@ -591,9 +618,22 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         </div>
 
                         {!this.miniPlayer && (
-                            <div className="audiobox-button" title={this.timeline?.IsTrimming !== TrimScope.None ? 'finish' : 'trim'} onPointerDown={this.onClipPointerDown}>
-                                <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size={'1x'} />
-                            </div>
+                            <>
+                                <Tooltip title={<>trim audio</>}>
+                                    <div className="audiobox-button" onPointerDown={this.onClipPointerDown}>
+                                        <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'check' : 'cut'} size={'1x'} />
+                                    </div>
+                                </Tooltip>
+                                {this.timeline?.IsTrimming == TrimScope.None && !NumCast(this.layoutDoc.clipStart) && NumCast(this.layoutDoc.clipEnd) === this.rawDuration ? (
+                                    <></>
+                                ) : (
+                                    <Tooltip title={<>{this.timeline?.IsTrimming !== TrimScope.None ? 'Cancel trimming' : 'Edit original timeline'}</>}>
+                                        <div className="audiobox-button" onPointerDown={this.onResetPointerDown}>
+                                            <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? 'cancel' : 'arrows-left-right'} size={'1x'} />
+                                        </div>
+                                    </Tooltip>
+                                )}
+                            </>
                         )}
                     </div>
                     <div className="controls-right">
@@ -658,7 +698,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     @computed get renderTimeline() {
         return (
             <CollectionStackedTimeline
-                ref={action((r: any) => (this._stackedTimeline = r))}
+                ref={action((r: CollectionStackedTimeline | null) => (this._stackedTimeline = r))}
                 {...this.props}
                 CollectionFreeFormDocumentView={undefined}
                 dataFieldKey={this.fieldKey}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 26ad8b7bb..ebb8a3374 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -27,6 +27,7 @@ import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
 import { TrackMovements } from '../../util/TrackMovements';
+import { media_state } from './AudioBox';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
@@ -181,7 +182,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatabl
                 ref={r => {
                     this._videoRef = r;
                     setTimeout(() => {
-                        if (this.rootDoc.mediaState === 'pendingRecording' && this._videoRef) {
+                        if (this.rootDoc.mediaState === media_state.PendingRecording && this._videoRef) {
                             this.toggleRecording();
                         }
                     }, 100);
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 58b824159..6fdde4b6b 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -71,6 +71,7 @@ import { schema } from './schema_rts';
 import { SummaryView } from './SummaryView';
 import applyDevTools = require('prosemirror-dev-tools');
 import React = require('react');
+import { media_state } from '../AudioBox';
 const translateGoogleApi = require('translate-google-api');
 export const GoogleRef = 'googleDocId';
 type PullHandler = (exportState: Opt<GoogleApiClientUtils.Docs.ImportResult>, dataDoc: Doc) => void;
@@ -154,10 +155,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
     }
     @computed get _recordingDictation() {
-        return this.dataDoc?.mediaState === 'recording';
+        return this.dataDoc?.mediaState === media_state.Recording;
     }
     set _recordingDictation(value) {
-        !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? 'recording' : undefined);
+        !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? media_state.Recording : undefined);
     }
     @computed get config() {
         this._keymap = buildKeymap(schema, this.props);
@@ -271,7 +272,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 if (target) {
                     anchor.followLinkAudio = true;
                     let stopFunc: any;
-                    Doc.GetProto(target).mediaState = 'recording';
+                    Doc.GetProto(target).mediaState = media_state.Recording;
                     Doc.GetProto(target).audioAnnoState = 'recording';
                     DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
                     let reactionDisposer = reaction(
@@ -389,7 +390,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         let link;
         LinkManager.Links(this.dataDoc).forEach((l, i) => {
             const anchor = (l.link_anchor_1 as Doc).annotationOn ? (l.link_anchor_1 as Doc) : (l.link_anchor_2 as Doc).annotationOn ? (l.link_anchor_2 as Doc) : undefined;
-            if (anchor && (anchor.annotationOn as Doc).mediaState === 'recording') {
+            if (anchor && (anchor.annotationOn as Doc).mediaState === media_state.Recording) {
                 linkTime = NumCast(anchor._timecodeToShow /* audioStart */);
                 linkAnchor = anchor;
                 link = l;
-- 
cgit v1.2.3-70-g09d2

