From c563aec906c5728a5563fefac6ab573a31375641 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 12 Mar 2024 09:09:59 -0400
Subject: made text templates be both layout templates and prototypes of new
 text documents.  fixed onPaint funcs to be undoable.  fixed comparisonBox to
 render a text box if it's fieldKey has a richtext field - this makes
 flashcard templates much easier.  fixed right-click on hyperlinks to bring up
 menu.   fixed layout_centered to be settable on templates.  added enable
 flashcard property for text.

---
 src/client/views/StyleProvider.tsx | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

(limited to 'src/client/views/StyleProvider.tsx')

diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index 0794efe4c..ab811858a 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -61,7 +61,7 @@ function togglePaintView(e: React.MouseEvent, doc: Opt<Doc>, props: Opt<FieldVie
         value: undefined,
     };
     e.stopPropagation();
-    UndoManager.RunInBatch(() => doc && ScriptCast(doc.onPaint).script.run(scriptProps), 'togglePaintView');
+    ScriptCast(doc?.onPaint)?.script.run(scriptProps);
 }
 
 export function wavyBorderPath(pw: number, ph: number, inset: number = 0.05) {
-- 
cgit v1.2.3-70-g09d2


From 409a72c8c2546851fc824877b27a71d101839e7e Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 14 Mar 2024 10:55:29 -0400
Subject: cleaned up some audio recording  and annotating code

---
 src/client/views/MarqueeAnnotator.tsx              |  2 +-
 src/client/views/StyleProvider.tsx                 | 11 ++-
 src/client/views/nodes/DocumentView.tsx            | 90 ++++++++++------------
 .../views/nodes/formattedText/FormattedTextBox.tsx | 21 ++---
 src/client/views/pdf/AnchorMenu.tsx                |  6 +-
 src/fields/Doc.ts                                  |  4 +-
 src/fields/DocSymbols.ts                           | 35 +++++----
 7 files changed, 81 insertions(+), 88 deletions(-)

(limited to 'src/client/views/StyleProvider.tsx')

diff --git a/src/client/views/MarqueeAnnotator.tsx b/src/client/views/MarqueeAnnotator.tsx
index f59042b04..3c35b441b 100644
--- a/src/client/views/MarqueeAnnotator.tsx
+++ b/src/client/views/MarqueeAnnotator.tsx
@@ -184,7 +184,7 @@ export class MarqueeAnnotator extends ObservableReactComponent<MarqueeAnnotatorP
         };
         AnchorMenu.Instance.OnClick = undoable((e: PointerEvent) => this.props.anchorMenuClick?.()?.(this.highlight(this.props.highlightDragSrcColor ?? 'rgba(173, 216, 230, 0.75)', true, undefined, true)), 'make sidebar annotation');
         AnchorMenu.Instance.OnAudio = unimplementedFunction;
-        AnchorMenu.Instance.Highlight = this.highlight;
+        AnchorMenu.Instance.Highlight = (color: string) => this.highlight(color, false, undefined, true);
         AnchorMenu.Instance.GetAnchor = (savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => this.highlight('rgba(173, 216, 230, 0.75)', true, savedAnnotations, true);
         AnchorMenu.Instance.onMakeAnchor = () => AnchorMenu.Instance.GetAnchor(undefined, true);
 
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx
index ab811858a..1adb0d9e5 100644
--- a/src/client/views/StyleProvider.tsx
+++ b/src/client/views/StyleProvider.tsx
@@ -50,6 +50,11 @@ export enum StyleProp {
     Highlighting = 'highlighting', // border highlighting
 }
 
+export enum AudioAnnoState {
+    stopped = 'stopped',
+    playing = 'playing',
+}
+
 function toggleLockedPosition(doc: Doc) {
     UndoManager.RunInBatch(() => Doc.toggleLockedPosition(doc), 'toggleBackground');
 }
@@ -330,14 +335,14 @@ export function DefaultStyleProvider(doc: Opt<Doc>, props: Opt<FieldViewProps &
                 );
             };
             const audio = () => {
-                const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, 'stopped');
+                const audioAnnoState = (doc: Doc) => StrCast(doc.audioAnnoState, AudioAnnoState.stopped);
                 const audioAnnosCount = (doc: Doc) => StrListCast(doc[fieldKey + 'audioAnnotations']).length;
                 if (!doc || props?.renderDepth === -1 || !audioAnnosCount(doc)) return null;
-                const audioIconColors: { [key: string]: string } = { recording: 'red', playing: 'green', stopped: 'blue' };
+                const audioIconColors: { [key: string]: string } = { playing: 'green', stopped: 'blue' };
                 return (
                     <Tooltip title={<div>{StrListCast(doc[fieldKey + 'audioAnnotations_text']).lastElement()}</div>}>
                         <div className="styleProvider-audio" onPointerDown={() => DocumentManager.Instance.getFirstDocumentView(doc)?.playAnnotation()}>
-                            <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon={'file-audio'} size="sm" />
+                            <FontAwesomeIcon className="documentView-audioFont" style={{ color: audioIconColors[audioAnnoState(doc)] }} icon='file-audio' size="sm" />
                         </div>
                     </Tooltip>
                 );
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 40592c2cd..bbeacef88 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -38,7 +38,7 @@ import { DocComponent, ViewBoxInterface } from '../DocComponent';
 import { EditableView } from '../EditableView';
 import { GestureOverlay } from '../GestureOverlay';
 import { LightboxView } from '../LightboxView';
-import { StyleProp } from '../StyleProvider';
+import { AudioAnnoState, StyleProp } from '../StyleProvider';
 import { DocumentContentsView, ObserverJsxParser } from './DocumentContentsView';
 import { DocumentLinksButton } from './DocumentLinksButton';
 import './DocumentView.scss';
@@ -1004,49 +1004,41 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
     public static recordAudioAnnotation(dataDoc: Doc, field: string, onRecording?: (stop: () => void) => void, onEnd?: () => void) {
         let gumStream: any;
         let recorder: any;
-        navigator.mediaDevices
-            .getUserMedia({
-                audio: true,
-            })
-            .then(function (stream) {
-                let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
-                if (audioTextAnnos) audioTextAnnos.push('');
-                else audioTextAnnos = dataDoc[field + '_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({ file: e.data });
-                    if (!(result instanceof Error)) {
-                        const audioField = new AudioField(result.accessPaths.agnostic.client);
-                        const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
-                        if (audioAnnos === undefined) {
-                            dataDoc[field + '_audioAnnotations'] = new List([audioField]);
-                        } else {
-                            audioAnnos.push(audioField);
-                        }
-                    }
-                };
-                //runInAction(() => (dataDoc.audioAnnoState = 'recording'));
-                recorder.start();
-                const stopFunc = () => {
-                    recorder.stop();
-                    DictationManager.Controls.stop(false);
-                    runInAction(() => (dataDoc.audioAnnoState = 'stopped'));
-                    gumStream.getAudioTracks()[0].stop();
-                };
-                if (onRecording) onRecording(stopFunc);
-                else setTimeout(stopFunc, 5000);
+        navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
+            let audioTextAnnos = Cast(dataDoc[field + '_audioAnnotations_text'], listSpec('string'), null);
+            if (audioTextAnnos) audioTextAnnos.push('');
+            else audioTextAnnos = dataDoc[field + '_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({ file: e.data });
+                if (!(result instanceof Error)) {
+                    const audioField = new AudioField(result.accessPaths.agnostic.client);
+                    const audioAnnos = Cast(dataDoc[field + '_audioAnnotations'], listSpec(AudioField), null);
+                    if (audioAnnos) audioAnnos.push(audioField);
+                    else dataDoc[field + '_audioAnnotations'] = new List([audioField]);
+                }
+            };
+            recorder.start();
+            const stopFunc = () => {
+                recorder.stop();
+                DictationManager.Controls.stop(false);
+                dataDoc.audioAnnoState = AudioAnnoState.stopped;
+                gumStream.getAudioTracks()[0].stop();
+            };
+            if (onRecording) onRecording(stopFunc);
+            else setTimeout(stopFunc, 5000);
+        });
     }
 }
 
@@ -1231,25 +1223,25 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
 
     public playAnnotation = () => {
         const self = this;
-        const audioAnnoState = this.dataDoc.audioAnnoState ?? 'stopped';
+        const audioAnnoState = this.dataDoc.audioAnnoState ?? AudioAnnoState.stopped;
         const audioAnnos = Cast(this.dataDoc[this.LayoutFieldKey + '_audioAnnotations'], listSpec(AudioField), null);
         const anno = audioAnnos?.lastElement();
         if (anno instanceof AudioField) {
             switch (audioAnnoState) {
-                case 'stopped':
+                case AudioAnnoState.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')),
+                        onend: action(() => (self.dataDoc.audioAnnoState = AudioAnnoState.stopped)),
                     });
-                    this.dataDoc.audioAnnoState = 'playing';
+                    this.dataDoc.audioAnnoState = AudioAnnoState.playing;
                     break;
-                case 'playing':
+                case AudioAnnoState.playing:
                     this.dataDoc[AudioPlay]?.stop();
-                    this.dataDoc.audioAnnoState = 'stopped';
+                    this.dataDoc.audioAnnoState = AudioAnnoState.stopped;
                     break;
             }
         }
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index e80c869d3..fb709818c 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -51,7 +51,7 @@ import { SidebarAnnos } from '../../SidebarAnnos';
 import { StyleProp } from '../../StyleProvider';
 import { media_state } from '../AudioBox';
 import { DocumentView, DocumentViewInternal, OpenWhere } from '../DocumentView';
-import { FocusViewOptions, FieldView, FieldViewProps } from '../FieldView';
+import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
 import { LinkInfo } from '../LinkDocPreview';
 import { PinProps, PresBox } from '../trails';
 import { DashDocCommentView } from './DashDocCommentView';
@@ -271,29 +271,24 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FieldViewProps
                     let stopFunc: any;
                     const targetData = target[DocData];
                     targetData.mediaState = media_state.Recording;
-                    targetData.audioAnnoState = 'recording';
                     DocumentViewInternal.recordAudioAnnotation(targetData, Doc.LayoutFieldKey(target), stop => (stopFunc = stop));
-                    let reactionDisposer = reaction(
+                    const reactionDisposer = reaction(
                         () => target.mediaState,
-                        action(dictation => {
+                        dictation => {
                             if (!dictation) {
-                                targetData.audioAnnoState = 'stopped';
                                 stopFunc();
                                 reactionDisposer();
                             }
-                        })
+                        }
                     );
                     target.title = ComputedField.MakeFunction(`self["text_audioAnnotations_text"].lastElement()`);
                 }
             });
         };
-        AnchorMenu.Instance.Highlight = undoable(
-            action((color: string, isLinkButton: boolean) => {
-                this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
-                return undefined;
-            }),
-            'highlght text'
-        );
+        AnchorMenu.Instance.Highlight = undoable((color: string) => {
+            this._editorView?.state && RichTextMenu.Instance.setHighlight(color);
+            return undefined;
+        }, 'highlght text');
         AnchorMenu.Instance.onMakeAnchor = () => this.getAnchor(true);
         AnchorMenu.Instance.StartCropDrag = unimplementedFunction;
         /**
diff --git a/src/client/views/pdf/AnchorMenu.tsx b/src/client/views/pdf/AnchorMenu.tsx
index d0688c338..59f191af0 100644
--- a/src/client/views/pdf/AnchorMenu.tsx
+++ b/src/client/views/pdf/AnchorMenu.tsx
@@ -46,7 +46,7 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     public OnAudio: (e: PointerEvent) => void = unimplementedFunction;
     public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
     public StartCropDrag: (e: PointerEvent, ele: HTMLElement) => void = unimplementedFunction;
-    public Highlight: (color: string, isTargetToggler: boolean, savedAnnotations?: ObservableMap<number, HTMLDivElement[]>, addAsAnnotation?: boolean) => Opt<Doc> = (color: string, isTargetToggler: boolean) => undefined;
+    public Highlight: (color: string) => Opt<Doc> = (color: string) => undefined;
     public GetAnchor: (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => Opt<Doc> = (savedAnnotations: Opt<ObservableMap<number, HTMLDivElement[]>>, addAsAnnotation: boolean) => undefined;
     public Delete: () => void = unimplementedFunction;
     public PinToPres: () => void = unimplementedFunction;
@@ -118,8 +118,8 @@ export class AnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     };
 
     @action
-    highlightClicked = (e: React.MouseEvent) => {
-        this.Highlight(this.highlightColor, false, undefined, true);
+    highlightClicked = () => {
+        this.Highlight(this.highlightColor);
         AnchorMenu.Instance.fadeOut(true);
     };
 
diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts
index ea5411740..921d7aa5d 100644
--- a/src/fields/Doc.ts
+++ b/src/fields/Doc.ts
@@ -15,7 +15,7 @@ import { incrementTitleCopy, Utils } from '../Utils';
 import { DateField } from './DateField';
 import {
     AclAdmin, AclAugment, AclEdit, AclPrivate, AclReadonly, Animation, AudioPlay, Brushed, CachedUpdates, DirectLinks,
-    DocAcl, DocCss, DocData, DocFields, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
+    DocAcl, DocCss, DocData, DocLayout, DocViews, FieldKeys, FieldTuples, ForceServerWrite, Height, Highlight,
     Initializing, Self, SelfProxy, TransitionTimer, UpdatingFromServer, Width
 } from './DocSymbols'; // prettier-ignore
 import { Copy, FieldChanged, HandleUpdate, Id, Parent, ToJavascriptString, ToScriptString, ToString } from './FieldSymbols';
@@ -227,7 +227,6 @@ export class Doc extends RefField {
                     DocAcl,
                     DocCss,
                     DocData,
-                    DocFields,
                     DocLayout,
                     DocViews,
                     FieldKeys,
@@ -308,7 +307,6 @@ export class Doc extends RefField {
             DocServer.UpdateField(this[Id], serverOp);
         }
     };
-    public [DocFields] = () => this[Self][FieldTuples]; // Object.keys(this).reduce((fields, key) => { fields[key] = this[key]; return fields; }, {} as any);
     public [Width] = () => NumCast(this[SelfProxy]._width);
     public [Height] = () => NumCast(this[SelfProxy]._height);
     public [TransitionTimer]: any = undefined;
diff --git a/src/fields/DocSymbols.ts b/src/fields/DocSymbols.ts
index 64d657e4f..837fcc90e 100644
--- a/src/fields/DocSymbols.ts
+++ b/src/fields/DocSymbols.ts
@@ -1,8 +1,26 @@
-export const DocUpdated = Symbol('DocUpdated');
+// Symbols for fundamental Doc operations such as: permissions, field and proxy access and server interactions
+export const AclPrivate = Symbol('DocAclOwnerOnly');
+export const AclReadonly = Symbol('DocAclReadOnly');
+export const AclAugment = Symbol('DocAclAugment');
+export const AclSelfEdit = Symbol('DocAclSelfEdit');
+export const AclEdit = Symbol('DocAclEdit');
+export const AclAdmin = Symbol('DocAclAdmin');
+export const DocAcl = Symbol('DocAcl');
+export const CachedUpdates = Symbol('DocCachedUpdates');
+export const UpdatingFromServer = Symbol('DocUpdatingFromServer');
+export const ForceServerWrite = Symbol('DocForceServerWrite');
 export const Self = Symbol('DocSelf');
 export const SelfProxy = Symbol('DocSelfProxy');
 export const FieldKeys = Symbol('DocFieldKeys');
 export const FieldTuples = Symbol('DocFieldTuples');
+export const Initializing = Symbol('DocInitializing');
+
+// Symbols for core Dash document model including data docs, layout docs, and links
+export const DocData = Symbol('DocData');
+export const DocLayout = Symbol('DocLayout');
+export const DirectLinks = Symbol('DocDirectLinks');
+
+// Symbols for view related operations for Documents
 export const AudioPlay = Symbol('DocAudioPlay');
 export const Width = Symbol('DocWidth');
 export const Height = Symbol('DocHeight');
@@ -10,22 +28,7 @@ export const Animation = Symbol('DocAnimation');
 export const Highlight = Symbol('DocHighlight');
 export const DocViews = Symbol('DocViews');
 export const Brushed = Symbol('DocBrushed');
-export const DocData = Symbol('DocData');
-export const DocLayout = Symbol('DocLayout');
-export const DocFields = Symbol('DocFields');
 export const DocCss = Symbol('DocCss');
-export const DocAcl = Symbol('DocAcl');
 export const TransitionTimer = Symbol('DocTransitionTimer');
-export const DirectLinks = Symbol('DocDirectLinks');
-export const AclPrivate = Symbol('DocAclOwnerOnly');
-export const AclReadonly = Symbol('DocAclReadOnly');
-export const AclAugment = Symbol('DocAclAugment');
-export const AclSelfEdit = Symbol('DocAclSelfEdit');
-export const AclEdit = Symbol('DocAclEdit');
-export const AclAdmin = Symbol('DocAclAdmin');
-export const UpdatingFromServer = Symbol('DocUpdatingFromServer');
-export const Initializing = Symbol('DocInitializing');
-export const ForceServerWrite = Symbol('DocForceServerWrite');
-export const CachedUpdates = Symbol('DocCachedUpdates');
 
 export const DashVersion = 'v0.8.0';
-- 
cgit v1.2.3-70-g09d2

