From 06debb1314e4cadb00c29bd70a747971d1d9e98a Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 17 Aug 2023 19:32:34 -0400
Subject: normalized some field names data_dictation, etc.   added hover info
 for field names in keyvalue pane.

---
 src/client/views/nodes/VideoBox.tsx | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

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

diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 1f52c2d92..0f470347f 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -103,7 +103,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     // returns the path of the audio file
     @computed get audiopath() {
-        const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null);
+        const field = Cast(this.props.Document[this.props.fieldKey + '_audio'], AudioField, null);
         const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
         return field?.url.href ?? vfield?.url.href ?? '';
     }
@@ -959,7 +959,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                     {...this.props}
                     dataFieldKey={this.fieldKey}
                     fieldKey={this.annotationKey}
-                    dictationKey={this.fieldKey + '-dictation'}
+                    dictationKey={this.fieldKey + '_dictation'}
                     mediaPath={this.audiopath}
                     thumbnails={() => StrListCast(this.dataDoc[this.fieldKey + '_thumbnails'])}
                     renderDepth={this.props.renderDepth + 1}
-- 
cgit v1.2.3-70-g09d2


From 9dfca50488241998fa1a99f660b12f43ad803d38 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 17 Aug 2023 22:31:42 -0400
Subject: Added the beginning of an A/V recording button

---
 src/client/util/CurrentUserUtils.ts                |  9 ++--
 .../views/nodes/RecordingBox/RecordingBox.tsx      | 63 ++++++++++++++++++----
 .../views/nodes/RecordingBox/RecordingView.tsx     | 44 ++++++++++-----
 src/client/views/nodes/ScreenshotBox.tsx           |  2 +-
 src/client/views/nodes/VideoBox.tsx                |  6 +--
 src/server/DashUploadUtils.ts                      |  2 +-
 6 files changed, 95 insertions(+), 31 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 114249b24..b148a5e26 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -701,10 +701,10 @@ export class CurrentUserUtils {
                     CollectionViewType.Carousel3D, CollectionViewType.Linear, CollectionViewType.Map,
                     CollectionViewType.Grid, CollectionViewType.NoteTaking]),
               title: "Perspective",                    toolTip: "View",                 btnType: ButtonType.DropdownList,                                                                                                                                    ignoreClick: true, width: 100, scripts: { script: 'setView(value, _readOnly_)'}},
-            { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         expertMode: false,                                                                                                                         width: 30,  scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
+            { title: "Pin",     icon: "map-pin",       toolTip: "Pin View to Trail",    btnType: ButtonType.ClickButton,         expertMode: false,                                                                                                                             width: 30,  scripts: { onClick: 'pinWithView(altKey)'}, funcs: {hidden: "IsNoneSelected()"}},
             { title: "Fill",    icon: "fill-drip",     toolTip: "Background Fill Color",btnType: ButtonType.ColorButton,         expertMode: false,                                                                                                          ignoreClick: true, width: 30,  scripts: { script:  'return setBackgroundColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}}, // Only when a document is selected
-            { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         expertMode: true,                                                                                                          ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}},
-            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'},       scripts: { onClick: 'return { toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
+            { title: "Header",  icon: "heading",       toolTip: "Header Color",         btnType: ButtonType.ColorButton,         expertMode: true,                                                                                                           ignoreClick: true,             scripts: { script:  'return setHeaderColor(value, _readOnly_)'}, funcs: {hidden: "IsNoneSelected()"}},
+            { title: "Overlay", icon: "layer-group",   toolTip: "Overlay",              btnType: ButtonType.ToggleButton,        expertMode: true, toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)'},        scripts: { onClick: 'return { toggleOverlay(_readOnly_); }'}}, // Only when floating document is selected in freeform
             { title: "Back",    icon: "chevron-left",  toolTip: "Prev Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30,  scripts: { onClick: 'prevKeyFrame(_readOnly_)'}},
             { title: "Num",     icon:"",               toolTip: "Frame Number (click to toggle edit mode)",    btnType: ButtonType.TextButton,          expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)', buttonText: 'selectedDocs()?.lastElement()?.currentFrame?.toString()'}, width: 20, scripts: { onClick: '{ return curKeyFrame(_readOnly_);}'}},
             { title: "Fwd",     icon: "chevron-right", toolTip: "Next Animation Frame", btnType: ButtonType.ClickButton,         expertMode: true,  toolType:CollectionViewType.Freeform, funcs: {hidden: '!SelectionManager_selectedDocType(self.toolType, self.expertMode)'}, width: 30,  scripts: { onClick: 'nextKeyFrame(_readOnly_)'}},
@@ -713,7 +713,8 @@ export class CurrentUserUtils {
             { title: "Doc",     icon: "Doc",           toolTip: "Freeform Doc tools",   subMenu: CurrentUserUtils.freeTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode, true)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
             { title: "View",    icon: "View",          toolTip: "View tools",           subMenu: CurrentUserUtils.viewTools(),   expertMode: false, toolType:CollectionViewType.Freeform, funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Always available
             { title: "Web",     icon: "Web",           toolTip: "Web functions",        subMenu: CurrentUserUtils.webTools(),    expertMode: false, toolType:DocumentType.WEB,            funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Web is selected
-            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} } // Only when Schema is selected
+            { title: "Schema",  icon: "Schema",linearBtnWidth:58,toolTip: "Schema functions",subMenu: CurrentUserUtils.schemaTools(), expertMode: false, toolType:CollectionViewType.Schema,   funcs: {hidden: `!SelectionManager_selectedDocType(self.toolType, self.expertMode)`, linearView_IsOpen: `SelectionManager_selectedDocType(self.toolType, self.expertMode)`} }, // Only when Schema is selected
+            { title: "Audio",   icon: "microphone",    toolTip: "Dictate",              btnType: ButtonType.ToggleButton,        expertMode: false,                                                                                                          ignoreClick: true,             scripts: { onClick:  'return toggleRecording(_readOnly_)'}, funcs: { }}
         ];
     }
 
diff --git a/src/client/views/nodes/RecordingBox/RecordingBox.tsx b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
index 04f11a5df..8fa2861b6 100644
--- a/src/client/views/nodes/RecordingBox/RecordingBox.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingBox.tsx
@@ -4,28 +4,28 @@ import * as React from 'react';
 import { VideoField } from '../../../../fields/URLField';
 import { Upload } from '../../../../server/SharedMediaTypes';
 import { ViewBoxBaseComponent } from '../../DocComponent';
-import { FieldView } from '../FieldView';
+import { FieldView, FieldViewProps } from '../FieldView';
 import { VideoBox } from '../VideoBox';
 import { RecordingView } from './RecordingView';
 import { DocumentType } from '../../../documents/DocumentTypes';
 import { Presentation } from '../../../util/TrackMovements';
 import { Doc } from '../../../../fields/Doc';
 import { Id } from '../../../../fields/FieldSymbols';
-import { DocCast } from '../../../../fields/Types';
+import { BoolCast, DocCast } from '../../../../fields/Types';
+import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
+import { DocumentManager } from '../../../util/DocumentManager';
+import { Docs } from '../../../documents/Documents';
 
 @observer
-export class RecordingBox extends ViewBoxBaseComponent() {
+export class RecordingBox extends ViewBoxBaseComponent<FieldViewProps>() {
     public static LayoutString(fieldKey: string) {
         return FieldView.LayoutString(RecordingBox, fieldKey);
     }
 
     private _ref: React.RefObject<HTMLDivElement> = React.createRef();
 
-    constructor(props: any) {
-        super(props);
-    }
-
     componentDidMount() {
+        this.props.setContentView?.(this);
         Doc.SetNativeWidth(this.dataDoc, 1280);
         Doc.SetNativeHeight(this.dataDoc, 720);
     }
@@ -46,20 +46,63 @@ export class RecordingBox extends ViewBoxBaseComponent() {
 
         this.dataDoc.layout = VideoBox.LayoutString(this.fieldKey);
         this.dataDoc[this.props.fieldKey] = new VideoField(this.result.accessPaths.client);
-        this.dataDoc[this.fieldKey + '-recorded'] = true;
+        this.dataDoc[this.fieldKey + '_recorded'] = true;
         // stringify the presentation and store it
         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);
+            this.dataDoc[this.fieldKey + '_presentation'] = JSON.stringify(presCopy);
         }
     };
 
+    Record: undefined | (() => void);
+    Pause: undefined | (() => void);
+    Finish: undefined | (() => void);
+    getControls = (record: () => void, pause: () => void, finish: () => void) => {
+        this.Record = record;
+        this.Pause = pause;
+        this.Finish = finish;
+    };
+
     render() {
         return (
             <div className="recordingBox" ref={this._ref}>
-                {!this.result && <RecordingView setResult={this.setResult} setDuration={this.setVideoDuration} id={DocCast(this.rootDoc.proto)?.[Id] || ''} />}
+                {!this.result && (
+                    <RecordingView
+                        forceTrackScreen={BoolCast(this.layoutDoc[this.fieldKey + '_trackScreen'])}
+                        getControls={this.getControls}
+                        setResult={this.setResult}
+                        setDuration={this.setVideoDuration}
+                        id={DocCast(this.rootDoc.proto)?.[Id] || ''}
+                    />
+                )}
             </div>
         );
     }
+    static screengrabber: RecordingBox | undefined;
 }
+ScriptingGlobals.add(function toggleRecording(_readOnly_: boolean) {
+    if (_readOnly_) return RecordingBox.screengrabber ? true : false;
+    if (RecordingBox.screengrabber) {
+        RecordingBox.screengrabber.Pause?.();
+        setTimeout(() => {
+            RecordingBox.screengrabber?.Finish?.();
+            RecordingBox.screengrabber!.rootDoc.overlayX = 100;
+            RecordingBox.screengrabber!.rootDoc.overlayY = 100;
+            RecordingBox.screengrabber = undefined;
+        }, 100);
+    } else {
+        const screengrabber = Docs.Create.WebCamDocument('', {
+            _width: 384,
+            _height: 216,
+        });
+        screengrabber.overlayX = -400;
+        screengrabber.overlayY = 0;
+        screengrabber[Doc.LayoutFieldKey(screengrabber) + '_trackScreen'] = true;
+        Doc.AddToMyOverlay(screengrabber);
+        DocumentManager.Instance.AddViewRenderedCb(screengrabber, docView => {
+            RecordingBox.screengrabber = docView.ComponentView as RecordingBox;
+            RecordingBox.screengrabber.Record?.();
+        });
+    }
+}, 'toggle recording');
diff --git a/src/client/views/nodes/RecordingBox/RecordingView.tsx b/src/client/views/nodes/RecordingBox/RecordingView.tsx
index 51eb774e2..0e386b093 100644
--- a/src/client/views/nodes/RecordingBox/RecordingView.tsx
+++ b/src/client/views/nodes/RecordingBox/RecordingView.tsx
@@ -21,6 +21,8 @@ interface IRecordingViewProps {
     setResult: (info: Upload.AccessPathInfo, presentation?: Presentation) => void;
     setDuration: (seconds: number) => void;
     id: string;
+    getControls: (record: () => void, pause: () => void, finish: () => void) => void;
+    forceTrackScreen: boolean;
 }
 
 const MAXTIME = 100000;
@@ -60,14 +62,14 @@ export function RecordingView(props: IRecordingViewProps) {
     useEffect(() => {
         if (finished) {
             // make the total presentation that'll match the concatted video
-            let concatPres = trackScreen && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
+            let concatPres = (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.concatPresentations(videos.map(v => v.presentation as Presentation));
 
             // this async function uses the server to create the concatted video and then sets the result to it's accessPaths
             (async () => {
                 const videoFiles = videos.map((vid, i) => new File(vid.videoChunks, `segvideo${i}.mkv`, { type: vid.videoChunks[0].type, lastModified: Date.now() }));
 
                 // upload the segments to the server and get their server access paths
-                const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles.map(file => ({file})))).map(res => (res.result instanceof Error ? '' : res.result.accessPaths.agnostic.server));
+                const serverPaths: string[] = (await Networking.UploadFilesToServer(videoFiles.map(file => ({ file })))).map(res => (res.result instanceof Error ? '' : res.result.accessPaths.agnostic.server));
 
                 // concat the segments together using post call
                 const result: Upload.AccessPathInfo | Error = await Networking.PostToServer('/concatVideos', serverPaths);
@@ -132,7 +134,7 @@ export function RecordingView(props: IRecordingViewProps) {
         videoRecorder.current.onstart = (event: any) => {
             setRecording(true);
             // start the recording api when the video recorder starts
-            trackScreen && TrackMovements.Instance.start();
+            (trackScreen || props.forceTrackScreen) && TrackMovements.Instance.start();
         };
 
         videoRecorder.current.onstop = () => {
@@ -147,7 +149,7 @@ export function RecordingView(props: IRecordingViewProps) {
 
                 // depending on if a presenation exists, add it to the video
                 const presentation = TrackMovements.Instance.yieldPresentation();
-                setVideos(videos => [...videos, presentation != null && trackScreen ? { ...nextVideo, presentation } : nextVideo]);
+                setVideos(videos => [...videos, presentation != null && (trackScreen || props.forceTrackScreen) ? { ...nextVideo, presentation } : nextVideo]);
             }
 
             // reset the temporary chunks
@@ -159,9 +161,7 @@ export function RecordingView(props: IRecordingViewProps) {
     };
 
     // if this is called, then we're done recording all the segments
-    const finish = (e: React.PointerEvent) => {
-        e.stopPropagation();
-
+    const finish = () => {
         // call stop on the video recorder if active
         videoRecorder.current?.state !== 'inactive' && videoRecorder.current?.stop();
 
@@ -176,8 +176,7 @@ export function RecordingView(props: IRecordingViewProps) {
         setFinished(true);
     };
 
-    const pause = (e: React.PointerEvent) => {
-        e.stopPropagation();
+    const pause = () => {
         // if recording, then this is just a new segment
         videoRecorder.current?.state === 'recording' && videoRecorder.current.stop();
     };
@@ -217,6 +216,10 @@ export function RecordingView(props: IRecordingViewProps) {
         return toTwoDigit(minutes) + ' : ' + toTwoDigit(seconds);
     };
 
+    useEffect(() => {
+        props.getControls(record, pause, finish);
+    }, []);
+
     return (
         <div className="recording-container">
             <div className="video-wrapper">
@@ -227,7 +230,19 @@ export function RecordingView(props: IRecordingViewProps) {
                 </div>
                 <div className="controls">
                     <div className="controls-inner-container">
-                        <div className="record-button-wrapper">{recording ? <button className="stop-button" onPointerDown={pause} /> : <button className="record-button" onPointerDown={start} />}</div>
+                        <div className="record-button-wrapper">
+                            {recording ? (
+                                <button
+                                    className="stop-button"
+                                    onPointerDown={e => {
+                                        e.stopPropagation();
+                                        pause();
+                                    }}
+                                />
+                            ) : (
+                                <button className="record-button" onPointerDown={start} />
+                            )}
+                        </div>
 
                         {!recording &&
                             (videos.length > 0 ? (
@@ -236,7 +251,12 @@ export function RecordingView(props: IRecordingViewProps) {
                                         <MdBackspace onPointerDown={undoPrevious} />
                                     </IconContext.Provider>
                                     <IconContext.Provider value={{ color: '#cc1c08', className: 'video-edit-buttons' }}>
-                                        <FaCheckCircle onPointerDown={finish} />
+                                        <FaCheckCircle
+                                            onPointerDown={e => {
+                                                e.stopPropagation();
+                                                finish();
+                                            }}
+                                        />
                                     </IconContext.Provider>
                                 </div>
                             ) : (
@@ -244,7 +264,7 @@ export function RecordingView(props: IRecordingViewProps) {
                                     <label className="track-screen">
                                         <input
                                             type="checkbox"
-                                            checked={trackScreen}
+                                            checked={trackScreen || props.forceTrackScreen}
                                             onChange={e => {
                                                 setTrackScreen(e.target.checked);
                                             }}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 0a33a336f..83a29f071 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -17,6 +17,7 @@ import { DocUtils } from '../../documents/Documents';
 import { DocumentType } from '../../documents/DocumentTypes';
 import { Networking } from '../../Network';
 import { CaptureManager } from '../../util/CaptureManager';
+import { SettingsManager } from '../../util/SettingsManager';
 import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView';
 import { CollectionStackedTimeline } from '../collections/CollectionStackedTimeline';
 import { ContextMenu } from '../ContextMenu';
@@ -25,7 +26,6 @@ import { FieldView, FieldViewProps } from './FieldView';
 import { FormattedTextBox } from './formattedText/FormattedTextBox';
 import './ScreenshotBox.scss';
 import { VideoBox } from './VideoBox';
-import { SettingsManager } from '../../util/SettingsManager';
 
 declare class MediaRecorder {
     constructor(e: any, options?: any); // whatever MediaRecorder has
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 0f470347f..2177adeff 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -110,7 +110,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
 
     // returns the presentation data if it exists, null otherwise
     @computed get presentation() {
-        const data = this.dataDoc[this.fieldKey + '-presentation'];
+        const data = this.dataDoc[this.fieldKey + '_presentation'];
         return data ? JSON.parse(StrCast(data)) : null;
     }
 
@@ -524,7 +524,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                 icon: 'expand-arrows-alt',
             });
             // if the videobox was turned from a recording box
-            if (this.dataDoc[this.fieldKey + '-recorded'] === true) {
+            if (this.dataDoc[this.fieldKey + '_recorded'] === true) {
                 subitems.push({
                     description: 'Recreate recording',
                     event: () => {
@@ -533,7 +533,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
                         this.dataDoc[this.props.fieldKey] = '';
                         this.dataDoc[this.fieldKey + '_duration'] = '';
                         // delete assoicated presentation data
-                        this.dataDoc[this.fieldKey + '-presentation'] = '';
+                        this.dataDoc[this.fieldKey + '_presentation'] = '';
                     },
                     icon: 'expand-arrows-alt',
                 });
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index 337bb812f..117981a7c 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -99,7 +99,7 @@ export namespace DashUploadUtils {
             merge
                 .input(textFilePath)
                 .inputOptions(['-f concat', '-safe 0'])
-                .outputOptions('-c copy')
+                // .outputOptions('-c copy')
                 //.videoCodec("copy")
                 .save(outputFilePath)
                 .on('error', (err: any) => {
-- 
cgit v1.2.3-70-g09d2

