From 2c565fd81daca02cabb9598c699cedb7611c3841 Mon Sep 17 00:00:00 2001
From: ljungster <parkerljung@gmail.com>
Date: Sat, 12 Mar 2022 07:44:01 -0500
Subject: attempting to add note-taking

I think this has something to do with the view not being rendered in novice mode. Assuming this is an issue in CollectionMenu.tsx.

Essentially what I did was add a note-taking view wherever I found a stacking view (via global search)
---
 src/client/views/nodes/button/FontIconBox.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

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

diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index bd103dcf7..423318519 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -259,7 +259,7 @@ export class FontIconBox extends DocComponent<ButtonProps, FontIconDocument>(Fon
                 icon = "globe-asia";
                 text = "User Default";
             }
-            noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking];
+            noviceList = [CollectionViewType.Freeform, CollectionViewType.Schema, CollectionViewType.Stacking, CollectionViewType.NoteTaking];
         } else if (script === 'setFont') {
             const editorView = RichTextMenu.Instance?.TextView?.EditorView;
             text = StrCast((editorView ? RichTextMenu.Instance : Doc.UserDoc()).fontFamily);
-- 
cgit v1.2.3-70-g09d2


From f9cded6755ee236d88e21b3b2d4c6da357d059b2 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 8 Aug 2022 15:47:35 -0400
Subject: moved 5 sec anno recording button to documentButtonsBar.  made
 recording button appear only when there is one & click to play recording.

---
 src/client/util/CurrentUserUtils.ts                |  3 +-
 src/client/util/DictationManager.ts                |  4 --
 src/client/views/DocumentButtonBar.tsx             | 24 ++++++-
 src/client/views/PropertiesButtons.tsx             |  9 ---
 src/client/views/nodes/DocumentView.scss           |  6 +-
 src/client/views/nodes/DocumentView.tsx            | 82 +++++++++++++---------
 src/client/views/nodes/button/FontIconBox.tsx      |  9 ++-
 .../views/nodes/formattedText/FormattedTextBox.tsx | 12 +---
 8 files changed, 90 insertions(+), 59 deletions(-)

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

diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts
index 7856c913b..d19874720 100644
--- a/src/client/util/CurrentUserUtils.ts
+++ b/src/client/util/CurrentUserUtils.ts
@@ -107,7 +107,7 @@ export class CurrentUserUtils {
         const reqdClickList = reqdTempOpts.map(opts => {
             const allOpts = {...reqdClickOpts, ...opts.opts};
             const clickDoc = tempClicks ? DocListCast(tempClicks.data).find(doc => doc.title === opts.opts.title): undefined;
-            return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script,allOpts));
+            return DocUtils.AssignOpts(clickDoc, allOpts) ?? Docs.Create.ScriptingDocument(ScriptField.MakeScript(opts.script, allOpts),allOpts);
         });
 
         const reqdOpts:DocumentOptions = { title: "child click editors", _height:75, system: true};
@@ -633,6 +633,7 @@ export class CurrentUserUtils {
             { title: "Center", toolTip: "Center align", btnType: ButtonType.ToggleButton, icon: "align-center", scripts: {onClick:'{ return setAlignment("center", _readOnly_);}'} },
             { title: "Right",  toolTip: "Right align",  btnType: ButtonType.ToggleButton, icon: "align-right",  scripts: {onClick:'{ return setAlignment("right", _readOnly_);}'} },
             { title: "NoLink", toolTip: "Auto Link",    btnType: ButtonType.ToggleButton, icon: "link",         scripts: {onClick:'{ return toggleNoAutoLinkAnchor(_readOnly_);}'}},
+            { title: "Dictate",toolTip: "Dictate",      btnType: ButtonType.ToggleButton, icon: "microphone",   scripts: {onClick:'{ return toggleDictation(_readOnly_);}'}},
         ];
     }
 
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 13f036838..0a61f3478 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -152,7 +152,6 @@ export namespace DictationManager {
 
             return new Promise<string>((resolve, reject) => {
                 recognizer.onerror = (e: any) => {
-                    console.log('SPEECH error:', encodeURIComponent);
                     // e is SpeechRecognitionError but where is that defined?
                     if (!(indefinite && e.error === 'no-speech')) {
                         recognizer.stop();
@@ -162,7 +161,6 @@ export namespace DictationManager {
                 };
 
                 recognizer.onresult = (e: SpeechRecognitionEvent) => {
-                    console.log('RESULT: ', e);
                     current = synthesize(e, intra);
                     let matchedTerminator: string | undefined;
                     if (options?.terminators && (matchedTerminator = options.terminators.find(end => (current ? current.trim().toLowerCase().endsWith(end.toLowerCase()) : false)))) {
@@ -175,7 +173,6 @@ export namespace DictationManager {
                 };
 
                 recognizer.onend = (e: Event) => {
-                    console.log('END: ', e);
                     if (!indefinite || isManuallyStopped) {
                         return complete();
                     }
@@ -188,7 +185,6 @@ export namespace DictationManager {
                 };
 
                 const complete = () => {
-                    console.log('COMPLETE:');
                     if (indefinite) {
                         current && sessionResults.push(current);
                         sessionResults.length && resolve(sessionResults.join(inter || interSession));
diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx
index 1d4056759..265df3abc 100644
--- a/src/client/views/DocumentButtonBar.tsx
+++ b/src/client/views/DocumentButtonBar.tsx
@@ -6,7 +6,7 @@ import { observer } from 'mobx-react';
 import { Doc } from '../../fields/Doc';
 import { RichTextField } from '../../fields/RichTextField';
 import { Cast, NumCast } from '../../fields/Types';
-import { emptyFunction, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
+import { emptyFunction, returnFalse, setupMoveUpEvents, simulateMouseClick } from '../../Utils';
 import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager';
 import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils';
 import { Docs } from '../documents/Documents';
@@ -334,6 +334,27 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
             </Tooltip>
         );
     }
+
+    @observable _isRecording = false;
+    @computed
+    get recordButton() {
+        const targetDoc = this.view0?.props.Document;
+        return !targetDoc ? null : (
+            <Tooltip title={<div className="dash-tooltip">{'Click to record 5 second annotation'}</div>}>
+                <div
+                    className="documentButtonBar-icon"
+                    style={{ backgroundColor: this._isRecording ? 'red' : targetDoc.isLinkButton ? Colors.LIGHT_BLUE : Colors.DARK_GRAY, color: targetDoc.isLinkButton ? Colors.BLACK : Colors.WHITE }}
+                    onClick={undoBatch(
+                        action(e => {
+                            this._isRecording = true;
+                            this.props.views().map(view => view?.docView?.recordAudioAnnotation(action(() => (this._isRecording = false))));
+                        })
+                    )}>
+                    <FontAwesomeIcon className="documentdecorations-icon" size="sm" icon="microphone" />
+                </div>
+            </Tooltip>
+        );
+    }
     @observable _aliasDown = false;
     onTemplateButton = action((e: React.PointerEvent): void => {
         this._tooltipOpen = false;
@@ -411,6 +432,7 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV
                         <DocumentLinksButton View={this.view0} AlwaysOn={true} InMenu={true} StartLink={false} />
                     </div>
                 ) : null}
+                <div className="documentButtonBar-button">{this.recordButton}</div>
                 {
                     Doc.noviceMode ? null : <div className="documentButtonBar-button">{this.templateButton}</div>
                     /*<div className="documentButtonBar-button">
diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx
index 39e7b89c1..80c2c7705 100644
--- a/src/client/views/PropertiesButtons.tsx
+++ b/src/client/views/PropertiesButtons.tsx
@@ -68,14 +68,6 @@ export class PropertiesButtons extends React.Component<{}, {}> {
             on => 'thumbtack'
         );
     }
-    @computed get dictationButton() {
-        return this.propertyToggleBtn(
-            'Dictate',
-            '_showAudio',
-            on => `${on ? 'Hide' : 'Show'} dictation/recording controls`,
-            on => 'microphone'
-        );
-    }
     @computed get maskButton() {
         return this.propertyToggleBtn(
             'Mask',
@@ -365,7 +357,6 @@ export class PropertiesButtons extends React.Component<{}, {}> {
                 {toggle(this.titleButton)}
                 {toggle(this.captionButton)}
                 {toggle(this.lockButton)}
-                {toggle(this.dictationButton, { display: isNovice ? 'none' : '' })}
                 {toggle(this.onClickButton)}
                 {toggle(this.fitWidthButton)}
                 {toggle(this.freezeThumb)}
diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss
index c02692bfb..6ea697a2f 100644
--- a/src/client/views/nodes/DocumentView.scss
+++ b/src/client/views/nodes/DocumentView.scss
@@ -65,11 +65,13 @@
         width: 25px;
         height: 25;
         position: absolute;
-        top: 10px;
-        left: 10px;
+        top: 0;
+        left: 50%;
         border-radius: 25px;
         background: white;
         opacity: 0.3;
+        pointer-events: all;
+        cursor: default;
 
         svg {
             width: 90% !important;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 2524d66dd..4b2bd07ef 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -10,7 +10,7 @@ import { List } from '../../../fields/List';
 import { ObjectField } from '../../../fields/ObjectField';
 import { listSpec } from '../../../fields/Schema';
 import { ScriptField } from '../../../fields/ScriptField';
-import { BoolCast, Cast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, ImageCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types';
 import { AudioField } from '../../../fields/URLField';
 import { GetEffectiveAcl, SharingPermissions, TraceMobx } from '../../../fields/util';
 import { MobileInterface } from '../../../mobile/MobileInterface';
@@ -52,6 +52,8 @@ import { RadialMenu } from './RadialMenu';
 import { ScriptingBox } from './ScriptingBox';
 import { PresBox } from './trails/PresBox';
 import React = require('react');
+import { DictationManager } from '../../util/DictationManager';
+import { Tooltip } from '@material-ui/core';
 const { Howl } = require('howler');
 
 interface Window {
@@ -1005,16 +1007,16 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     linkButtonInverseScaling = () => (this.props.NativeDimScaling?.() || 1) * this.props.DocumentView().screenToLocalTransform().Scale;
     @computed get contents() {
         TraceMobx();
-        const audioView = !this.layoutDoc._showAudio ? null : (
-            <div className="documentView-audioBackground" onPointerDown={this.recordAudioAnnotation} onPointerEnter={this.onPointerEnter}>
-                <FontAwesomeIcon
-                    className="documentView-audioFont"
-                    style={{ color: [DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']).length ? 'blue' : 'gray', 'green', 'red'][this._mediaState] }}
-                    icon={!DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']).length ? 'microphone' : 'file-audio'}
-                    size="sm"
-                />
-            </div>
-        );
+        const audioAnnosCount = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null)?.length;
+        const audioTextAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
+        const audioView =
+            (!this.props.isSelected() && !this._isHovering) || this.props.renderDepth === -1 || SnappingManager.GetIsDragging() || (!audioAnnosCount && !this._mediaState) ? null : (
+                <Tooltip title={<div>{audioTextAnnos?.lastElement()}</div>}>
+                    <div className="documentView-audioBackground" onPointerDown={this.playAnnotation}>
+                        <FontAwesomeIcon className="documentView-audioFont" style={{ color: [audioAnnosCount ? 'blue' : 'gray', 'green', 'red'][this._mediaState] }} icon={!audioAnnosCount ? 'microphone' : 'file-audio'} size="sm" />
+                    </div>
+                </Tooltip>
+            );
 
         return (
             <div
@@ -1148,26 +1150,29 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
     }
 
     @action
-    onPointerEnter = () => {
+    playAnnotation = () => {
         const self = this;
-        const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations']);
-        if (audioAnnos && audioAnnos.length && this._mediaState === 0) {
-            const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)];
-            anno.data instanceof AudioField &&
-                new Howl({
-                    src: [anno.data.url.href],
-                    format: ['mp3'],
-                    autoplay: true,
-                    loop: false,
-                    volume: 0.5,
-                    onend: function () {
-                        runInAction(() => (self._mediaState = 0));
-                    },
-                });
+        const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
+        const anno = audioAnnos.lastElement();
+        if (anno instanceof AudioField && this._mediaState === 0) {
+            new Howl({
+                src: [anno.url.href],
+                format: ['mp3'],
+                autoplay: true,
+                loop: false,
+                volume: 0.5,
+                onend: function () {
+                    runInAction(() => {
+                        console.log('PLAYED');
+                        self._mediaState = 0;
+                    });
+                },
+            });
             this._mediaState = 1;
         }
     };
-    recordAudioAnnotation = () => {
+
+    recordAudioAnnotation = (onEnd?: () => void) => {
         let gumStream: any;
         let recorder: any;
         const self = this;
@@ -1176,18 +1181,30 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 audio: true,
             })
             .then(function (stream) {
+                let audioTextAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'], listSpec('string'), null);
+                if (audioTextAnnos) audioTextAnnos.push('');
+                else audioTextAnnos = self.dataDoc[self.LayoutFieldKey + '-audioAnnotations-text'] = new List<string>(['']);
+                DictationManager.Controls.listen({
+                    interimHandler: value => (audioTextAnnos[audioTextAnnos.length - 1] = value),
+                    continuous: { indefinite: false },
+                }).then(results => {
+                    if (results && [DictationManager.Controls.Infringed].includes(results)) {
+                        DictationManager.Controls.stop();
+                    }
+                    onEnd?.();
+                });
+
                 gumStream = stream;
                 recorder = new MediaRecorder(stream);
                 recorder.ondataavailable = async (e: any) => {
                     const [{ result }] = await Networking.UploadFilesToServer(e.data);
                     if (!(result instanceof Error)) {
-                        const audioDoc = Docs.Create.AudioDocument(result.accessPaths.agnostic.client, { title: 'audio test', _width: 200, _height: 32 });
-                        audioDoc.treeViewExpandedView = 'layout';
-                        const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'], listSpec(Doc));
+                        const audioField = new AudioField(result.accessPaths.agnostic.client);
+                        const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'], listSpec(AudioField), null);
                         if (audioAnnos === undefined) {
-                            self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'] = new List([audioDoc]);
+                            self.dataDoc[self.LayoutFieldKey + '-audioAnnotations'] = new List([audioField]);
                         } else {
-                            audioAnnos.push(audioDoc);
+                            audioAnnos.push(audioField);
                         }
                     }
                 };
@@ -1195,6 +1212,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                 recorder.start();
                 setTimeout(() => {
                     recorder.stop();
+                    DictationManager.Controls.stop(false);
                     runInAction(() => (self._mediaState = 0));
                     gumStream.getAudioTracks()[0].stop();
                 }, 5000);
diff --git a/src/client/views/nodes/button/FontIconBox.tsx b/src/client/views/nodes/button/FontIconBox.tsx
index 78ef85ec2..d3b95e25a 100644
--- a/src/client/views/nodes/button/FontIconBox.tsx
+++ b/src/client/views/nodes/button/FontIconBox.tsx
@@ -1,7 +1,7 @@
 import { IconProp } from '@fortawesome/fontawesome-svg-core';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@material-ui/core';
-import { action, computed, observable } from 'mobx';
+import { action, computed, observable, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ColorState, SketchPicker } from 'react-color';
@@ -710,6 +710,13 @@ ScriptingGlobals.add(function toggleNoAutoLinkAnchor(checkResult?: boolean) {
     }
     if (editorView) RichTextMenu.Instance?.toggleNoAutoLinkAnchor();
 });
+ScriptingGlobals.add(function toggleDictation(checkResult?: boolean) {
+    const textView = RichTextMenu.Instance?.TextView;
+    if (checkResult) {
+        return textView?._recording ? Colors.MEDIUM_BLUE : 'transparent';
+    }
+    if (textView) runInAction(() => (textView._recording = !textView._recording));
+});
 
 ScriptingGlobals.add(function toggleBold(checkResult?: boolean) {
     const editorView = RichTextMenu.Instance?.TextView?.EditorView;
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 849deb04e..929cca1ea 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -771,7 +771,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         const uicontrols: ContextMenuProps[] = [];
         !Doc.noviceMode && uicontrols.push({ description: `${FormattedTextBox._canAnnotate ? "Don't" : ''} Show Menu on Selections`, event: () => (FormattedTextBox._canAnnotate = !FormattedTextBox._canAnnotate), icon: 'expand-arrows-alt' });
         uicontrols.push({ description: !this.Document._noSidebar ? 'Hide Sidebar Handle' : 'Show Sidebar Handle', event: () => (this.layoutDoc._noSidebar = !this.layoutDoc._noSidebar), icon: 'expand-arrows-alt' });
-        uicontrols.push({ description: `${this.layoutDoc._showAudio ? 'Hide' : 'Show'} Dictation Icon`, event: () => (this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: 'expand-arrows-alt' });
         uicontrols.push({ description: 'Show Highlights...', noexpand: true, subitems: highlighting, icon: 'hand-point-right' });
         !Doc.noviceMode &&
             uicontrols.push({
@@ -839,7 +838,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         }
     };
     recordDictation = () => {
-        console.log('RECORD DICTATIN:');
         DictationManager.Controls.listen({
             interimHandler: this.setDictationContent,
             continuous: { indefinite: false },
@@ -852,11 +850,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     stopDictation = (abort: boolean) => DictationManager.Controls.stop(!abort);
 
     setDictationContent = (value: string) => {
-        console.log('DICTATION CONETNT: ' + value);
         if (this._editorView && this._recordingStart) {
-            console.log('STEP 1');
             if (this._break) {
-                console.log('BREAK');
                 const textanchorFunc = () => {
                     const tanch = Docs.Create.TextanchorDocument({ title: 'dictation anchor' });
                     return this.addDocument(tanch) ? tanch : undefined;
@@ -880,7 +875,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     }
                 }
             }
-            console.log('FINALIziNG');
             const from = this._editorView.state.selection.from;
             this._break = false;
             const tr = this._editorView.state.tr.insertText(value);
@@ -1732,7 +1726,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             .scale(1 / NumCast(this.layoutDoc._viewScale, 1) / (this.props.NativeDimScaling?.() || 1));
 
     @computed get audioHandle() {
-        return (
+        return !this._recording ? null : (
             <div
                 className="formattedTextBox-dictation"
                 onPointerDown={e =>
@@ -1744,7 +1738,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         action(e => (this._recording = !this._recording))
                     )
                 }>
-                <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: this._recording ? 'red' : 'blue', transitionDelay: '0.6s', opacity: this._recording ? 1 : 0.25 }} icon={'microphone'} size="sm" />
+                <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
             </div>
         );
     }
@@ -1898,7 +1892,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     </div>
                     {this.noSidebar || this.props.dontSelectOnLoad || !this.SidebarShown || this.sidebarWidthPercent === '0%' ? null : this.sidebarCollection}
                     {this.noSidebar || this.Document._noSidebar || this.props.dontSelectOnLoad || this.Document._singleLine ? null : this.sidebarHandle}
-                    {!this.layoutDoc._showAudio ? null : this.audioHandle}
+                    {this.audioHandle}
                 </div>
             </div>
         );
-- 
cgit v1.2.3-70-g09d2

