From 9d6c7f8100de3a952d20ad41ab20872737cb909e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Sun, 27 Aug 2023 20:25:55 -0400
Subject: lots of cleanup to streamline import orderings (ie packages should
 not mutually import each other directly or via a chain).  change
 raiseWhenDragged to be keepZWhenDragged and got rid of system wide default.

---
 src/client/views/pdf/AnchorMenu.tsx | 15 ++++++---------
 1 file changed, 6 insertions(+), 9 deletions(-)

(limited to 'src/client/views/pdf/AnchorMenu.tsx')

diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index b0924888a..6a8b06377 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -1,23 +1,20 @@
 import React = require('react');
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { Tooltip } from '@material-ui/core';
+import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
 import { observer } from 'mobx-react';
+import { EditorView } from 'prosemirror-view';
 import { ColorState } from 'react-color';
 import { Doc, Opt } from '../../../fields/Doc';
+import { StrCast } from '../../../fields/Types';
 import { returnFalse, setupMoveUpEvents, unimplementedFunction, Utils } from '../../../Utils';
+import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
+import { DocumentType } from '../../documents/DocumentTypes';
 import { SelectionManager } from '../../util/SelectionManager';
 import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
 import { LinkPopup } from '../linking/LinkPopup';
-import { ButtonDropdown } from '../nodes/formattedText/RichTextMenu';
-import { gptAPICall, GPTCallType } from '../../apis/gpt/GPT';
-import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
-import { LightboxView } from '../LightboxView';
-import { EditorView } from 'prosemirror-view';
 import './AnchorMenu.scss';
-import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
-import { StrCast } from '../../../fields/Types';
-import { DocumentType } from '../../documents/DocumentTypes';
+import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
-- 
cgit v1.2.3-70-g09d2


From b5605e12c760af341516f5bc0b0fe4afb14a14ca Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 5 Sep 2023 11:11:11 -0400
Subject: change getAnchor to honor addAsAnnotation so that pinning docs
 doesn't add to annotation list.  fixed dragging anchor menu icons so that
 they appear.

---
 src/client/util/DocumentManager.ts                 |   5 +-
 src/client/views/DocComponent.tsx                  |   3 +-
 .../collectionFreeForm/CollectionFreeFormView.tsx  |   1 +
 src/client/views/nodes/DocumentView.tsx            |   1 +
 src/client/views/nodes/ImageBox.tsx                |   7 +-
 src/client/views/nodes/WebBox.tsx                  |   6 +-
 src/client/views/pdf/AnchorMenu.tsx                | 122 +++++----------------
 7 files changed, 39 insertions(+), 106 deletions(-)

(limited to 'src/client/views/pdf/AnchorMenu.tsx')

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index bfe0e1b48..b9f6059f4 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -11,7 +11,6 @@ import { CollectionDockingView } from '../views/collections/CollectionDockingVie
 import { TabDocView } from '../views/collections/TabDocView';
 import { LightboxView } from '../views/LightboxView';
 import { DocFocusOptions, DocumentView, DocumentViewInternal, OpenWhere, OpenWhereMod } from '../views/nodes/DocumentView';
-import { FormattedTextBox } from '../views/nodes/formattedText/FormattedTextBox';
 import { KeyValueBox } from '../views/nodes/KeyValueBox';
 import { LinkAnchorBox } from '../views/nodes/LinkAnchorBox';
 import { PresBox } from '../views/nodes/trails';
@@ -320,7 +319,9 @@ export class DocumentManager {
     @action
     restoreDocView(viewSpec: Opt<Doc>, docView: DocumentView, options: DocFocusOptions, contextView: Opt<DocumentView>, targetDoc: Doc) {
         if (viewSpec && docView) {
-            if (docView.ComponentView instanceof FormattedTextBox) docView.ComponentView?.focus(viewSpec, options);
+            //if (docView.ComponentView instanceof FormattedTextBox)
+            //viewSpec !== docView.rootDoc &&
+            docView.ComponentView?.focus?.(viewSpec, options);
             PresBox.restoreTargetDocView(docView, viewSpec, options.zoomTime ?? 500);
             Doc.linkFollowHighlight(viewSpec ? [docView.rootDoc, viewSpec] : docView.rootDoc, undefined, options.effect);
             if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.rootDoc._layout_currentTimecode));
diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx
index 6a1efdf9c..483b92957 100644
--- a/src/client/views/DocComponent.tsx
+++ b/src/client/views/DocComponent.tsx
@@ -139,7 +139,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
             const indocs = doc instanceof Doc ? [doc] : doc;
             const docs = indocs.filter(doc => [AclEdit, AclAdmin].includes(effectiveAcl) || GetEffectiveAcl(doc) === AclAdmin);
 
-            docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
+            // docs.forEach(doc => doc.annotationOn === this.props.Document && Doc.SetInPlace(doc, 'annotationOn', undefined, true));
             const targetDataDoc = this.dataDoc;
             const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]);
             const toRemove = value.filter(v => docs.includes(v));
@@ -194,6 +194,7 @@ export function ViewBoxAnnotatableComponent<P extends ViewBoxAnnotatableProps>()
                     added.forEach(doc => {
                         doc._dragOnlyWithinContainer = undefined;
                         if (annotationKey ?? this._annotationKeySuffix()) Doc.GetProto(doc).annotationOn = this.rootDoc;
+                        else Doc.GetProto(doc).annotationOn = undefined;
                         Doc.SetContainer(doc, this.rootDoc);
                         inheritParentAcls(targetDataDoc, doc, true);
                     });
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 16d6f1270..7768ca547 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -311,6 +311,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
 
     focus = (anchor: Doc, options: DocFocusOptions) => {
         if (this._lightboxDoc) return;
+        if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return;
         const xfToCollection = options?.docTransform ?? Transform.Identity();
         const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined };
         const cantTransform = this.fitContentsToBox || ((this.rootDoc._isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index f7773ff18..ab99f3c6d 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -121,6 +121,7 @@ export interface DocComponentView {
     reverseNativeScaling?: () => boolean; // DocumentView's setup screenToLocal based on the doc having a nativeWidth/Height.  However, some content views (e.g., FreeFormView w/ fitContentsToBox set) may ignore the native dimensions so this flags the DocumentView to not do Nativre scaling.
     shrinkWrap?: () => void; // requests a document to display all of its contents with no white space.  currently only implemented (needed?) for freeform views
     select?: (ctrlKey: boolean, shiftKey: boolean) => void;
+    focus?: (textAnchor: Doc, options: DocFocusOptions) => Opt<number>;
     menuControls?: () => JSX.Element; // controls to display in the top menu bar when the document is selected.
     isAnyChildContentActive?: () => boolean; // is any child content of the document active
     onClickScriptDisable?: () => 'never' | 'always'; // disable click scripts : never, always, or undefined = only when selected
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f5c6a9273..a19cc3555 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -85,8 +85,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
     };
 
     getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => {
+        const visibleAnchor = this._getAnchor?.(this._savedAnnotations, false); // use marquee anchor, otherwise, save zoom/pan as anchor
         const anchor =
-            this._getAnchor?.(this._savedAnnotations, false) ?? // use marquee anchor, otherwise, save zoom/pan as anchor
+            visibleAnchor ??
             Docs.Create.ConfigDocument({
                 title: 'ImgAnchor:' + this.rootDoc.title,
                 config_panX: NumCast(this.layoutDoc._freeform_panX),
@@ -96,8 +97,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
             });
         if (anchor) {
             if (!addAsAnnotation) anchor.backgroundColor = 'transparent';
-            /* addAsAnnotation &&*/ this.addDocument(anchor);
-            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: true } }, this.rootDoc);
+            (addAsAnnotation || visibleAnchor) && this.addDocument(anchor);
+            PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: visibleAnchor ? false : true } }, this.rootDoc);
             return anchor;
         }
         return this.rootDoc;
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index f5db2ccfd..5f891e9a0 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -330,8 +330,9 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
                 ele.append(contents);
             }
         } catch (e) {}
+        const visibleAnchor = this._getAnchor(this._savedAnnotations, false);
         const anchor =
-            this._getAnchor(this._savedAnnotations, false) ??
+            visibleAnchor ??
             Docs.Create.ConfigDocument({
                 title: StrCast(this.rootDoc.title + ' ' + this.layoutDoc._layout_scrollTop),
                 y: NumCast(this.layoutDoc._layout_scrollTop),
@@ -340,8 +341,7 @@ export class WebBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProps
         PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), scrollable: pinProps?.pinData ? true : false, pannable: true } }, this.rootDoc);
         anchor.text = ele?.textContent ?? '';
         anchor.text_html = ele?.innerHTML;
-        //addAsAnnotation &&
-        this.addDocumentWrapper(anchor);
+        (addAsAnnotation || visibleAnchor) && this.addDocumentWrapper(anchor);
         return anchor;
     };
 
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index 6a8b06377..a52250145 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -3,7 +3,6 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { ColorPicker, Group, IconButton, Popup, Size, Toggle, ToggleType, Type } from 'browndash-components';
 import { action, computed, IReactionDisposer, observable, ObservableMap, reaction } from 'mobx';
 import { observer } from 'mobx-react';
-import { EditorView } from 'prosemirror-view';
 import { ColorState } from 'react-color';
 import { Doc, Opt } from '../../../fields/Doc';
 import { StrCast } from '../../../fields/Types';
@@ -15,68 +14,24 @@ import { AntimodeMenu, AntimodeMenuProps } from '../AntimodeMenu';
 import { LinkPopup } from '../linking/LinkPopup';
 import './AnchorMenu.scss';
 import { GPTPopup, GPTPopupMode } from './GPTPopup/GPTPopup';
+import { SettingsManager } from '../../util/SettingsManager';
 
 @observer
 export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     static Instance: AnchorMenu;
 
     private _disposer: IReactionDisposer | undefined;
-    private _disposer2: IReactionDisposer | undefined;
-    private _commentCont = React.createRef<HTMLButtonElement>();
-    private _palette = [
-        'rgba(208, 2, 27, 0.8)',
-        'rgba(238, 0, 0, 0.8)',
-        'rgba(245, 166, 35, 0.8)',
-        'rgba(248, 231, 28, 0.8)',
-        'rgba(245, 230, 95, 0.616)',
-        'rgba(139, 87, 42, 0.8)',
-        'rgba(126, 211, 33, 0.8)',
-        'rgba(65, 117, 5, 0.8)',
-        'rgba(144, 19, 254, 0.8)',
-        'rgba(238, 169, 184, 0.8)',
-        'rgba(224, 187, 228, 0.8)',
-        'rgba(225, 223, 211, 0.8)',
-        'rgba(255, 255, 255, 0.8)',
-        'rgba(155, 155, 155, 0.8)',
-        'rgba(0, 0, 0, 0.8)',
-    ];
+    private _commentRef = React.createRef<HTMLDivElement>();
+    private _cropRef = React.createRef<HTMLDivElement>();
 
     @observable private highlightColor: string = 'rgba(245, 230, 95, 0.616)';
 
     @observable public Status: 'marquee' | 'annotation' | '' = '';
 
     // GPT additions
-    @observable private GPTMode: GPTPopupMode = GPTPopupMode.SUMMARY;
     @observable private selectedText: string = '';
-    @observable private editorView?: EditorView;
-    @observable private textDoc?: Doc;
-    @observable private highlightRange: number[] | undefined;
-    private selectionRange: number[] | undefined;
-
-    @action
-    setGPTMode = (mode: GPTPopupMode) => {
-        this.GPTMode = mode;
-    };
-
     @action
-    setHighlightRange(r: number[] | undefined) {
-        this.highlightRange = r;
-    }
-
-    @action
-    public setSelectedText = (txt: string) => {
-        this.selectedText = txt;
-    };
-
-    @action
-    public setEditorView = (editor: EditorView) => {
-        this.editorView = editor;
-    };
-
-    @action
-    public setTextDoc = (textDoc: Doc) => {
-        this.textDoc = textDoc;
-    };
+    public setSelectedText = (txt: string) => (this.selectedText = txt);
 
     public onMakeAnchor: () => Opt<Doc> = () => undefined; // Method to get anchor from text search
 
@@ -105,20 +60,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
 
     componentWillUnmount() {
         this._disposer?.();
-        this._disposer2?.();
     }
 
     componentDidMount() {
-        this._disposer2 = reaction(
-            () => this._opacity,
-            opacity => {},
-            { fireImmediately: true }
-        );
         this._disposer = reaction(
             () => SelectionManager.Views().slice(),
-            selected => {
-                AnchorMenu.Instance.fadeOut(true);
-            }
+            selected => AnchorMenu.Instance.fadeOut(true)
         );
     }
 
@@ -129,17 +76,12 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     gptSummarize = async (e: React.PointerEvent) => {
         // move this logic to gptpopup, need to implement generate again
         GPTPopup.Instance.setVisible(true);
-        this.setHighlightRange(undefined);
         GPTPopup.Instance.setMode(GPTPopupMode.SUMMARY);
         GPTPopup.Instance.setLoading(true);
 
         try {
             const res = await gptAPICall(this.selectedText, GPTCallType.SUMMARY);
-            if (res) {
-                GPTPopup.Instance.setText(res);
-            } else {
-                GPTPopup.Instance.setText('Something went wrong.');
-            }
+            GPTPopup.Instance.setText(res || 'Something went wrong.');
         } catch (err) {
             console.error(err);
         }
@@ -151,7 +93,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             this,
             e,
             (e: PointerEvent) => {
-                this.StartDrag(e, this._commentCont.current!);
+                this.StartDrag(e, this._commentRef.current!);
                 return true;
             },
             returnFalse,
@@ -168,7 +110,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
             this,
             e,
             (e: PointerEvent) => {
-                this.StartCropDrag(e, this._commentCont.current!);
+                this.StartCropDrag(e, this._cropRef.current!);
                 return true;
             },
             returnFalse,
@@ -214,37 +156,21 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
      * all selected text available to summarize but its only supported for pdf and web ATM.
      * @returns Whether the GPT icon for summarization should appear
      */
-    canSummarize = (): boolean => {
-        const docs = SelectionManager.Docs();
-        if (docs.length > 0) {
-            return docs.some(doc => doc.type === DocumentType.PDF || doc.type === DocumentType.WEB);
-        }
-        return false;
-    };
-
-    /**
-     * Returns whether the selected text can be edited.
-     * @returns Whether the GPT icon for summarization should appear
-     */
-    canEdit = (): boolean => {
-        const docs = SelectionManager.Docs();
-        if (docs.length > 0) {
-            return docs.some(doc => doc.type === 'rtf');
-        }
-        return false;
-    };
+    canSummarize = () => SelectionManager.Docs().some(doc => [DocumentType.PDF, DocumentType.WEB].includes(doc.type as any));
 
     render() {
         const buttons =
             this.Status === 'marquee' ? (
                 <>
                     {this.highlighter}
-                    <IconButton
-                        tooltip="Drag to Place Annotation" //
-                        onPointerDown={this.pointerDown}
-                        icon={<FontAwesomeIcon icon="comment-alt" />}
-                        color={StrCast(Doc.UserDoc().userColor)}
-                    />
+                    <div ref={this._commentRef}>
+                        <IconButton
+                            tooltip="Drag to Place Annotation" //
+                            onPointerDown={this.pointerDown}
+                            icon={<FontAwesomeIcon icon="comment-alt" />}
+                            color={SettingsManager.userColor}
+                        />
+                    </div>
                     {/* GPT Summarize icon only shows up when text is highlighted, not on marquee selection*/}
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction && this.canSummarize() && (
                         <IconButton
@@ -270,12 +196,14 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         color={StrCast(Doc.UserDoc().userColor)}
                     />
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
-                        <IconButton
-                            tooltip="Click/Drag to create cropped image" //
-                            onPointerDown={this.cropDown}
-                            icon={<FontAwesomeIcon icon="image" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
-                        />
+                        <div ref={this._cropRef}>
+                            <IconButton
+                                tooltip="Click/Drag to create cropped image" //
+                                onPointerDown={this.cropDown}
+                                icon={<FontAwesomeIcon icon="image" />}
+                                color={StrCast(Doc.UserDoc().userColor)}
+                            />
+                        </div>
                     )}
                 </>
             ) : (
-- 
cgit v1.2.3-70-g09d2


From dc92e167391988b63e3ff15e67bcfad6df21c044 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 6 Sep 2023 13:53:32 -0400
Subject: fixed up audio annotation to add dictation to sidebar note.

---
 src/client/util/SearchUtil.ts                      |  4 +-
 src/client/views/StyleProvider.tsx                 |  2 +-
 src/client/views/global/globalScripts.ts           |  4 +-
 src/client/views/nodes/DocumentView.tsx            | 33 +++++++++++------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 43 +++++++++++++++-------
 src/client/views/pdf/AnchorMenu.tsx                | 18 ++++-----
 src/fields/Doc.ts                                  |  2 +
 src/fields/DocSymbols.ts                           |  1 +
 8 files changed, 67 insertions(+), 40 deletions(-)

(limited to 'src/client/views/pdf/AnchorMenu.tsx')

diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts
index 64aa7ba9b..560d6b30f 100644
--- a/src/client/util/SearchUtil.ts
+++ b/src/client/util/SearchUtil.ts
@@ -57,8 +57,8 @@ export namespace SearchUtil {
                     const hlights = new Set<string>();
                     SearchUtil.documentKeys(doc).forEach(
                         key =>
-                            Field.toString(doc[key] as Field)
-                                .toLowerCase()
+                            (Field.toString(doc[key] as Field) + Field.toScriptString(doc[key] as Field))
+                                .toLowerCase() //
                                 .includes(query) && hlights.add(key)
                     );
                     blockedKeys.forEach(key => hlights.delete(key));
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index a9e4ac17d..cdf104a18 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -295,7 +295,7 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<DocumentViewProps
             const audio = () => {
                 const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped');
                 const audioAnnosCount = (doc: Doc) => StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations']).length;
-                if (!doc || props?.renderDepth === -1 || (!audioAnnosCount(doc) && audioAnnoState(doc) === 'stopped')) return null;
+                if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
                 const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
                 return (
                     <Tooltip title={<div>{StrListCast(doc[Doc.LayoutFieldKey(doc) + '_audioAnnotations_text']).lastElement()}</div>}>
diff --git a/src/client/views/global/globalScripts.ts b/src/client/views/global/globalScripts.ts
index 38bf1042d..9b9cc6d24 100644
--- a/src/client/views/global/globalScripts.ts
+++ b/src/client/views/global/globalScripts.ts
@@ -182,8 +182,8 @@ ScriptingGlobals.add(function toggleCharStyle(charStyle: attrname, checkResult?:
                         toggle: () => editorView?.state && RichTextMenu.Instance.changeListType(list) }]);
     // prettier-ignore
     const attrs:attrfuncs[] = [
-        ['dictation', { checkResult: () => textView?._recording ? true:false,
-                        toggle: () => textView && runInAction(() => (textView._recording = !textView._recording)) }],
+        ['dictation', { checkResult: () => textView?._recordingDictation ? true:false,
+                        toggle: () => textView && runInAction(() => (textView._recordingDictation = !textView._recordingDictation)) }],
         ['noAutoLink',{ checkResult: () => (editorView ? RichTextMenu.Instance.noAutoLink : false),
                         toggle: () => editorView && RichTextMenu.Instance?.toggleNoAutoLinkAnchor()}],
         ['bold',      { checkResult: () => (editorView ? RichTextMenu.Instance.bold : (Doc.UserDoc().fontWeight === 'bold') ? true:false),
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ab99f3c6d..dcb2d9d51 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -4,7 +4,7 @@ import { observer } from 'mobx-react';
 import { computedFn } from 'mobx-utils';
 import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal';
 import { Doc, DocListCast, Field, Opt, StrListCast } from '../../../fields/Doc';
-import { AclPrivate, Animation, DocData, Width } from '../../../fields/DocSymbols';
+import { AclPrivate, Animation, AudioPlay, DocData, Width } from '../../../fields/DocSymbols';
 import { Id } from '../../../fields/FieldSymbols';
 import { InkTool } from '../../../fields/InkField';
 import { List } from '../../../fields/List';
@@ -52,6 +52,7 @@ import { LinkAnchorBox } from './LinkAnchorBox';
 import { PresEffect, PresEffectDirection } from './trails';
 import { PinProps, PresBox } from './trails/PresBox';
 import React = require('react');
+import { isThisTypeNode } from 'typescript';
 const { Howl } = require('howler');
 
 interface Window {
@@ -1054,7 +1055,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
                         }
                     }
                 };
-                runInAction(() => (dataDoc.audioAnnoState = 'recording'));
+                //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
                 recorder.start();
                 const stopFunc = () => {
                     recorder.stop();
@@ -1071,16 +1072,24 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
         const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
         const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
         const anno = audioAnnos?.lastElement();
-        if (anno instanceof AudioField && audioAnnoState === 'stopped') {
-            new Howl({
-                src: [anno.url.href],
-                format: ['mp3'],
-                autoplay: true,
-                loop: false,
-                volume: 0.5,
-                onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
-            });
-            this.dataDoc.audioAnnoState = 'playing';
+        if (anno instanceof AudioField) {
+            switch (audioAnnoState) {
+                case 'stopped':
+                    this.dataDoc[AudioPlay] = new Howl({
+                        src: [anno.url.href],
+                        format: ['mp3'],
+                        autoplay: true,
+                        loop: false,
+                        volume: 0.5,
+                        onend: action(() => (self.dataDoc.audioAnnoState = 'stopped')),
+                    });
+                    this.dataDoc.audioAnnoState = 'playing';
+                    break;
+                case 'playing':
+                    this.dataDoc[AudioPlay]?.stop();
+                    this.dataDoc.audioAnnoState = 'stopped';
+                    break;
+            }
         }
     };
 
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index c9481482f..58b824159 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -153,10 +153,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     @computed get layout_autoHeightMargins() {
         return this.titleHeight + NumCast(this.layoutDoc._layout_autoHeightMargins);
     }
-    @computed get _recording() {
+    @computed get _recordingDictation() {
         return this.dataDoc?.mediaState === 'recording';
     }
-    set _recording(value) {
+    set _recordingDictation(value) {
         !this.dataDoc[`${this.fieldKey}_recordingSource`] && (this.dataDoc.mediaState = value ? 'recording' : undefined);
     }
     @computed get config() {
@@ -265,11 +265,25 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
         AnchorMenu.Instance.OnAudio = (e: PointerEvent) => {
             !this.layoutDoc.layout_showSidebar && this.toggleSidebar();
             const anchor = this.makeLinkAnchor(undefined, OpenWhere.addRight, undefined, 'Anchored Selection', true, true);
+
             setTimeout(() => {
                 const target = this._sidebarRef.current?.anchorMenuClick(anchor);
                 if (target) {
                     anchor.followLinkAudio = true;
-                    DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target));
+                    let stopFunc: any;
+                    Doc.GetProto(target).mediaState = 'recording';
+                    Doc.GetProto(target).audioAnnoState = 'recording';
+                    DocumentViewInternal.recordAudioAnnotation(Doc.GetProto(target), Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
+                    let reactionDisposer = reaction(
+                        () => target.mediaState,
+                        action(dictation => {
+                            if (!dictation) {
+                                Doc.GetProto(target).audioAnnoState = 'stopped';
+                                stopFunc();
+                                reactionDisposer();
+                            }
+                        })
+                    );
                     target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`);
                 }
             });
@@ -948,14 +962,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
     };
 
     breakupDictation = () => {
-        if (this._editorView && this._recording) {
+        if (this._editorView && this._recordingDictation) {
             this.stopDictation(true);
             this._break = true;
             const state = this._editorView.state;
             const to = state.selection.to;
             const updated = TextSelection.create(state.doc, to, to);
             this._editorView.dispatch(state.tr.setSelection(updated).insertText('\n', to));
-            if (this._recording) {
+            if (this._recordingDictation) {
                 this.recordDictation();
             }
         }
@@ -1245,13 +1259,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
         if (!this.props.dontRegisterView) {
             this._disposers.record = reaction(
-                () => this._recording,
+                () => this._recordingDictation,
                 () => {
                     this.stopDictation(true);
-                    this._recording && this.recordDictation();
-                }
+                    this._recordingDictation && this.recordDictation();
+                },
+                { fireImmediately: true }
             );
-            if (this._recording) setTimeout(this.recordDictation);
+            if (this._recordingDictation) setTimeout(this.recordDictation);
         }
         var quickScroll: string | undefined = '';
         this._disposers.scroll = reaction(
@@ -1549,8 +1564,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
 
     @action
     componentWillUnmount() {
-        if (this._recording) {
-            this._recording = !this._recording;
+        if (this._recordingDictation) {
+            this._recordingDictation = !this._recordingDictation;
         }
         Object.values(this._disposers).forEach(disposer => disposer?.());
         this.endUndoTypingBatch();
@@ -1588,7 +1603,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                 }
             });
         }
-        if (this._recording && !e.ctrlKey && e.button === 0) {
+        if (this._recordingDictation && !e.ctrlKey && e.button === 0) {
             this.breakupDictation();
         }
         this._downX = e.clientX;
@@ -1890,7 +1905,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
             .scale(1 / NumCast(this.layoutDoc._freeform_scale, 1) / (this.props.NativeDimScaling?.() || 1));
 
     @computed get audioHandle() {
-        return !this._recording ? null : (
+        return !this._recordingDictation ? null : (
             <div
                 className="formattedTextBox-dictation"
                 onPointerDown={e =>
@@ -1899,7 +1914,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                         e,
                         returnFalse,
                         emptyFunction,
-                        action(e => (this._recording = !this._recording))
+                        action(e => (this._recordingDictation = !this._recordingDictation))
                     )
                 }>
                 <FontAwesomeIcon className="formattedTextBox-audioFont" style={{ color: 'red' }} icon={'microphone'} size="sm" />
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index a52250145..85c1cce8c 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -132,7 +132,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                     tooltip={'Click to Highlight'}
                     onClick={this.highlightClicked}
                     colorPicker={this.highlightColor}
-                    color={StrCast(Doc.UserDoc().userColor)}
+                    color={SettingsManager.userColor}
                 />
                 <ColorPicker selectedColor={this.highlightColor} setFinalColor={this.changeHighlightColor} setSelectedColor={this.changeHighlightColor} size={Size.XSMALL} />
             </Group>
@@ -177,7 +177,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             tooltip="Summarize with AI" //
                             onPointerDown={this.gptSummarize}
                             icon={<FontAwesomeIcon icon="comment-dots" size="lg" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
+                            color={SettingsManager.userColor}
                         />
                     )}
                     {AnchorMenu.Instance.OnAudio === unimplementedFunction ? null : (
@@ -185,7 +185,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             tooltip="Click to Record Annotation" //
                             onPointerDown={this.audioDown}
                             icon={<FontAwesomeIcon icon="microphone" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
+                            color={SettingsManager.userColor}
                         />
                     )}
                     <Popup
@@ -193,7 +193,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                         type={Type.PRIM}
                         icon={<FontAwesomeIcon icon={'search'} />}
                         popup={<LinkPopup key="popup" linkCreateAnchor={this.onMakeAnchor} />}
-                        color={StrCast(Doc.UserDoc().userColor)}
+                        color={SettingsManager.userColor}
                     />
                     {AnchorMenu.Instance.StartCropDrag === unimplementedFunction ? null : (
                         <div ref={this._cropRef}>
@@ -201,7 +201,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                                 tooltip="Click/Drag to create cropped image" //
                                 onPointerDown={this.cropDown}
                                 icon={<FontAwesomeIcon icon="image" />}
-                                color={StrCast(Doc.UserDoc().userColor)}
+                                color={SettingsManager.userColor}
                             />
                         </div>
                     )}
@@ -213,7 +213,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             tooltip="Remove Link Anchor" //
                             onPointerDown={this.Delete}
                             icon={<FontAwesomeIcon icon="trash-alt" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
+                            color={SettingsManager.userColor}
                         />
                     )}
                     {this.PinToPres !== returnFalse && (
@@ -221,7 +221,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             tooltip="Pin to Presentation" //
                             onPointerDown={this.PinToPres}
                             icon={<FontAwesomeIcon icon="map-pin" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
+                            color={SettingsManager.userColor}
                         />
                     )}
                     {this.ShowTargetTrail !== returnFalse && (
@@ -229,7 +229,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             tooltip="Show Linked Trail" //
                             onPointerDown={this.ShowTargetTrail}
                             icon={<FontAwesomeIcon icon="taxi" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
+                            color={SettingsManager.userColor}
                         />
                     )}
                     {this.IsTargetToggler !== returnFalse && (
@@ -240,7 +240,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
                             toggleStatus={this.IsTargetToggler()}
                             onClick={this.MakeTargetToggle}
                             icon={<FontAwesomeIcon icon="thumbtack" />}
-                            color={StrCast(Doc.UserDoc().userColor)}
+                            color={SettingsManager.userColor}
                         />
                     )}
                 </>
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index 2a1dfbfc7..dd3f11444 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -20,6 +20,7 @@ import {
     AclPrivate,
     AclReadonly,
     Animation,
+    AudioPlay,
     CachedUpdates,
     DirectLinks,
     DocAcl,
@@ -349,6 +350,7 @@ export class Doc extends RefField {
     @observable public [DocAcl]: { [key: string]: symbol } = {};
     @observable public [DocCss]: number = 0; // incrementer denoting a change to CSS layout
     @observable public [DirectLinks] = new ObservableSet<Doc>();
+    @observable public [AudioPlay]: any; // meant to store sound object from Howl
     @observable public [Animation]: Opt<Doc>;
     @observable public [Highlight]: boolean = false;
     static __Anim(Doc: Doc) {
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 5b743f710..bb974ee74 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -3,6 +3,7 @@ export const Self = Symbol('DocSelf');
 export const SelfProxy = Symbol('DocSelfProxy');
 export const FieldKeys = Symbol('DocFieldKeys');
 export const FieldTuples = Symbol('DocFieldTuples');
+export const AudioPlay = Symbol('DocAudioPlay');
 export const Width = Symbol('DocWidth');
 export const Height = Symbol('DocHeight');
 export const Animation = Symbol('DocAnimation');
-- 
cgit v1.2.3-70-g09d2

