From 18774b42e3c8e1e899978fe9f16a4d123adee803 Mon Sep 17 00:00:00 2001
From: Geireann Lindfield Roberts <geireann_lindfield_roberts@brown.edu>
Date: Wed, 1 Jan 2025 22:32:19 -0800
Subject: monorepo setup

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

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

diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 06869717a..f23b32a48 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,7 +1,7 @@
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import Slider from '@mui/material/Slider';
-import { Button, Dropdown, DropdownType, IconButton, Toggle, ToggleType, Type } from 'browndash-components';
+import { Button, Dropdown, DropdownType, IconButton, Toggle, ToggleType, Type } from '@dash/components';
 import { IReactionDisposer, ObservableSet, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
-- 
cgit v1.2.3-70-g09d2


From f66eaad00dbfe27e8dbe28019d1148a70f6ba0d9 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 3 Feb 2025 12:44:38 -0500
Subject: fixed defaults for roll animations effect.  fixed hideBefore pres
 effect to not show Doc before effect starts

---
 src/client/util/DocumentManager.ts         | 29 +++++++++-----------
 src/client/views/nodes/DocumentView.tsx    | 12 ++++-----
 src/client/views/nodes/FocusViewOptions.ts | 11 ++++++++
 src/client/views/nodes/trails/PresBox.tsx  | 43 ++++++++++++++----------------
 4 files changed, 50 insertions(+), 45 deletions(-)

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

diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 286112bf9..acb35f7eb 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -8,7 +8,7 @@ import { Cast, DocCast, NumCast, StrCast } from '../../fields/Types';
 import { AudioField } from '../../fields/URLField';
 import { CollectionViewType } from '../documents/DocumentTypes';
 import { DocumentView, DocumentViewInternal } from '../views/nodes/DocumentView';
-import { FocusViewOptions } from '../views/nodes/FocusViewOptions';
+import { FocusEffectDelay, FocusViewOptions } from '../views/nodes/FocusViewOptions';
 import { OpenWhere } from '../views/nodes/OpenWhere';
 import { PresBox } from '../views/nodes/trails';
 
@@ -352,21 +352,18 @@ export class DocumentManager {
             // if there's an options.effect, it will be handled from linkFollowHighlight.  We delay the start of
             // the highlight so that the target document can be somewhat centered so that the effect/highlight will be seen
             // bcz: should this delay be an options parameter?
-            setTimeout(
-                () => {
-                    Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
-                    if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) {
-                        // if the docView is a text anchor, the contextView is the PDF/Web/Text doc
-                        contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
-                        DocumentManager._overlayViews.add(contextView);
-                    }
-                    Doc.AddUnHighlightWatcher(() => {
-                        docView.Document[Animation] = undefined;
-                        DocumentManager.removeOverlayViews();
-                    });
-                },
-                (options.zoomTime ?? 0) * 0.5
-            );
+            setTimeout(() => {
+                Doc.linkFollowHighlight(viewSpec ? [docView.Document, viewSpec] : docView.Document, undefined, options.effect);
+                if (options.zoomTextSelections && Doc.IsUnhighlightTimerSet() && contextView && targetDoc.text_html) {
+                    // if the docView is a text anchor, the contextView is the PDF/Web/Text doc
+                    contextView.setTextHtmlOverlay(StrCast(targetDoc.text_html), options.effect);
+                    DocumentManager._overlayViews.add(contextView);
+                }
+                Doc.AddUnHighlightWatcher(() => {
+                    docView.Document[Animation] = undefined;
+                    DocumentManager.removeOverlayViews();
+                });
+            }, FocusEffectDelay(options));
             if (options.playMedia) docView.ComponentView?.playFrom?.(NumCast(docView.Document._layout_currentTimecode));
             if (options.playAudio) DocumentManager.playAudioAnno(docView.Document);
             if (options.toggleTarget && (!options.didMove || docView.Document.hidden)) docView.Document.hidden = !docView.Document.hidden;
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 5fd9223e3..a60415e43 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1043,13 +1043,13 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         >,
         root: Doc
     ) {
-        const dir = ((presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection) || PresEffectDirection.Center) as PresEffectDirection;
+        const effectDirection = (presEffectDoc?.presentation_effectDirection ?? presEffectDoc?.followLinkAnimDirection) as PresEffectDirection;
         const duration = Cast(presEffectDoc?.presentation_transition, 'number', Cast(presEffectDoc?.followLinkTransitionTime, 'number', null));
         const effectProps = {
-            left: dir === PresEffectDirection.Left,
-            right: dir === PresEffectDirection.Right,
-            top: dir === PresEffectDirection.Top,
-            bottom: dir === PresEffectDirection.Bottom,
+            left: effectDirection === PresEffectDirection.Left,
+            right: effectDirection === PresEffectDirection.Right,
+            top: effectDirection === PresEffectDirection.Top,
+            bottom: effectDirection === PresEffectDirection.Bottom,
             opposite: true,
             delay: 0,
             duration,
@@ -1063,7 +1063,7 @@ export class DocumentViewInternal extends DocComponent<FieldViewProps & Document
         const presEffect = StrCast(presEffectDoc?.presentation_effect, StrCast(presEffectDoc?.followLinkAnimEffect));
         switch (presEffect) {
             case PresEffect.Expand: case PresEffect.Flip: case PresEffect.Rotate: case PresEffect.Bounce:    
-            case PresEffect.Roll:       return <SpringAnimation doc={root} startOpacity={0} dir={dir} presEffect={presEffect}   springSettings={timingConfig}>{renderDoc}</SpringAnimation>
+            case PresEffect.Roll:       return <SpringAnimation doc={root} startOpacity={0} dir={effectDirection || PresEffectDirection.Left} presEffect={presEffect}   springSettings={timingConfig}>{renderDoc}</SpringAnimation>
             // case PresEffect.Fade:    return <SlideEffect doc={root} dir={dir} presEffect={PresEffect.Fade} tension={timingConfig.stiffness} friction={timingConfig.damping} mass={timingConfig.mass}>{renderDoc}</SlideEffect>
             case PresEffect.Fade:       return <Fade {...effectProps}>{renderDoc}</Fade>
             // keep as preset, doesn't really make sense with spring config
diff --git a/src/client/views/nodes/FocusViewOptions.ts b/src/client/views/nodes/FocusViewOptions.ts
index bb0d2b03c..1c462e98f 100644
--- a/src/client/views/nodes/FocusViewOptions.ts
+++ b/src/client/views/nodes/FocusViewOptions.ts
@@ -22,3 +22,14 @@ export interface FocusViewOptions {
     pointFocus?: { X: number; Y: number }; // clientX and clientY coordinates to focus on instead of a document target (used by explore mode)
     contextPath?: Doc[]; // path of inner documents that will also be focused
 }
+
+/**
+ * if there's an options.effect, it will be handled from linkFollowHighlight.  We delay the start of
+ * the highlight so that the target document can be somewhat centered so that the effect/highlight will be seen
+ *  bcz: should this delay be an options parameter?
+ * @param options
+ * @returns
+ */
+export function FocusEffectDelay(options: FocusViewOptions) {
+    return (options.zoomTime ?? 0) * 0.5;
+}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index f23b32a48..7ca0bbc8f 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,7 +1,8 @@
+import { Button, Dropdown, DropdownType, IconButton, Toggle, ToggleType, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import Slider from '@mui/material/Slider';
-import { Button, Dropdown, DropdownType, IconButton, Toggle, ToggleType, Type } from '@dash/components';
+import _ from 'lodash';
 import { IReactionDisposer, ObservableSet, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import * as React from 'react';
@@ -40,7 +41,7 @@ import { CollectionFreeFormPannableContents } from '../../collections/collection
 import { Colors } from '../../global/globalEnums';
 import { DocumentView } from '../DocumentView';
 import { FieldView, FieldViewProps } from '../FieldView';
-import { FocusViewOptions } from '../FocusViewOptions';
+import { FocusEffectDelay, FocusViewOptions } from '../FocusViewOptions';
 import { OpenWhere, OpenWhereMod } from '../OpenWhere';
 import { ScriptingBox } from '../ScriptingBox';
 import CubicBezierEditor, { EaseFuncToPoints, TIMING_DEFAULT_MAPPINGS } from './CubicBezierEditor';
@@ -48,7 +49,6 @@ import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
 import SlideEffect from './SlideEffect';
 import { AnimationSettings, SpringSettings, SpringType, easeItems, effectItems, effectTimings, movementItems, presEffectDefaultTimings, springMappings, springPreviewColors } from './SpringUtils';
-import _ from 'lodash';
 
 @observer
 export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
@@ -547,8 +547,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             if (!group) this.clearSelectedArray();
             this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); // Update selected array
             this.turnOffEdit();
-            this.navigateToActiveItem(finished); // Handles movement to element only when presentationTrail is list
-            this.doHideBeforeAfter(); // Handles hide after/before
+            this.navigateToActiveItem((options: FocusViewOptions) => {
+                setTimeout(this.doHideBeforeAfter, FocusEffectDelay(options)); // Handles hide after/before
+                finished?.();
+            }); // Handles movement to element only when presentationTrail is list
         }
     });
     static pinDataTypes(target?: Doc): dataTypes {
@@ -784,11 +786,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 });
             setTimeout(
                 () =>
-                    Array.from(transitioned).forEach(
-                        action(doc => {
-                            doc._dataTransition = undefined;
-                        })
-                    ),
+                    Array.from(transitioned).forEach(doc => {
+                        doc._dataTransition = undefined;
+                    }),
                 transTime + 10
             );
         }
@@ -826,16 +826,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
      * a new tab. If presCollection is undefined it will open the document
      * on the right.
      */
-    navigateToActiveItem = (afterNav?: () => void) => {
+    navigateToActiveItem = (afterNav?: (options: FocusViewOptions) => void) => {
         const { activeItem, targetDoc } = this;
-        const finished = () => {
-            afterNav?.();
+        const finished = (options: FocusViewOptions) => {
+            afterNav?.(options);
             targetDoc[Animation] = undefined;
         };
         const selViewCache = Array.from(this.selectedArray);
         const dragViewCache = Array.from(this._dragArray);
         const eleViewCache = Array.from(this._eleArray);
-        const resetSelection = action(() => {
+        const resetSelection = action((options: FocusViewOptions) => {
             if (!this._props.isSelected()) {
                 const presDocView = DocumentView.getDocumentView(this.Document);
                 if (presDocView) DocumentView.SelectView(presDocView, false);
@@ -844,14 +844,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 this._dragArray.splice(0, this._dragArray.length, ...dragViewCache);
                 this._eleArray.splice(0, this._eleArray.length, ...eleViewCache);
             }
-            finished();
+            finished(options);
         });
         PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection);
     };
 
     public static PanelName = 'PRESBOX';
 
-    static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) {
+    static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: (options: FocusViewOptions) => void) {
         if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
             (DocumentView.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
             return;
@@ -886,9 +886,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 if (!DocumentView.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
                     DocumentView.SetLightboxDoc(undefined);
                 }
-                DocumentView.showDocument(targetDoc, options, finished);
+                DocumentView.showDocument(targetDoc, options, () => finished?.(options));
             });
-        } else finished?.();
+        } else finished?.(options);
     }
 
     /**
@@ -900,8 +900,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.childDocs.forEach((doc, index) => {
             const curDoc = Cast(doc, Doc, null);
             const tagDoc = PresBox.targetRenderedDoc(curDoc);
-            const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, curDoc);
-            let opacity: Opt<number> = index === this.itemIndex ? 1 : undefined;
+            const itemIndexes = this.getAllIndexes(this.tagDocs, curDoc);
+            let opacity = index === this.itemIndex ? 1 : undefined;
             if (curDoc.presentation_hide) {
                 if (index !== this.itemIndex) {
                     opacity = 1;
@@ -913,9 +913,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     opacity = 0;
                 } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
                     opacity = 1;
-                    setTimeout(() => {
-                        tagDoc._dataTransition = undefined;
-                    }, 1000);
                 }
             }
             const hidingIndAft =
-- 
cgit v1.2.3-70-g09d2


From deb038e6b659a1c7563ccc9df2ef51221c0492ea Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 3 Feb 2025 14:42:42 -0500
Subject: fixed pres animation of colection that's progresivized by removing
 dataTransition assignments that made no sense.

---
 .../collectionFreeForm/CollectionFreeFormView.tsx  |  2 +-
 .../views/nodes/CollectionFreeFormDocumentView.tsx |  2 +-
 src/client/views/nodes/DocumentView.tsx            | 26 ++++++++------------
 src/client/views/nodes/trails/PresBox.tsx          | 28 ++++++++++------------
 4 files changed, 25 insertions(+), 33 deletions(-)

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

diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 69fdf52ff..3c31b584e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -232,7 +232,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection
     }
 
     public static gotoKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], duration: number) {
-        return DocumentView.SetViewTransition(docs, 'all', duration, timer, undefined, true);
+        return DocumentView.SetViewTransition(docs, 'all', duration, timer, true);
     }
     changeKeyFrame = (back = false) => {
         const currentFrame = Cast(this.Document._currentFrame, 'number', null);
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 68105f2f1..beea6ab3c 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -198,7 +198,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
     }
 
     public static updateKeyframe(timer: NodeJS.Timeout | undefined, docs: Doc[], time: number) {
-        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, undefined, true);
+        const newTimer = DocumentView.SetViewTransition(docs, 'all', 1000, timer, true);
         const timecode = Math.round(time);
         docs.forEach(doc => {
             this.animFields.forEach(val => {
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index a60415e43..0193fd328 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1390,8 +1390,8 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         this.Document[Animation] = presEffect;
         this._animEffectTimer = setTimeout(() => { this.Document[Animation] = undefined; }, timeInMs); // prettier-ignore
     };
-    public setViewTransition = (transProp: string, timeInMs: number, afterTrans?: () => void, dataTrans = false) => {
-        this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, afterTrans, dataTrans);
+    public setViewTransition = (transProp: string, timeInMs: number, dataTrans = false) => {
+        this._viewTimer = DocumentView.SetViewTransition([this.layoutDoc], transProp, timeInMs, this._viewTimer, dataTrans);
     };
 
     public setCustomView = undoable((custom: boolean, layout: string): void => {
@@ -1590,21 +1590,15 @@ export class DocumentView extends DocComponent<DocumentViewProps>() {
         );
     }
 
-    public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, timer?: NodeJS.Timeout | undefined, afterTrans?: () => void, dataTrans = false) {
-        docs.forEach(doc => {
-            doc._viewTransition = `${transProp} ${timeInMs}ms`;
-            dataTrans && (doc.dataTransition = `${transProp} ${timeInMs}ms`);
-        });
+    public static SetViewTransition(docs: Doc[], transProp: string, timeInMs: number, timer?: NodeJS.Timeout | undefined, dataTrans = false) {
+        const setTrans = (transition?: string) =>
+            docs.forEach(doc => {
+                doc._viewTransition = transition;
+                dataTrans && (doc.dataTransition = transition);
+            });
+        setTrans(`${transProp} ${timeInMs}ms`);
         timer && clearTimeout(timer);
-        return setTimeout(
-            () =>
-                docs.forEach(doc => {
-                    doc._viewTransition = undefined;
-                    dataTrans && (doc.dataTransition = 'inherit');
-                    afterTrans?.();
-                }),
-            timeInMs + 10
-        );
+        return setTimeout(setTrans, timeInMs + 10);
     }
 
     // shows a stacking view collection (by default, but the user can change) of all documents linked to the source
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 7ca0bbc8f..68634076d 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -452,27 +452,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const progressiveReveal = (first: boolean) => {
             const presIndexed = Cast(this.activeItem?.presentation_indexed, 'number', null);
             if (presIndexed !== undefined) {
-                const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem);
-                targetRenderedDoc._dataTransition = 'all 1s';
-                targetRenderedDoc.opacity = 1;
-                setTimeout(() => {
-                    targetRenderedDoc._dataTransition = 'inherit';
-                }, 1000);
                 const listItems = this.progressivizedItems(this.activeItem);
-                if (listItems && presIndexed < listItems.length) {
+                const listItemDoc = listItems?.[presIndexed];
+                if (listItems && listItemDoc) {
                     if (!first) {
-                        const listItemDoc = listItems[presIndexed];
-                        const targetView = listItems && DocumentView.getFirstDocumentView(listItemDoc);
+                        const presBulletTiming = 500; // bcz: hardwired for now
                         Doc.linkFollowUnhighlight();
                         Doc.HighlightDoc(listItemDoc);
                         listItemDoc.presentation_effect = this.activeItem.presBulletEffect;
-                        listItemDoc.presentation_transition = 500;
-                        targetView?.setAnimEffect(listItemDoc, 500);
-                        if (targetView && this.activeItem.presBulletExpand) {
-                            targetView.setAnimateScaling(1.2, 400);
-                            Doc.AddUnHighlightWatcher(() => targetView?.setAnimateScaling(0, undefined));
-                        }
+                        listItemDoc.presentation_transition = presBulletTiming;
                         listItemDoc.opacity = undefined;
+
+                        const targetView = DocumentView.getFirstDocumentView(listItemDoc);
+                        if (targetView) {
+                            targetView.setAnimEffect(listItemDoc, presBulletTiming);
+                            if (this.activeItem.presBulletExpand) {
+                                targetView.setAnimateScaling(1.2, presBulletTiming * 0.8);
+                                Doc.AddUnHighlightWatcher(() => targetView.setAnimateScaling(0, undefined));
+                            }
+                        }
                         this.activeItem.presentation_indexed = presIndexed + 1;
                     }
                     return true;
-- 
cgit v1.2.3-70-g09d2


From e8c1107aebd660872018c472ff5017d873e4b626 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 3 Feb 2025 15:37:29 -0500
Subject: fixed crash switching to tree view in pres

---
 src/client/views/nodes/MapBox/MapAnchorMenu.tsx | 12 +++++++-----
 src/client/views/nodes/trails/PresBox.tsx       |  2 +-
 2 files changed, 8 insertions(+), 6 deletions(-)

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

diff --git a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
index 87469b50e..cef202256 100644
--- a/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
+++ b/src/client/views/nodes/MapBox/MapAnchorMenu.tsx
@@ -109,7 +109,7 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
         return this._left > 0;
     }
 
-    constructor(props: any) {
+    constructor(props: AntimodeMenuProps) {
         super(props);
         makeObservable(this);
         MapAnchorMenu.Instance = this;
@@ -117,10 +117,12 @@ export class MapAnchorMenu extends AntimodeMenu<AntimodeMenuProps> {
     }
 
     componentWillUnmount() {
-        this.destinationFeatures = [];
-        this.destinationSelected = false;
-        this.selectedDestinationFeature = undefined;
-        this.currentRouteInfoMap = undefined;
+        runInAction(() => {
+            this.destinationFeatures = [];
+            this.destinationSelected = false;
+            this.selectedDestinationFeature = undefined;
+            this.currentRouteInfoMap = undefined;
+        });
         this._disposer?.();
     }
 
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 68634076d..e0de8e1fd 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1806,7 +1806,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get transitionDropdown() {
         const { activeItem } = this;
         // Retrieving spring timing properties
-        const timing = StrCast(activeItem.presentation_effectTiming);
+        const timing = StrCast(activeItem?.presentation_effectTiming);
         let timingConfig: SpringSettings | undefined;
         if (timing) {
             timingConfig = JSON.parse(timing);
-- 
cgit v1.2.3-70-g09d2


From 68bf2f9e4141d8a99407ff4c6c02d44795c638de Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Tue, 4 Feb 2025 14:08:38 -0500
Subject: fixing up gpt and animation panel in presbox.

---
 src/client/apis/gpt/PresCustomization.ts           | 115 ++++----
 .../views/nodes/imageEditor/ImageEditorButtons.tsx |   3 +-
 src/client/views/nodes/trails/PresBox.tsx          | 295 +++++++--------------
 src/client/views/nodes/trails/SpringUtils.ts       |   9 +-
 4 files changed, 170 insertions(+), 252 deletions(-)

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

diff --git a/src/client/apis/gpt/PresCustomization.ts b/src/client/apis/gpt/PresCustomization.ts
index 2262886a2..c465f098f 100644
--- a/src/client/apis/gpt/PresCustomization.ts
+++ b/src/client/apis/gpt/PresCustomization.ts
@@ -1,3 +1,5 @@
+import { PresEffect, PresEffectDirection } from '../../views/nodes/trails/PresEnums';
+import { AnimationSettingsProperties, easeItems } from '../../views/nodes/trails/SpringUtils';
 import { openai } from './setup';
 
 export enum CustomizationType {
@@ -10,15 +12,17 @@ interface PromptInfo {
 }
 const prompts: { [key: string]: PromptInfo } = {
     trails: {
-        description:
-            'We are customizing the properties and transition of a slide in a presentation. You are given the current properties of the slide in a json with the fields [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection], as well as the prompt for how the user wants to change it. Return a json with the required fields: [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection] by applying the changes in the prompt to the current state of the slide.',
+        description: `We are customizing the properties and transition of a slide in a presentation.
+             You are given the current properties of the slide in a json with the fields [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection], 
+                 as well as the prompt for how the user wants to change it. 
+             Return a json with the required fields: [title, presentation_transition, presentation_effect, config_zoom, presentation_effectDirection] by applying the changes in the prompt to the current state of the slide.`,
         features: [],
     },
 };
 
 // Allows you to register properties that are customizable
 export const addCustomizationProperty = (type: CustomizationType, name: string, description: string, values?: string[]) => {
-    values ? prompts[type].features.push({ name, description, values }) : prompts[type].features.push({ name, description });
+    prompts[type].features.push({ name, description, ...(values ? { values } : {}) });
 };
 
 // All the registered fields, make sure to update during registration, this
@@ -41,35 +45,34 @@ export const gptSlideProperties = [
 
 // Registers slide properties
 const setupPresSlideCustomization = () => {
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'title', 'is the title/name of the slide.');
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_transition', 'is a number in milliseconds for how long it should take to transition/move to a slide.');
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_easeFunc', 'is the easing function for the movement to the slide.', ['Ease', 'Ease In', 'Ease Out', 'Ease Out', 'Ease In Out', 'Linear']);
-
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effect', 'is an effect applied to the slide when we transition to it.', ['None', 'Expand', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']);
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_effectDirection', 'is what direction the effect is applied.', ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']);
-    addCustomizationProperty(
-        CustomizationType.PRES_TRAIL_SLIDE,
-        'presentation_effectTiming',
-        "is a json object of the format: {type: string, stiffness: number, damping: number, mass: number}. Type is always “custom”. Controls the spring-based timing of the presentation effect animation. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to match the user's description of how they want to animate the effect."
-    );
-
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'config_zoom', 'is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.');
-
-    // boolean values
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_playAudio', 'is a boolean value indicating if we should play audio when we go to the slide.');
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_zoomText', 'is a boolean value indicating if we should zoom into text selections when we go to the slide.');
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hideBefore', 'is a boolean value indicating if we should hide the slide before going to it.');
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hide', 'is a boolean value indicating if we should hide the slide during the presentation.');
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_hideAfter', 'is a boolean value indicating if we should hide the slide after going to it.');
-    addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, 'presentation_openInLightbox', 'is a boolean value indicating if we should open the slide in an overlay or lightbox view during the presentation.');
-};
+    const add = (name: string, val:string, opts?:string[]) => addCustomizationProperty(CustomizationType.PRES_TRAIL_SLIDE, name, val, opts);
+    const addBool = (name: string, val:string) => add(name, 'is a boolean value indicating if we should ' + val);
+    add('title',                       'is the title/name of the slide.');
+    add('config_zoom',                 'is a number from 0 to 1.0 indicating the percentage we should zoom into the slide.');
+    add('presentation_transition',     'is a number in milliseconds for how long it should take to transition/move to a slide.');
+    add('presentation_easeFunc',       'is the easing function for the movement to the slide.', easeItems.filter(val => val.text !== 'Custom').map(val => val.text))
+    add('presentation_effect',         'is an effect applied to the slide when we transition to it.', Object.keys(PresEffect));
+    add('presentation_effectDirection','is what direction the effect is applied.', Object.keys(PresEffectDirection).filter(key => key !== PresEffectDirection.None));
+    add('presentation_effectTiming',   `is a json object of the format: {type: string, ${AnimationSettingsProperties.stiffness}: number, ${AnimationSettingsProperties.damping}: number, ${AnimationSettingsProperties.mass}: number}. 
+                                        Type is always “custom”. Controls the spring-based timing of the presentation effect animation. 
+                                        Stiffness, damping, and mass control the physics-based properties of spring animations. 
+                                        This is used to create a more natural looking timing, bouncy effects, etc.
+                                        Use spring physics to adjust these parameters to match the user's description of how they want to animate the effect.`);
+
+
+    addBool('presentation_playAudio',      'play audio when we go to the slide.');
+    addBool('presentation_zoomText',       'zoom into text selections when we go to the slide.');
+    addBool('presentation_hideBefore',     'hide the slide before going to it.');
+    addBool('presentation_hide',           'hide the slide during the presentation.');
+    addBool('presentation_hideAfter',      'hide the slide after going to it.');
+    addBool('presentation_openInLightbox', 'open the slide in an overlay or lightbox view during the presentation.');
+}; // prettier-ignore
 
 setupPresSlideCustomization();
 
-export const getSlideTransitionSuggestions = async (inputText: string) => {
+export const getSlideTransitionSuggestions = (inputText: string) => {
     /**
-     * Prompt: Generate an entrance animations from slower and gentler
-     * to bouncier and more high energy
+     * Prompt: Generate entrance animations from slower and gentler to bouncier and more high energy
      *
      * Format:
      * {
@@ -81,13 +84,19 @@ export const getSlideTransitionSuggestions = async (inputText: string) => {
      * }
      */
 
-    const prompt =
-        "I want to generate four distinct types of slide effect animations. Return a json of the form {effect: string, direction: string, stiffness: number, damping: number, mass: number}[] with four elements.  Effect is the type of animation; its only possible values are ['Expand', 'Fade in', 'Bounce', 'Flip', 'Rotate', 'Roll']. Direction is the direction that the animation starts from; its only possible values are ['Enter from left', 'Enter from right', 'Enter from bottom', 'Enter from Top', 'Enter from center']. Stiffness, damping, and mass control the physics-based properties of spring animations. This is used to create a more natural-looking timing, bouncy effects, etc. Use spring physics to adjust these parameters to animate the effect.";
+    const prompt = `I want to generate four distinct types of slide effect animations. 
+         Return a json of the form { ${AnimationSettingsProperties.effect}: string, ${AnimationSettingsProperties.direction}: string, ${AnimationSettingsProperties.stiffness}: number, ${AnimationSettingsProperties.damping}: number, ${AnimationSettingsProperties.mass}: number}[] with four elements.  
+         ${AnimationSettingsProperties.effect} is the type of animation; its only possible values are [${Object.keys(PresEffect).filter(key => key !== PresEffect.None).join(',')}]. 
+         ${AnimationSettingsProperties.direction} is the direction that the animation starts from; 
+            its only possible values are [${Object.values(PresEffectDirection).filter(key => key !== PresEffectDirection.None).join(',')}]. 
+         ${AnimationSettingsProperties.stiffness}, ${AnimationSettingsProperties.damping}, and ${AnimationSettingsProperties.mass} control the physics-based properties of spring animations. 
+         This is used to create a more natural-looking timing, bouncy effects, etc. 
+         Use spring physics to adjust these parameters to animate the effect.`; // prettier-ignore
 
     const customInput = inputText ?? 'Make them as contrasting as possible with different effects and timings ranging from gentle to energetic.';
 
-    try {
-        const response = await openai.chat.completions.create({
+    return openai.chat.completions
+        .create({
             model: 'gpt-4',
             messages: [
                 { role: 'system', content: prompt },
@@ -95,39 +104,33 @@ export const getSlideTransitionSuggestions = async (inputText: string) => {
             ],
             temperature: 0,
             max_tokens: 1000,
+        })
+        .then(response => response.choices[0].message?.content ?? '')
+        .catch(err => {
+            console.log(err);
+            return 'Error connecting with API.';
         });
-        return response.choices[0].message?.content;
-    } catch (err) {
-        console.log(err);
-        return 'Error connecting with API.';
-    }
 };
 
-export const gptTrailSlideCustomization = async (inputText: string, properties: any | any[]) => {
-    let prompt = prompts.trails.description;
-
-    prompts.trails.features.forEach(feature => {
-        prompt += feature.name + ' ' + feature.description;
-        if (feature.values) {
-            prompt += `Its only possible values are [${feature.values.join(', ')}].`;
-        }
-    });
+export const gptTrailSlideCustomization = (inputText: string, properties: string) => {
+    const preamble = prompts.trails.description + prompts.trails.features.map(feature => feature.name + ' ' + feature.description + (feature.values ? `Its only possible values are [${feature.values.join(', ')}]` : '')).join('. ');
 
-    prompt += 'Set unchanged values to null and make sure you include new properties if they are specified in the prompt even if they do not exist in current properties. Please only return the json with the keys described and their values.';
+    const prompt = `Set unchanged values to null and make sure you include new properties if they are specified in the prompt even if they do not exist in current properties. 
+                    Please only return the json with the keys described and their values.`;
 
-    try {
-        const response = await openai.chat.completions.create({
+    return openai.chat.completions
+        .create({
             model: 'gpt-4',
             messages: [
-                { role: 'system', content: prompt },
-                { role: 'user', content: `Prompt: ${inputText}, Current properties: ${JSON.stringify(properties)}` },
+                { role: 'system', content: preamble + prompt },
+                { role: 'user', content: `Prompt: ${inputText}, Current properties: ${properties}` },
             ],
             temperature: 0,
             max_tokens: 1000,
+        })
+        .then(response => response.choices[0].message?.content ?? '')
+        .catch(err => {
+            console.log(err);
+            return 'Error connecting with API.';
         });
-        return response.choices[0].message?.content;
-    } catch (err) {
-        console.log(err);
-        return 'Error connecting with API.';
-    }
 };
diff --git a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
index e810881a5..985dc914f 100644
--- a/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
+++ b/src/client/views/nodes/imageEditor/ImageEditorButtons.tsx
@@ -53,9 +53,8 @@ export function ApplyFuncButtons({ loading, onClick: getEdit, onReset, btnText }
 
 export function ImageToolButton(tool: ImageEditTool, isActive: boolean, selectTool: (type: ImageToolType) => void) {
     return (
-        <div className="imageEditorButtonContainer">
+        <div key={tool.name} className="imageEditorButtonContainer">
             <Button
-                key={tool.name}
                 style={{ width: '100%' }}
                 text={tool.name}
                 type={Type.TERT}
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index e0de8e1fd..3919ac3a8 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,4 +1,4 @@
-import { Button, Dropdown, DropdownType, IconButton, Toggle, ToggleType, Type } from '@dash/components';
+import { Button, Dropdown, DropdownType, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import Slider from '@mui/material/Slider';
@@ -61,6 +61,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
     static navigateToDocScript: ScriptField;
 
+    public static PanelName = 'PRESBOX'; // name of dockingview tab where presentations get added
+
     constructor(props: FieldViewProps) {
         super(props);
         makeObservable(this);
@@ -72,6 +74,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     private _disposers: { [name: string]: IReactionDisposer } = {};
     public selectedArray = new ObservableSet<Doc>();
+    public slideToModify: Doc | null = null;
     _batch: UndoManager.Batch | undefined = undefined; // undo batch for dragging sliders which generate multiple scene edit events as the cursor moves
     _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed.  Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
     _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
@@ -97,14 +100,17 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _presKeyEvents: boolean = false;
     @observable _forceKeyEvents: boolean = false;
 
+    @observable _showBezierEditor = false;
+    @observable _showSpringEditor = true;
+    @observable _showPreview = true;
+    @observable _easeDropdownVal = 'ease';
+
     // GPT
-    private _inputref: HTMLTextAreaElement | null = null;
-    private _inputref2: HTMLTextAreaElement | null = null;
-    @observable chatActive: boolean = false;
-    @observable chatInput: string = '';
-    public slideToModify: Doc | null = null;
-    @observable isRecording: boolean = false;
-    @observable isLoading: boolean = false;
+    @observable _chatActive: boolean = false;
+    @observable _animationChat: string = '';
+    @observable _chatInput: string = '';
+    @observable _isRecording: boolean = false;
+    @observable _isLoading: boolean = false;
 
     @observable generatedAnimations: AnimationSettings[] = [
         // default presets
@@ -138,54 +144,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         },
     ];
 
-    @action
-    setGeneratedAnimations = (settings: AnimationSettings[]) => {
-        this.generatedAnimations = settings;
-    };
-
-    @observable animationChat: string = '';
-
-    @action
-    setChatInput = (input: string) => {
-        this.chatInput = input;
-    };
-
-    @action
-    setAnimationChat = (input: string) => {
-        this.animationChat = input;
-    };
-
-    @action
-    setIsLoading = (isLoading: boolean) => {
-        this.isLoading = isLoading;
-    };
-
-    @action
-    public setIsRecording = (isRecording: boolean) => {
-        this.isRecording = isRecording;
-    };
-
-    @observable showBezierEditor = false;
-    @action setBezierEditorVisibility = (visible: boolean) => {
-        this.showBezierEditor = visible;
-    };
-    @observable showSpringEditor = true;
-    @action setSpringEditorVisibility = (visible: boolean) => {
-        this.showSpringEditor = visible;
-    };
-
-    // Easing function variables
-
-    @observable easeDropdownVal = 'ease';
-
-    @action setBezierControlPoints = (newPoints: { p1: number[]; p2: number[] }) => {
+    setGeneratedAnimations =    action((input: AnimationSettings[]) => { this.generatedAnimations = input; }) // prettier-ignore
+    setChatInput =              action((input: string) => { this._chatInput = input; }); // prettier-ignore
+    setAnimationChat =          action((input: string) => { this._animationChat = input; }); // prettier-ignore
+    setIsLoading =              action((input?: boolean) => { this._isLoading = !!input; }); // prettier-ignore
+    setIsRecording =            action((input: boolean) => { this._isRecording = input; }); // prettier-ignore
+    setBezierEditorVisibility = action((visible: boolean) => { this._showBezierEditor = visible; }); // prettier-ignore
+    setSpringEditorVisibility = action((visible: boolean) => { this._showSpringEditor = visible; }); // prettier-ignore
+    setBezierControlPoints = action((newPoints: { p1: number[]; p2: number[] }) => {
         this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
-    };
+    });
 
     @computed
     get currCPoints() {
-        const strPoints = this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease';
-        return EaseFuncToPoints(strPoints);
+        return EaseFuncToPoints(this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease');
     }
 
     @computed
@@ -221,25 +193,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._type_collection === CollectionViewType.Stacking) return true;
         return false;
     }
-    @computed get panable() {
-        if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._type_collection === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true;
-        return false;
-    }
     @computed get selectedDocumentView() {
         if (DocumentView.Selected().length) return DocumentView.Selected()[0];
         if (this.selectedArray.size) return DocumentView.getDocumentView(this.Document);
         return undefined;
     }
-    @computed get isPres() {
-        return this.selectedDoc === this.Document;
-    }
-    @computed get selectedDoc() {
-        return this.selectedDocumentView?.Document;
-    }
-    isActiveItemTarget = (layoutDoc: Doc) => this.activeItem?.presentation_targetDoc === layoutDoc;
-    clearSelectedArray = () => this.selectedArray.clear();
-    addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc));
-    removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc));
 
     componentWillUnmount() {
         this._unmounting = true;
@@ -256,7 +214,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             () => this.pauseAutoPres()
         );
         this._disposers.keyboard = reaction(
-            () => this.selectedDoc,
+            () => this.selectedDocumentView?.Document,
             selected => {
                 document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
                 (this._presKeyEvents = selected === this.Document) && document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
@@ -293,13 +251,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         );
     }
 
+    clearSelectedArray = () => this.selectedArray.clear();
+    addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc));
+    removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc));
+
     @action
     updateCurrentPresentation = (pres?: Doc) => {
         Doc.ActivePresentation = pres ?? this.Document;
         PresBox.Instance = this;
     };
 
-    _mediaTimer!: [NodeJS.Timeout, Doc];
     // 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played
     startTempMedia = (targetDoc: Doc, activeItem: Doc) => {
         const duration: number = NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart);
@@ -318,7 +279,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     };
 
     // Recording for GPT customization
-
     recordDictation = () => {
         this.setIsRecording(true);
         this.setChatInput('');
@@ -336,64 +296,34 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         DictationManager.Controls.stop();
     };
 
-    setDictationContent = (value: string) => {
-        console.log('Dictation value', value);
-        this.setChatInput(value);
-    };
+    setDictationContent = (value: string) => this.setChatInput(value);
 
-    @action
-    customizeAnimations = async () => {
+    customizeAnimations = action(() => {
         this.setIsLoading(true);
-        try {
-            const res = await getSlideTransitionSuggestions(this.animationChat);
-            if (typeof res === 'string') {
-                const resObj = JSON.parse(res);
-                console.log('Parsed GPT Result ', resObj);
-                this.setGeneratedAnimations(resObj as AnimationSettings[]);
-            }
-        } catch (err) {
-            console.error(err);
-        }
-        this.setIsLoading(false);
-    };
+        getSlideTransitionSuggestions(this._animationChat)
+            .then(res => this.setGeneratedAnimations(JSON.parse(res) as AnimationSettings[]))
+            .catch(err => console.error(err))
+            .finally(this.setIsLoading);
+    });
 
-    @action
-    customizeWithGPT = async (input: string) => {
+    customizeWithGPT = action((input: string) => {
         // const testInput = 'change title to Customized Slide, transition for 2.3s with fade in effect';
         this.setIsRecording(false);
         this.setIsLoading(true);
-
-        const currSlideProperties: { [key: string]: FieldResult } = {};
-        gptSlideProperties.forEach(key => {
-            if (this.activeItem[key]) {
-                currSlideProperties[key] = this.activeItem[key];
-            }
-            // default values
-            else if (key === 'presentation_transition') {
-                currSlideProperties[key] = 500;
-            } else if (key === 'config_zoom') {
-                currSlideProperties[key] = 1.0;
-            }
-        });
-        console.log('current slide props ', currSlideProperties);
-
-        try {
-            const res = await gptTrailSlideCustomization(input, currSlideProperties);
-            if (typeof res === 'string') {
-                const resObj = JSON.parse(res);
-                console.log('Parsed GPT Result ', resObj);
-                for (const key in resObj) {
-                    if (resObj[key]) {
-                        console.log('typeof property', typeof resObj[key]);
-                        this.activeItem[key] = resObj[key];
-                    }
-                }
-            }
-        } catch (err) {
-            console.error(err);
-        }
-        this.setIsLoading(false);
-    };
+        const slideDefaults: { [key: string]: FieldResult } = { presentation_transition: 500, config_zoom: 1 };
+        const currSlideProperties = gptSlideProperties.reduce(
+            (prev, key) => { prev[key] = Field.toString(this.activeItem[key]) ?? prev[key]; return prev; }, 
+            slideDefaults); // prettier-ignore
+
+        gptTrailSlideCustomization(input, JSON.stringify(currSlideProperties))
+            .then(res =>
+                (Object.entries(JSON.parse(res)) as string[][]).forEach(([key, val]) => {
+                    this.activeItem[key] = (+val).toString() === val ? +val : (val ?? this.activeItem[key]);
+                })
+            )
+            .catch(e => console.error(e))
+            .finally(this.setIsLoading);
+    });
 
     // TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time
     // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions
@@ -847,8 +777,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection);
     };
 
-    public static PanelName = 'PRESBOX';
-
     static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: (options: FocusViewOptions) => void) {
         if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
             (DocumentView.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
@@ -1842,10 +1770,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 <ReactTextareaAutosize
                                     placeholder="Describe how you would like to modify the slide properties."
                                     className="pres-chatbox"
-                                    value={this.chatInput}
-                                    onChange={e => {
-                                        this.setChatInput(e.target.value);
-                                    }}
+                                    value={this._chatInput}
+                                    onChange={e => this.setChatInput(e.target.value)}
                                     onKeyDown={e => {
                                         this.stopDictation();
                                         e.stopPropagation();
@@ -1853,11 +1779,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 />
                                 <IconButton
                                     type={Type.TERT}
-                                    color={this.isRecording ? '#2bcaff' : SnappingManager.userVariantColor}
+                                    color={this._isRecording ? '#2bcaff' : SnappingManager.userVariantColor}
                                     tooltip="Record"
                                     icon={<BiMicrophone size="16px" />}
                                     onClick={() => {
-                                        if (!this.isRecording) {
+                                        if (!this._isRecording) {
                                             this.recordDictation();
                                         } else {
                                             this.stopDictation();
@@ -1869,12 +1795,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 style={{ alignSelf: 'flex-end' }}
                                 text="Send"
                                 type={Type.TERT}
-                                icon={this.isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
+                                icon={this._isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
                                 iconPlacement="right"
                                 color={SnappingManager.userVariantColor}
                                 onClick={() => {
                                     this.stopDictation();
-                                    this.customizeWithGPT(this.chatInput);
+                                    this.customizeWithGPT(this._chatInput);
                                 }}
                             />
                         </div>
@@ -1952,16 +1878,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 style={{ alignSelf: 'flex-start' }}
                                 onClick={e => {
                                     e.stopPropagation();
-                                    this.setBezierEditorVisibility(!this.showBezierEditor);
+                                    this.setBezierEditorVisibility(!this._showBezierEditor);
                                 }}>
-                                {`${this.showBezierEditor ? 'Hide' : 'Show'} Timing Editor`}
-                                <FontAwesomeIcon icon={this.showBezierEditor ? 'chevron-up' : 'chevron-down'} />
+                                {`${this._showBezierEditor ? 'Hide' : 'Show'} Timing Editor`}
+                                <FontAwesomeIcon icon={this._showBezierEditor ? 'chevron-up' : 'chevron-down'} />
                             </div>
                         </div>
                     </div>
 
                     {/* Cubic bezier editor */}
-                    {this.showBezierEditor && (
+                    {this._showBezierEditor && (
                         <div className="presBox-option-block" style={{ paddingTop: 0 }}>
                             <p className="presBox-submenu-label" style={{ alignSelf: 'flex-start' }}>
                                 Custom Timing Function
@@ -1978,7 +1904,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 <ReactTextareaAutosize
                                     placeholder="Customize prompt for effect suggestions. Leave blank for random results."
                                     className="pres-chatbox"
-                                    value={this.animationChat}
+                                    value={this._animationChat}
                                     onChange={e => {
                                         this.setAnimationChat(e.target.value);
                                     }}
@@ -1992,7 +1918,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 style={{ alignSelf: 'flex-end' }}
                                 text="Send"
                                 type={Type.TERT}
-                                icon={this.isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
+                                icon={this._isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
                                 iconPlacement="right"
                                 color={SnappingManager.userVariantColor}
                                 onClick={this.customizeAnimations}
@@ -2107,12 +2033,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         closeOnSelect
                                         items={effectTimings}
                                         selectedVal={timingConfig.type}
-                                        setSelectedVal={val => {
-                                            this.updateEffectTiming(activeItem, {
-                                                type: val as SpringType,
-                                                ...springMappings[val],
-                                            });
-                                        }}
+                                        setSelectedVal={val => this.updateEffectTiming(activeItem, { type: val as SpringType, ...springMappings[val] })}
                                         dropdownType={DropdownType.SELECT}
                                         type={Type.TERT}
                                     />
@@ -2120,73 +2041,61 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         className="presBox-show-hide-dropdown"
                                         onClick={e => {
                                             e.stopPropagation();
-                                            this.setSpringEditorVisibility(!this.showSpringEditor);
+                                            this.setSpringEditorVisibility(!this._showSpringEditor);
                                         }}>
-                                        {`${this.showSpringEditor ? 'Hide' : 'Show'} Spring Settings`}
-                                        <FontAwesomeIcon icon={this.showSpringEditor ? 'chevron-up' : 'chevron-down'} />
+                                        {`${this._showSpringEditor ? 'Hide' : 'Show'} Spring Settings`}
+                                        <FontAwesomeIcon icon={this._showSpringEditor ? 'chevron-up' : 'chevron-down'} />
                                     </div>
-                                    {this.showSpringEditor && (
+                                    {this._showSpringEditor && (
                                         <>
                                             <div>Tension</div>
-                                            <div
-                                                onPointerDown={e => {
-                                                    e.stopPropagation();
-                                                }}>
-                                                <Slider
-                                                    min={1}
-                                                    max={1000}
-                                                    step={5}
-                                                    size="small"
+                                            <div onPointerDown={e => e.stopPropagation()}>
+                                                {/* prettier-ignore */}
+                                                <Slider min={1} max={1000} step={5} size="small" 
                                                     value={timingConfig.stiffness}
-                                                    onChange={(e, val) => {
-                                                        if (!timingConfig) return;
-                                                        this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number });
-                                                    }}
+                                                    onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number })}
                                                     valueLabelDisplay="auto"
                                                 />
                                             </div>
                                             <div>Damping</div>
-                                            <div
-                                                onPointerDown={e => {
-                                                    e.stopPropagation();
-                                                }}>
-                                                <Slider
-                                                    min={1}
-                                                    max={100}
-                                                    step={1}
-                                                    size="small"
+                                            <div onPointerDown={e => e.stopPropagation()}>
+                                                {/* prettier-ignore */}
+                                                <Slider min={1} max={100} step={1} size="small"
                                                     value={timingConfig.damping}
-                                                    onChange={(e, val) => {
-                                                        if (!timingConfig) return;
-                                                        this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, damping: val as number });
-                                                    }}
+                                                    onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, damping: val as number })}
                                                     valueLabelDisplay="auto"
                                                 />
                                             </div>
                                             <div>Mass</div>
-                                            <div
-                                                onPointerDown={e => {
-                                                    e.stopPropagation();
-                                                }}>
-                                                <Slider
-                                                    min={1}
-                                                    max={10}
-                                                    step={1}
-                                                    size="small"
+                                            <div onPointerDown={e => e.stopPropagation()}>
+                                                {/* prettier-ignore */}
+                                                <Slider min={1} max={10} step={1} size="small"
                                                     value={timingConfig.mass}
-                                                    onChange={(e, val) => {
-                                                        if (!timingConfig) return;
-                                                        this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, mass: val as number });
-                                                    }}
+                                                    onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, mass: val as number })}
                                                     valueLabelDisplay="auto"
                                                 />
                                             </div>
-                                            Preview Effect
-                                            <div className="presBox-option-block presBox-option-center">
-                                                <div className="presBox-effect-container">
-                                                    <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig} infinite>
-                                                        <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} />
-                                                    </SlideEffect>
+                                            <div style={{ display: 'flex', alignItems: 'center'}}>
+                                                <Button
+                                                    type={Type.TERT}
+                                                    tooltip="show preview of slide animation effect"
+                                                    size={Size.SMALL}
+                                                    color={SnappingManager.userColor}
+                                                    background={'transparent'}
+                                                    onClick={action(() => {
+                                                        this._showPreview = false;
+                                                        setTimeout(action(() => { this._showPreview = true; }) );// prettier-ignore
+                                                    })}
+                                                    text={'Preview of: ' + effect + ', ' + direction}
+                                                />
+                                                <div className="presBox-option-block presBox-option-center">
+                                                    <div className="presBox-effect-container">
+                                                        {!this._showPreview ? null : (
+                                                            <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig}>
+                                                                <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} />
+                                                            </SlideEffect>
+                                                        )}
+                                                    </div>
                                                 </div>
                                             </div>
                                         </>
@@ -3078,7 +2987,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     } */}
                 </div>
                 {/* presbox chatbox */}
-                {this.chatActive && <div className="presBox-chatbox" />}
+                {this._chatActive && <div className="presBox-chatbox" />}
             </div>
         );
     }
diff --git a/src/client/views/nodes/trails/SpringUtils.ts b/src/client/views/nodes/trails/SpringUtils.ts
index 73e1e14f1..044afbeb1 100644
--- a/src/client/views/nodes/trails/SpringUtils.ts
+++ b/src/client/views/nodes/trails/SpringUtils.ts
@@ -22,7 +22,14 @@ export interface SpringSettings {
 }
 
 // Overall config
-
+// Keeps these settings in sync with the AnimationSettings interface (used by gpt);
+export enum AnimationSettingsProperties {
+    effect = 'effect',
+    direction = 'direction',
+    stiffness = 'stiffness',
+    damping = 'damping',
+    mass = 'mass',
+}
 export interface AnimationSettings {
     effect: PresEffect;
     direction: PresEffectDirection;
-- 
cgit v1.2.3-70-g09d2


From 382e09f606d94921dab460cc8c8564199bb0b70d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 5 Feb 2025 17:10:20 -0500
Subject: cleanup of presbox properties.

---
 .../views/nodes/trails/CubicBezierEditor.tsx       | 154 ++++---
 src/client/views/nodes/trails/PresBox.scss         |  83 +++-
 src/client/views/nodes/trails/PresBox.tsx          | 475 +++++++++++----------
 3 files changed, 402 insertions(+), 310 deletions(-)

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

diff --git a/src/client/views/nodes/trails/CubicBezierEditor.tsx b/src/client/views/nodes/trails/CubicBezierEditor.tsx
index e1ad1e6e5..627b77184 100644
--- a/src/client/views/nodes/trails/CubicBezierEditor.tsx
+++ b/src/client/views/nodes/trails/CubicBezierEditor.tsx
@@ -118,84 +118,82 @@ function CubicBezierEditor({ setFunc, currPoints }: Props) {
     }, [c2Down, currPoints]);
 
     return (
-        <div
-            onPointerMove={e => {
-                e.stopPropagation;
-            }}>
-            <svg className="presBox-bezier-editor" width={`${CONTAINER_WIDTH}`} height={`${CONTAINER_WIDTH}`} xmlns="http://www.w3.org/2000/svg">
-                {/* Outlines */}
-                <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${EDITOR_WIDTH + OFFSET}`} y2={`${0 + OFFSET}`} stroke="#c1c1c1" strokeWidth="1" />
-                {/* Box Outline */}
-                <rect x={`${0 + OFFSET}`} y={`${0 + OFFSET}`} width={EDITOR_WIDTH} height={EDITOR_WIDTH} stroke="#c5c5c5" fill="transparent" strokeWidth="1" />
-                {/* Editor */}
-                <path
-                    d={`M ${0 + OFFSET} ${EDITOR_WIDTH + OFFSET} C ${currPoints.p1[0] * EDITOR_WIDTH + OFFSET} ${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}, ${
-                        currPoints.p2[0] * EDITOR_WIDTH + OFFSET
-                    } ${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}, ${EDITOR_WIDTH + OFFSET} ${0 + OFFSET}`}
-                    stroke="#ffffff"
-                    fill="transparent"
-                />
-                {/* Bottom left */}
-                <line
-                    onPointerDown={() => {
-                        setC1Down(true);
-                    }}
-                    onPointerUp={() => {
-                        setC1Down(false);
-                    }}
-                    x1={`${0 + OFFSET}`}
-                    y1={`${EDITOR_WIDTH + OFFSET}`}
-                    x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
-                    y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
-                    stroke="#00000000"
-                    strokeWidth="5"
-                />
-                <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
-                <circle
-                    cx={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
-                    cy={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
-                    r="5"
-                    fill={`${c1Down ? '#3fa9ff' : '#ffffff'}`}
-                    onPointerDown={e => {
-                        e.stopPropagation();
-                        setC1Down(true);
-                    }}
-                    onPointerUp={() => {
-                        setC1Down(false);
-                    }}
-                />
-                {/* Top right */}
-                <line
-                    onPointerDown={e => {
-                        e.stopPropagation();
-                        setC2Down(true);
-                    }}
-                    onPointerUp={() => {
-                        setC2Down(false);
-                    }}
-                    x1={`${EDITOR_WIDTH + OFFSET}`}
-                    y1={`${0 + OFFSET}`}
-                    x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
-                    y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
-                    stroke="#00000000"
-                    strokeWidth="5"
-                />
-                <line x1={`${EDITOR_WIDTH + OFFSET}`} y1={`${0 + OFFSET}`} x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
-                <circle
-                    cx={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
-                    cy={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
-                    r="5"
-                    fill={`${c2Down ? '#3fa9ff' : '#ffffff'}`}
-                    onPointerDown={e => {
-                        e.stopPropagation();
-                        setC2Down(true);
-                    }}
-                    onPointerUp={() => {
-                        setC2Down(false);
-                    }}
-                />
-            </svg>
-        </div>
+        <svg className="presBox-bezier-editor" width={`${CONTAINER_WIDTH}`} height={`${CONTAINER_WIDTH}`} xmlns="http://www.w3.org/2000/svg">
+            {/* Outlines */}
+            <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${EDITOR_WIDTH + OFFSET}`} y2={`${0 + OFFSET}`} stroke="#c1c1c1" strokeWidth="1" />
+            {/* Box Outline */}
+            <rect x={`${0 + OFFSET}`} y={`${0 + OFFSET}`} width={EDITOR_WIDTH} height={EDITOR_WIDTH} stroke="#c5c5c5" fill="transparent" strokeWidth="1" />
+            {/* Editor */}
+            <path
+                d={`M ${0 + OFFSET} ${EDITOR_WIDTH + OFFSET} C ${currPoints.p1[0] * EDITOR_WIDTH + OFFSET} ${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}, ${
+                    currPoints.p2[0] * EDITOR_WIDTH + OFFSET
+                } ${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}, ${EDITOR_WIDTH + OFFSET} ${0 + OFFSET}`}
+                stroke="#ffffff"
+                fill="transparent"
+            />
+            {/* Bottom left */}
+            <line
+                onPointerDown={() => {
+                    setC1Down(true);
+                }}
+                onPointerMove={e => {
+                    e.stopPropagation;
+                }}
+                onPointerUp={() => {
+                    setC1Down(false);
+                }}
+                x1={`${0 + OFFSET}`}
+                y1={`${EDITOR_WIDTH + OFFSET}`}
+                x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
+                y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
+                stroke="#00000000"
+                strokeWidth="5"
+            />
+            <line x1={`${0 + OFFSET}`} y1={`${EDITOR_WIDTH + OFFSET}`} x2={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
+            <circle
+                cx={`${currPoints.p1[0] * EDITOR_WIDTH + OFFSET}`}
+                cy={`${EDITOR_WIDTH - currPoints.p1[1] * EDITOR_WIDTH + OFFSET}`}
+                r="5"
+                fill={`${c1Down ? '#3fa9ff' : '#ffffff'}`}
+                onPointerDown={e => {
+                    e.stopPropagation();
+                    setC1Down(true);
+                }}
+                onPointerUp={() => {
+                    setC1Down(false);
+                }}
+            />
+            {/* Top right */}
+            <line
+                onPointerDown={e => {
+                    e.stopPropagation();
+                    setC2Down(true);
+                }}
+                onPointerUp={() => {
+                    setC2Down(false);
+                }}
+                x1={`${EDITOR_WIDTH + OFFSET}`}
+                y1={`${0 + OFFSET}`}
+                x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
+                y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
+                stroke="#00000000"
+                strokeWidth="5"
+            />
+            <line x1={`${EDITOR_WIDTH + OFFSET}`} y1={`${0 + OFFSET}`} x2={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`} y2={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`} stroke="#ffffff" strokeWidth="1" />
+            <circle
+                cx={`${currPoints.p2[0] * EDITOR_WIDTH + OFFSET}`}
+                cy={`${EDITOR_WIDTH - currPoints.p2[1] * EDITOR_WIDTH + OFFSET}`}
+                r="5"
+                fill={`${c2Down ? '#3fa9ff' : '#ffffff'}`}
+                onPointerDown={e => {
+                    e.stopPropagation();
+                    setC2Down(true);
+                }}
+                onPointerUp={() => {
+                    setC2Down(false);
+                }}
+            />
+        </svg>
     );
 }
 
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index 60d4e580d..a181687f8 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -5,11 +5,24 @@
     display: flex;
     flex-direction: column;
     gap: 1rem;
+    .presBox-gpt-chat-span {
+        display: flex;
+        align-items: center;
+        gap: 8px;
+    }
+    textarea {
+        width: 100%;
+    }
+}
+.presBox-subheading-slider {
+    max-width: calc(100% - 25px);
+    width: 100%;
+    padding: 15px;
 }
 
 .pres-chat {
     display: flex;
-    flex-direction: column;
+    flex-direction: row;
     gap: 8px;
 }
 
@@ -18,30 +31,38 @@
     gap: 8px;
 }
 
-.pres-chatbox-container {
-    padding: 16px;
+.pres-chatbox-container,
+.pres-chatbox-container-ai {
+    width: 100%;
+    padding-left: 16px;
+    padding-right: 16px;
     outline: 1px solid #999999;
-    border-radius: 16px;
+    border-radius: 5px;
     display: flex;
     align-items: center;
     justify-content: space-between;
+    overflow: auto;
+    max-height: 200px;
+    .pres-chatbox {
+        outline: none;
+        border: none;
+        resize: none;
+        font-family: Verdana, Geneva, sans-serif;
+        background-color: transparent;
+        overflow-y: hidden;
+    }
 }
 
-.pres-chatbox {
-    outline: none;
-    border: none;
-    resize: none;
-    font-family: Verdana, Geneva, sans-serif;
-    background-color: transparent;
-    overflow-y: hidden;
+.pres-chatbox-container-ai {
+    padding-left: 8px;
+    padding-right: 8px;
+    margin-left: 8px;
 }
-
 // Effect Animations
 
 .presBox-effects {
-    display: grid;
-    grid-template-columns: auto auto;
-    gap: 8px;
+    display: flow;
+    margin: auto;
 }
 
 .presBox-effect-row {
@@ -55,7 +76,7 @@
     overflow: hidden;
     width: 80px;
     height: 80px;
-    display: flex;
+    display: inline-flex;
     justify-content: center;
     align-items: center;
     border: 1px solid rgb(118, 118, 118);
@@ -74,7 +95,6 @@
 
 .presBox-show-hide-dropdown {
     cursor: pointer;
-    padding: 8px 0;
     display: flex;
     align-items: center;
     gap: 4px;
@@ -96,6 +116,18 @@
     align-items: center;
 }
 
+.presBox-previewContainer {
+    display: flex;
+    align-items: center;
+    width: fit-content;
+    margin: auto;
+    grid-template-columns: auto auto;
+    grid-gap: 10px;
+    .presBox-option-block {
+        padding: 0px;
+    }
+}
+
 .presBox-cont {
     cursor: auto;
     position: absolute;
@@ -280,6 +312,9 @@
     font-family: Roboto;
     z-index: 100;
     transition: 0.7s;
+    .form-wrapper.left .formLabel {
+        width: 100px;
+    }
 
     .ribbon-doubleButton {
         display: flex;
@@ -352,6 +387,18 @@
         font-size: 11;
         font-weight: 400;
         margin-top: 10px;
+        max-width: min(100px, 25%);
+        width: 100%;
+    }
+    .presBox-springSlider {
+        display: grid;
+        column-count: 2;
+        grid-template-columns: min(60px, 25%) calc(100% - min(60px, 25%) - min(5px, 10%));
+        grid-gap: min(5px, 10%);
+        > span {
+            overflow: hidden;
+            text-overflow: ellipsis;
+        }
     }
 
     @media screen and (-webkit-min-device-pixel-ratio: 0) {
@@ -459,7 +506,7 @@
         justify-content: space-between;
         width: 100%;
         height: max-content;
-        grid-template-columns: auto auto auto;
+        grid-template-columns: auto auto;
         grid-template-rows: max-content;
         font-weight: 100;
         margin-top: 3px;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3919ac3a8..5dbf502d2 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -100,8 +100,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _presKeyEvents: boolean = false;
     @observable _forceKeyEvents: boolean = false;
 
+    @observable _showAIGallery = false;
     @observable _showBezierEditor = false;
-    @observable _showSpringEditor = true;
+    @observable _showSpringEditor = false;
     @observable _showPreview = true;
     @observable _easeDropdownVal = 'ease';
 
@@ -151,6 +152,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     setIsRecording =            action((input: boolean) => { this._isRecording = input; }); // prettier-ignore
     setBezierEditorVisibility = action((visible: boolean) => { this._showBezierEditor = visible; }); // prettier-ignore
     setSpringEditorVisibility = action((visible: boolean) => { this._showSpringEditor = visible; }); // prettier-ignore
+    setShowAIGalleryVisibilty = action((visible: boolean) => { this._showAIGallery = visible; }); // prettier-ignore
     setBezierControlPoints = action((newPoints: { p1: number[]; p2: number[] }) => {
         this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
     });
@@ -1735,19 +1737,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         const { activeItem } = this;
         // Retrieving spring timing properties
         const timing = StrCast(activeItem?.presentation_effectTiming);
-        let timingConfig: SpringSettings | undefined;
-        if (timing) {
-            timingConfig = JSON.parse(timing);
-        }
-
-        if (!timingConfig) {
-            timingConfig = {
-                type: SpringType.GENTLE,
-                stiffness: 100,
-                damping: 15,
-                mass: 1,
-            };
-        }
+        const timingConfig: SpringSettings = timing
+            ? JSON.parse(timing)
+            : {
+                  type: SpringType.GENTLE,
+                  stiffness: 100,
+                  damping: 15,
+                  mass: 1,
+              };
 
         if (activeItem && this.targetDoc) {
             const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5;
@@ -1758,20 +1755,32 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             return (
                 <>
                     {/* This chatbox is for customizing the properties of trails, like transition time, movement type (zoom, pan) using GPT */}
-                    <div className="presBox-gpt-chat">
-                        <span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}>
+                    <div className="presBox-gpt-chat" style={{ display: SnappingManager.PropertiesWidth < 1 ? 'none' : undefined }}>
+                        <span className="presBox-gpt-chat-span">
                             Customize Slide Properties{' '}
                             <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/trails/#slide-customization')}>
                                 <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SnappingManager.userColor} />
                             </div>
                         </span>
                         <div className="pres-chat">
-                            <div className="pres-chatbox-container">
+                            <div className="pres-chatbox-container-ai">
                                 <ReactTextareaAutosize
-                                    placeholder="Describe how you would like to modify the slide properties."
+                                    placeholder="Describe how to modify the slide properties."
                                     className="pres-chatbox"
+                                    ref={r => {
+                                        setTimeout(() => {
+                                            if (r && !r.textContent) {
+                                                r.style.height = '';
+                                                r.style.height = r.scrollHeight + 'px';
+                                            }
+                                        });
+                                    }}
                                     value={this._chatInput}
-                                    onChange={e => this.setChatInput(e.target.value)}
+                                    onChange={e => {
+                                        e.currentTarget.style.height = '';
+                                        e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px';
+                                        this.setChatInput(e.target.value);
+                                    }}
                                     onKeyDown={e => {
                                         this.stopDictation();
                                         e.stopPropagation();
@@ -1805,6 +1814,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             />
                         </div>
                     </div>
+
                     {/* Movement */}
                     <div
                         className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`}
@@ -1816,46 +1826,47 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             this._openEffectDropdown = false;
                             this._openBulletEffectDropdown = false;
                         })}>
-                        <div
-                            className="presBox-option-block"
-                            // style={{ padding: '16px' }}
-                        >
-                            Movement
+                        <div className="presBox-option-block">
                             <Dropdown
                                 color={SnappingManager.userColor}
-                                formLabel="Movement"
+                                formLabel="Navigation"
+                                formLabelPlacement="left"
                                 closeOnSelect
                                 items={movementItems}
                                 selectedVal={this.movementName(activeItem)}
-                                setSelectedVal={val => {
-                                    this.updateMovement(val as PresMovement);
-                                }}
+                                setSelectedVal={val => this.updateMovement(val as PresMovement)}
                                 dropdownType={DropdownType.SELECT}
                                 type={Type.TERT}
                             />
-                            <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? 'inline-flex' : 'none' }}>
-                                <div className="presBox-subheading">Zoom (% screen filled)</div>
-                                <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
-                                    <input className="presBox-input" readOnly type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
+                            <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? undefined : 'none' }}>
+                                <Tooltip title={<div>How much (%) of screen target should occupy</div>}>
+                                    <div className="presBox-subheading">ZOOM %</div>
+                                </Tooltip>
+                                <div className="presBox-subheading-slider">{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}</div>
+                                <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
+                                    <input style={{ maxWidth: 25 }} className="presBox-input" readOnly type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />
+                                    <span>%</span>
                                 </div>
                             </div>
-                            {PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}
-                            <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
-                                <div className="presBox-subheading">Transition Time</div>
-                                <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
-                                    <input className="presBox-input" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
+                            <div className="ribbon-doubleButton">
+                                <div className="presBox-subheading">ZOOM/EFFECT TIME</div>
+                                <div className="presBox-subheading-slider">
+                                    {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.updateTransitionTime)}
+                                    <div className="slider-headers">
+                                        <div className="slider-text">Fast</div>
+                                        <div className="slider-text">Slow</div>
+                                    </div>
+                                </div>
+                                <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
+                                    <input style={{ maxWidth: 25 }} className="presBox-input" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} />
+                                    <span>s</span>
                                 </div>
-                            </div>
-                            {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.updateTransitionTime)}
-                            <div className="slider-headers">
-                                <div className="slider-text">Fast</div>
-                                <div className="slider-text">Medium</div>
-                                <div className="slider-text">Slow</div>
                             </div>
                             {/* Easing function */}
                             <Dropdown
                                 color={SnappingManager.userColor}
-                                formLabel="Easing Function"
+                                formLabel="Timing Function"
+                                formLabelPlacement="left"
                                 closeOnSelect
                                 items={easeItems}
                                 selectedVal={this.activeItem.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'}
@@ -1880,32 +1891,75 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     e.stopPropagation();
                                     this.setBezierEditorVisibility(!this._showBezierEditor);
                                 }}>
-                                {`${this._showBezierEditor ? 'Hide' : 'Show'} Timing Editor`}
-                                <FontAwesomeIcon icon={this._showBezierEditor ? 'chevron-up' : 'chevron-down'} />
+                                TIMING EDITOR
+                                <FontAwesomeIcon icon={this._showBezierEditor ? 'chevron-down' : 'chevron-up'} />
                             </div>
                         </div>
                     </div>
 
                     {/* Cubic bezier editor */}
                     {this._showBezierEditor && (
-                        <div className="presBox-option-block" style={{ paddingTop: 0 }}>
-                            <p className="presBox-submenu-label" style={{ alignSelf: 'flex-start' }}>
-                                Custom Timing Function
-                            </p>
+                        <div className="presBox-option-block" style={{ paddingTop: 0, alignItems: 'center' }}>
                             <CubicBezierEditor setFunc={this.setBezierControlPoints} currPoints={this.currCPoints} />
                         </div>
                     )}
 
+                    {effect === PresEffect.None ? null : (
+                        <div className="presBox-previewContainer">
+                            <Button
+                                type={Type.TERT}
+                                tooltip="show preview of slide animation effect"
+                                size={Size.SMALL}
+                                color={SnappingManager.userColor}
+                                background={'transparent'}
+                                onClick={action(() => {
+                                    this._showPreview = false;
+                                    setTimeout(action(() => { this._showPreview = true; }) ); // prettier-ignore
+                                })}
+                                text="Preview Effect"
+                            />
+                            <div className="presBox-option-block presBox-option-center">
+                                <div className="presBox-effect-container">
+                                    {!this._showPreview ? null : (
+                                        <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig}>
+                                            <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} />
+                                        </SlideEffect>
+                                    )}
+                                </div>
+                            </div>
+                        </div>
+                    )}
+
                     {/* This chatbox is for getting slide effect transition suggestions from gpt and visualizing them */}
-                    <div className="presBox-gpt-chat">
-                        Effects
-                        <div className="pres-chat">
-                            <div className="pres-chatbox-container">
+                    <div className="presBox-gpt-chat" style={{ display: SnappingManager.PropertiesWidth < 1 ? 'none' : undefined }}>
+                        {/* Custom */}
+                        <div
+                            className="presBox-show-hide-dropdown"
+                            style={{ alignSelf: 'flex-start' }}
+                            onClick={e => {
+                                e.stopPropagation();
+                                this.setShowAIGalleryVisibilty(!this._showAIGallery);
+                            }}>
+                            AI EFFECTS
+                            <FontAwesomeIcon icon={this._showAIGallery ? 'chevron-down' : 'chevron-up'} />
+                        </div>
+                        <div className="pres-chat" style={{ display: this._showAIGallery ? undefined : 'none' }}>
+                            <div className="pres-chatbox-container-ai">
                                 <ReactTextareaAutosize
-                                    placeholder="Customize prompt for effect suggestions. Leave blank for random results."
+                                    placeholder="Use AI to suggest effects. Leave blank for random results."
                                     className="pres-chatbox"
+                                    ref={r => {
+                                        setTimeout(() => {
+                                            if (r && !r.textContent) {
+                                                r.style.height = '';
+                                                r.style.height = r.scrollHeight + 'px';
+                                            }
+                                        });
+                                    }}
                                     value={this._animationChat}
                                     onChange={e => {
+                                        e.currentTarget.style.height = '';
+                                        e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px';
                                         this.setAnimationChat(e.target.value);
                                     }}
                                     onKeyDown={e => {
@@ -1924,6 +1978,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 onClick={this.customizeAnimations}
                             />
                         </div>
+                        <div style={{ display: this._showAIGallery ? 'flex' : 'none', alignItems: 'center' }}>
+                            Click a box to use the effect.
+                            {/* Preview Animations  */}
+                            <div className="presBox-effects">
+                                {this.generatedAnimations.map((elem, i) => (
+                                    <div
+                                        key={i}
+                                        className="presBox-effect-container"
+                                        onClick={() => {
+                                            this.updateEffect(elem.effect, false);
+                                            this.updateEffectDirection(elem.direction);
+                                            this.updateEffectTiming(this.activeItem, {
+                                                type: SpringType.CUSTOM,
+                                                stiffness: elem.stiffness,
+                                                damping: elem.damping,
+                                                mass: elem.mass,
+                                            });
+                                        }}>
+                                        <SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite>
+                                            <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} />
+                                        </SlideEffect>
+                                    </div>
+                                ))}
+                            </div>
+                        </div>
                     </div>
 
                     <div
@@ -1937,171 +2016,139 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             this._openBulletEffectDropdown = false;
                         })}>
                         <div className="presBox-option-block">
-                            Click on a box to apply the effect.
-                            <div className="presBox-option-block presBox-option-center">
-                                {/* Preview Animations  */}
-                                <div className="presBox-effects">
-                                    {this.generatedAnimations.map((elem, i) => (
-                                        <div
-                                            key={i}
-                                            className="presBox-effect-container"
-                                            onClick={() => {
-                                                this.updateEffect(elem.effect, false);
-                                                this.updateEffectDirection(elem.direction);
-                                                this.updateEffectTiming(this.activeItem, {
-                                                    type: SpringType.CUSTOM,
-                                                    stiffness: elem.stiffness,
-                                                    damping: elem.damping,
-                                                    mass: elem.mass,
-                                                });
-                                            }}>
-                                            <SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite>
-                                                <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} />
-                                            </SlideEffect>
-                                        </div>
-                                    ))}
-                                </div>
-                            </div>
                             {/* Effect dropdown */}
-                            <Dropdown
-                                color={SnappingManager.userColor}
-                                formLabel="Slide Effect"
-                                closeOnSelect
-                                items={effectItems}
-                                selectedVal={effect?.toString()}
-                                setSelectedVal={val => {
-                                    this.updateEffect(val as PresEffect, false);
-                                    // set default spring options for that effect
-                                    this.updateEffectTiming(activeItem, presEffectDefaultTimings[val as keyof typeof presEffectDefaultTimings]);
-                                }}
-                                dropdownType={DropdownType.SELECT}
-                                type={Type.TERT}
-                            />
-                            {/* Effect direction */}
-                            {/* Only applies to certain effects */}
-                            {(effect === PresEffect.Flip || effect === PresEffect.Bounce || effect === PresEffect.Roll) && (
-                                <>
-                                    <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
-                                        <div className="presBox-subheading">Effect direction</div>
-                                        <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
-                                            {StrCast(this.activeItem.presentation_effectDirection)}
+                            <div>
+                                <Dropdown
+                                    color={SnappingManager.userColor}
+                                    formLabel="Slide Effect"
+                                    formLabelPlacement="left"
+                                    closeOnSelect
+                                    items={effectItems}
+                                    selectedVal={effect?.toString()}
+                                    setSelectedVal={val => {
+                                        this.updateEffect(val as PresEffect, false);
+                                        // set default spring options for that effect
+                                        this.updateEffectTiming(activeItem, presEffectDefaultTimings[val as keyof typeof presEffectDefaultTimings]);
+                                    }}
+                                    dropdownType={DropdownType.SELECT}
+                                    type={Type.TERT}
+                                />
+                            </div>
+                            <div className="presBox-gpt-chat">
+                                {/* Effect direction */}
+                                {/* Only applies to certain effects */}
+                                {(effect === PresEffect.Flip || effect === PresEffect.Bounce || effect === PresEffect.Roll) && (
+                                    <>
+                                        <div className="ribbon-doubleButton">
+                                            <div className="presBox-subheading">EFFECT DIRECTION</div>
+                                            <div style={{ width: '100%' }}>
+                                                <div className="presBox-icon-list" style={{ width: 'fit-content', margin: 'auto' }}>
+                                                    <IconButton
+                                                        type={Type.TERT}
+                                                        color={activeItem.presentation_effectDirection === PresEffectDirection.Left ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+                                                        tooltip="Left"
+                                                        icon={<FaArrowRight size="16px" />}
+                                                        onClick={() => this.updateEffectDirection(PresEffectDirection.Left)}
+                                                    />
+                                                    <IconButton
+                                                        type={Type.TERT}
+                                                        color={activeItem.presentation_effectDirection === PresEffectDirection.Right ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+                                                        tooltip="Right"
+                                                        icon={<FaArrowLeft size="16px" />}
+                                                        onClick={() => this.updateEffectDirection(PresEffectDirection.Right)}
+                                                    />
+                                                    {effect !== PresEffect.Roll && (
+                                                        <>
+                                                            <IconButton
+                                                                type={Type.TERT}
+                                                                color={activeItem.presentation_effectDirection === PresEffectDirection.Top ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+                                                                tooltip="Top"
+                                                                icon={<FaArrowDown size="16px" />}
+                                                                onClick={() => this.updateEffectDirection(PresEffectDirection.Top)}
+                                                            />
+                                                            <IconButton
+                                                                type={Type.TERT}
+                                                                color={activeItem.presentation_effectDirection === PresEffectDirection.Bottom ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+                                                                tooltip="Bottom"
+                                                                icon={<FaArrowUp size="16px" />}
+                                                                onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)}
+                                                            />
+                                                        </>
+                                                    )}
+                                                </div>
+                                            </div>
                                         </div>
-                                    </div>
-                                    <div className="presBox-icon-list">
-                                        <IconButton
-                                            type={Type.TERT}
-                                            color={activeItem.presentation_effectDirection === PresEffectDirection.Left ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
-                                            tooltip="Left"
-                                            icon={<FaArrowRight size="16px" />}
-                                            onClick={() => this.updateEffectDirection(PresEffectDirection.Left)}
-                                        />
-                                        <IconButton
+                                    </>
+                                )}
+                                {effect !== PresEffect.Lightspeed && (
+                                    <>
+                                        <Dropdown
+                                            color={SnappingManager.userColor}
+                                            formLabel="Effect Timing"
+                                            formLabelPlacement="left"
+                                            closeOnSelect
+                                            items={effectTimings}
+                                            selectedVal={timingConfig.type}
+                                            setSelectedVal={val => this.updateEffectTiming(activeItem, { type: val as SpringType, ...springMappings[val] })}
+                                            dropdownType={DropdownType.SELECT}
                                             type={Type.TERT}
-                                            color={activeItem.presentation_effectDirection === PresEffectDirection.Right ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
-                                            tooltip="Right"
-                                            icon={<FaArrowLeft size="16px" />}
-                                            onClick={() => this.updateEffectDirection(PresEffectDirection.Right)}
                                         />
-                                        {effect !== PresEffect.Roll && (
-                                            <>
-                                                <IconButton
-                                                    type={Type.TERT}
-                                                    color={activeItem.presentation_effectDirection === PresEffectDirection.Top ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
-                                                    tooltip="Top"
-                                                    icon={<FaArrowDown size="16px" />}
-                                                    onClick={() => this.updateEffectDirection(PresEffectDirection.Top)}
-                                                />
-                                                <IconButton
-                                                    type={Type.TERT}
-                                                    color={activeItem.presentation_effectDirection === PresEffectDirection.Bottom ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
-                                                    tooltip="Bottom"
-                                                    icon={<FaArrowUp size="16px" />}
-                                                    onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)}
-                                                />
-                                            </>
-                                        )}
-                                    </div>
-                                </>
-                            )}
-                            {/* Spring settings */}
-                            {/* No spring settings for jackinthebox (lightspeed) */}
-                            {effect !== PresEffect.Lightspeed && (
-                                <>
-                                    <Dropdown
-                                        color={SnappingManager.userColor}
-                                        formLabel="Effect Timing"
-                                        closeOnSelect
-                                        items={effectTimings}
-                                        selectedVal={timingConfig.type}
-                                        setSelectedVal={val => this.updateEffectTiming(activeItem, { type: val as SpringType, ...springMappings[val] })}
-                                        dropdownType={DropdownType.SELECT}
-                                        type={Type.TERT}
-                                    />
-                                    <div
-                                        className="presBox-show-hide-dropdown"
-                                        onClick={e => {
-                                            e.stopPropagation();
-                                            this.setSpringEditorVisibility(!this._showSpringEditor);
-                                        }}>
-                                        {`${this._showSpringEditor ? 'Hide' : 'Show'} Spring Settings`}
-                                        <FontAwesomeIcon icon={this._showSpringEditor ? 'chevron-up' : 'chevron-down'} />
-                                    </div>
-                                    {this._showSpringEditor && (
-                                        <>
-                                            <div>Tension</div>
-                                            <div onPointerDown={e => e.stopPropagation()}>
-                                                {/* prettier-ignore */}
-                                                <Slider min={1} max={1000} step={5} size="small" 
-                                                    value={timingConfig.stiffness}
-                                                    onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number })}
-                                                    valueLabelDisplay="auto"
-                                                />
-                                            </div>
-                                            <div>Damping</div>
-                                            <div onPointerDown={e => e.stopPropagation()}>
-                                                {/* prettier-ignore */}
-                                                <Slider min={1} max={100} step={1} size="small"
-                                                    value={timingConfig.damping}
-                                                    onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, damping: val as number })}
-                                                    valueLabelDisplay="auto"
-                                                />
-                                            </div>
-                                            <div>Mass</div>
-                                            <div onPointerDown={e => e.stopPropagation()}>
-                                                {/* prettier-ignore */}
-                                                <Slider min={1} max={10} step={1} size="small"
-                                                    value={timingConfig.mass}
-                                                    onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, mass: val as number })}
-                                                    valueLabelDisplay="auto"
-                                                />
+
+                                        <div style={{ display: SnappingManager.PropertiesWidth < 1 ? 'none' : undefined }}>
+                                            {/* Custom */}
+                                            <div
+                                                className="presBox-show-hide-dropdown"
+                                                style={{ display: effect !== PresEffect.None ? undefined : 'none' }}
+                                                onClick={e => {
+                                                    e.stopPropagation();
+                                                    this.setSpringEditorVisibility(!this._showSpringEditor);
+                                                }}>
+                                                SPRING SETTINGS
+                                                <FontAwesomeIcon icon={this._showSpringEditor ? 'chevron-down' : 'chevron-up'} />
                                             </div>
-                                            <div style={{ display: 'flex', alignItems: 'center'}}>
-                                                <Button
-                                                    type={Type.TERT}
-                                                    tooltip="show preview of slide animation effect"
-                                                    size={Size.SMALL}
-                                                    color={SnappingManager.userColor}
-                                                    background={'transparent'}
-                                                    onClick={action(() => {
-                                                        this._showPreview = false;
-                                                        setTimeout(action(() => { this._showPreview = true; }) );// prettier-ignore
-                                                    })}
-                                                    text={'Preview of: ' + effect + ', ' + direction}
-                                                />
-                                                <div className="presBox-option-block presBox-option-center">
-                                                    <div className="presBox-effect-container">
-                                                        {!this._showPreview ? null : (
-                                                            <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig}>
-                                                                <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} />
-                                                            </SlideEffect>
-                                                        )}
+                                            {/* Spring settings */}
+                                            {/* No spring settings for jackinthebox (lightspeed) */}
+                                            {this._showSpringEditor && effect !== PresEffect.None && (
+                                                <>
+                                                    <div className="presBox-springSlider">
+                                                        <span>Tension</span>
+                                                        <div onPointerDown={e => e.stopPropagation()}>
+                                                            {/* prettier-ignore */}
+                                                            <Slider min={1} max={1000} step={5} size="small" 
+                                                        value={timingConfig.stiffness}
+                                                        onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number })}
+                                                        valueLabelDisplay="auto"
+                                                    />
+                                                        </div>
                                                     </div>
-                                                </div>
-                                            </div>
-                                        </>
-                                    )}
-                                </>
-                            )}
+                                                    <div className="presBox-springSlider">
+                                                        <span>Damping</span>
+                                                        <div onPointerDown={e => e.stopPropagation()}>
+                                                            {/* prettier-ignore */}
+                                                            <Slider min={1} max={100} step={1} size="small"
+                                                                value={timingConfig.damping}
+                                                                onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, damping: val as number })}
+                                                                valueLabelDisplay="auto"
+                                                            />
+                                                        </div>
+                                                    </div>
+                                                    <div className="presBox-springSlider">
+                                                        <span>Mass</span>
+                                                        <div onPointerDown={e => e.stopPropagation()}>
+                                                            {/* prettier-ignore */}
+                                                            <Slider min={1} max={10} step={1} size="small"
+                                                                value={timingConfig.mass}
+                                                                onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, mass: val as number })}
+                                                                valueLabelDisplay="auto"
+                                                            />
+                                                        </div>
+                                                    </div>
+                                                </>
+                                            )}
+                                        </div>
+                                    </>
+                                )}
+                            </div>
                         </div>
 
                         {/* Toggles */}
-- 
cgit v1.2.3-70-g09d2


From a23e4c26d23224b56ec51d73b38adeb2b54fed6d Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 6 Feb 2025 14:37:19 -0500
Subject: overhaul of presbox properties ui + fixing bugs with progressivize
 highlight expanding

---
 src/client/views/PropertiesView.tsx        |  70 ++--
 src/client/views/nodes/trails/PresBox.scss |  36 +-
 src/client/views/nodes/trails/PresBox.tsx  | 591 ++++++++++++++---------------
 3 files changed, 350 insertions(+), 347 deletions(-)

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

diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 42aa6782f..bed96f600 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -119,6 +119,7 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
     // Pres Trails booleans:
     @observable openPresTransitions: boolean = true;
     @observable openPresProgressivize: boolean = false;
+    @observable openPresMedia: boolean = false;
     @observable openPresVisibilityAndDuration: boolean = false;
     @observable openAddSlide: boolean = false;
     @observable openSlideOptions: boolean = false;
@@ -1936,74 +1937,74 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                         </div>
                     </div>
                     {!selectedItem ? null : (
-                        <div className="propertiesView-presentationTrails">
+                        <div className="propertiesView-section">
                             <div
-                                className="propertiesView-presentationTrails-title"
+                                className="propertiesView-sectionTitle"
                                 onPointerDown={action(() => {
-                                    this.openPresTransitions = !this.openPresTransitions;
+                                    this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration;
                                 })}
                                 style={{
                                     color: SnappingManager.userColor,
-                                    backgroundColor: this.openPresTransitions ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+                                    backgroundColor: SnappingManager.userVariantColor,
                                 }}>
-                                &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Transitions
+                                Visibility
                                 <div className="propertiesView-presentationTrails-title-icon">
-                                    <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
+                                    <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
                                 </div>
                             </div>
-                            {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
+                            {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
                         </div>
                     )}
                     {!selectedItem ? null : (
-                        <div className="propertiesView-presentationTrails">
+                        <div className="propertiesView-section">
                             <div
-                                className="propertiesView-presentationTrails-title"
+                                className="propertiesView-sectionTitle"
                                 onPointerDown={action(() => {
-                                    this.openPresVisibilityAndDuration = !this.openPresVisibilityAndDuration;
+                                    this.openPresProgressivize = !this.openPresProgressivize;
                                 })}
                                 style={{
                                     color: SnappingManager.userColor,
-                                    backgroundColor: this.openPresVisibilityAndDuration ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+                                    backgroundColor: SnappingManager.userVariantColor,
                                 }}>
-                                &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Visibility
+                                Progressivize
                                 <div className="propertiesView-presentationTrails-title-icon">
-                                    <FontAwesomeIcon icon={this.openPresVisibilityAndDuration ? 'caret-down' : 'caret-right'} size="lg" />
+                                    <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
                                 </div>
                             </div>
-                            {this.openPresVisibilityAndDuration ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.visibilityDurationDropdown}</div> : null}
+                            {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
                         </div>
                     )}
                     {!selectedItem ? null : (
-                        <div className="propertiesView-presentationTrails">
+                        <div className="propertiesView-section">
                             <div
-                                className="propertiesView-presentationTrails-title"
+                                className="propertiesView-sectionTitle"
                                 onPointerDown={action(() => {
-                                    this.openPresProgressivize = !this.openPresProgressivize;
+                                    this.openPresMedia = !this.openPresMedia;
                                 })}
                                 style={{
                                     color: SnappingManager.userColor,
-                                    backgroundColor: this.openPresProgressivize ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+                                    backgroundColor: SnappingManager.userVariantColor,
                                 }}>
-                                &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon="rocket" /> &nbsp; Progressivize
+                                Media
                                 <div className="propertiesView-presentationTrails-title-icon">
-                                    <FontAwesomeIcon icon={this.openPresProgressivize ? 'caret-down' : 'caret-right'} size="lg" />
+                                    <FontAwesomeIcon icon={this.openPresMedia ? 'caret-down' : 'caret-right'} size="lg" />
                                 </div>
                             </div>
-                            {this.openPresProgressivize ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.progressivizeDropdown}</div> : null}
+                            {this.openPresMedia ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaDropdown}</div> : null}
                         </div>
                     )}
                     {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? null : (
-                        <div className="propertiesView-presentationTrails">
+                        <div className="propertiesView-section">
                             <div
-                                className="propertiesView-presentationTrails-title"
+                                className="propertiesView-sectionTitle"
                                 onPointerDown={action(() => {
                                     this.openSlideOptions = !this.openSlideOptions;
                                 })}
                                 style={{
                                     color: SnappingManager.userColor,
-                                    backgroundColor: this.openSlideOptions ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+                                    backgroundColor: SnappingManager.userVariantColor,
                                 }}>
-                                &nbsp; <FontAwesomeIcon style={{ alignSelf: 'center' }} icon={type === DocumentType.AUDIO ? 'file-audio' : 'file-video'} /> &nbsp; {type === DocumentType.AUDIO ? 'Audio Options' : 'Video Options'}
+                                {type === DocumentType.AUDIO ? 'file-audio' : 'file-video'}
                                 <div className="propertiesView-presentationTrails-title-icon">
                                     <FontAwesomeIcon icon={this.openSlideOptions ? 'caret-down' : 'caret-right'} size="lg" />
                                 </div>
@@ -2011,6 +2012,25 @@ export class PropertiesView extends ObservableReactComponent<PropertiesViewProps
                             {this.openSlideOptions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.mediaOptionsDropdown}</div> : null}
                         </div>
                     )}
+                    {!selectedItem ? null : (
+                        <div className="propertiesView-section">
+                            <div
+                                className="propertiesView-sectionTitle"
+                                onPointerDown={action(() => {
+                                    this.openPresTransitions = !this.openPresTransitions;
+                                })}
+                                style={{
+                                    color: SnappingManager.userColor,
+                                    backgroundColor: SnappingManager.userVariantColor,
+                                }}>
+                                Transitions
+                                <div className="propertiesView-presentationTrails-title-icon">
+                                    <FontAwesomeIcon icon={this.openPresTransitions ? 'caret-down' : 'caret-right'} size="lg" />
+                                </div>
+                            </div>
+                            {this.openPresTransitions ? <div className="propertiesView-presentationTrails-content">{PresBox.Instance.transitionDropdown}</div> : null}
+                        </div>
+                    )}
                 </div>
             );
         }
diff --git a/src/client/views/nodes/trails/PresBox.scss b/src/client/views/nodes/trails/PresBox.scss
index a181687f8..e34e1b380 100644
--- a/src/client/views/nodes/trails/PresBox.scss
+++ b/src/client/views/nodes/trails/PresBox.scss
@@ -18,6 +18,7 @@
     max-width: calc(100% - 25px);
     width: 100%;
     padding: 15px;
+    padding-left: 0px;
 }
 
 .pres-chat {
@@ -100,6 +101,14 @@
     gap: 4px;
 }
 
+.presBox-switches {
+    display: flex;
+    width: 100%;
+    > div {
+        width: 100%;
+    }
+}
+
 .presBox-bezier-editor {
     border: 1px solid rgb(221, 221, 221);
     border-radius: 4px;
@@ -302,7 +311,7 @@
 }
 
 .presBox-toggles {
-    display: flex;
+    display: flow;
     overflow-x: auto;
 }
 
@@ -387,7 +396,7 @@
         font-size: 11;
         font-weight: 400;
         margin-top: 10px;
-        max-width: min(100px, 25%);
+        max-width: min(85px, 25%);
         width: 100%;
     }
     .presBox-springSlider {
@@ -567,20 +576,17 @@
     }
 
     .presBox-input {
-        border: none;
         background-color: transparent;
-        width: 40;
-        // padding: 8px;
-        // border-radius: 4px;
-        // width: 30;
-        // height: 100%;
-        // background: none;
-        // border: none;
-        // text-align: right;
+        text-align: center;
+        width: 100%;
+        height: 15px;
+        font-size: 10;
     }
-
-    .presBox-input:focus {
-        outline: none;
+    .presBox-inputNumber {
+        border: none;
+        background-color: transparent;
+        width: 100%;
+        max-width: 25px;
     }
 
     .ribbon-frameSelector {
@@ -784,7 +790,7 @@
         font-weight: 200;
         height: 20;
         background-color: $white;
-        display: flex;
+        display: inline-flex;
         color: $black;
         border-radius: 5px;
         width: max-content;
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 5dbf502d2..28afe1df4 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1,4 +1,4 @@
-import { Button, Dropdown, DropdownType, IconButton, Size, Toggle, ToggleType, Type } from '@dash/components';
+import { Button, Dropdown, DropdownType, IconButton, Size, Type } from '@dash/components';
 import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
 import { Tooltip } from '@mui/material';
 import Slider from '@mui/material/Slider';
@@ -101,8 +101,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _forceKeyEvents: boolean = false;
 
     @observable _showAIGallery = false;
-    @observable _showBezierEditor = false;
-    @observable _showSpringEditor = false;
     @observable _showPreview = true;
     @observable _easeDropdownVal = 'ease';
 
@@ -150,8 +148,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     setAnimationChat =          action((input: string) => { this._animationChat = input; }); // prettier-ignore
     setIsLoading =              action((input?: boolean) => { this._isLoading = !!input; }); // prettier-ignore
     setIsRecording =            action((input: boolean) => { this._isRecording = input; }); // prettier-ignore
-    setBezierEditorVisibility = action((visible: boolean) => { this._showBezierEditor = visible; }); // prettier-ignore
-    setSpringEditorVisibility = action((visible: boolean) => { this._showSpringEditor = visible; }); // prettier-ignore
     setShowAIGalleryVisibilty = action((visible: boolean) => { this._showAIGallery = visible; }); // prettier-ignore
     setBezierControlPoints = action((newPoints: { p1: number[]; p2: number[] }) => {
         this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
@@ -390,7 +386,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     if (!first) {
                         const presBulletTiming = 500; // bcz: hardwired for now
                         Doc.linkFollowUnhighlight();
-                        Doc.HighlightDoc(listItemDoc);
+                        Doc.linkFollowHighlight(listItemDoc);
                         listItemDoc.presentation_effect = this.activeItem.presBulletEffect;
                         listItemDoc.presentation_transition = presBulletTiming;
                         listItemDoc.opacity = undefined;
@@ -1612,20 +1608,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             </Tooltip>
                         </div>
                         {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as DocumentType) ? null : (
-                            <>
-                                <div className="ribbon-doubleButton">
-                                    <div className="presBox-subheading">Slide Duration</div>
-                                    <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}>
-                                        <input className="presBox-input" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
+                            <div className="ribbon-doubleButton">
+                                <Tooltip title={<div>How long to view the slide before transitioning to the next slide</div>}>
+                                    <div className="presBox-subheading">DURATION</div>
+                                </Tooltip>
+                                <div className="presBox-subheading-slider">
+                                    {PresBox.inputter('0.1', '0.1', '10', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)}
+                                    <div className="slider-headers">
+                                        <div className="slider-text">Short</div>
+                                        <div className="slider-text">Long</div>
                                     </div>
                                 </div>
-                                {PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)}
-                                <div className="slider-headers" style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}>
-                                    <div className="slider-text">Short</div>
-                                    <div className="slider-text">Medium</div>
-                                    <div className="slider-text">Long</div>
+                                <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
+                                    <input className="presBox-inputNumber" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateDurationTime(e.target.value))} />
+                                    <span>s</span>
                                 </div>
-                            </>
+                            </div>
                         )}
                     </div>
                 </div>
@@ -1633,28 +1631,62 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
         return undefined;
     }
-    @computed get progressivizeDropdown() {
+
+    @computed get mediaDropdown() {
         const { activeItem } = this;
         if (activeItem && this.targetDoc) {
-            const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None;
-            const bulletEffect = (presEffect: PresEffect) => (
-                <div
-                    className={`presBox-dropdownOption ${activeItem.presentation_effect === presEffect || (presEffect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
-                    onPointerDown={StopEvent}
-                    onClick={() => this.updateEffect(presEffect, true)}>
-                    {presEffect}
+            return (
+                <div className="presBox-option-block">
+                    <div className="presBox-ribbon presbox-toggles">
+                        <Tooltip title={<div className="dash-tooltip">Should first bullet be progressively disclosed or does it appear with slide.</div>}>
+                            <div
+                                className={`ribbon-toggle ${BoolCast(activeItem.presentation_playAudio) ? 'active' : ''}`}
+                                style={{
+                                    border: `solid 1px ${SnappingManager.userColor}`,
+                                    color: SnappingManager.userColor,
+                                    background: BoolCast(activeItem.presentation_playAudio) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+                                }}
+                                onClick={() => {
+                                    activeItem.presentation_playAudio = !BoolCast(activeItem.presentation_playAudio);
+                                }}>
+                                Play Audio Annotation
+                            </div>
+                        </Tooltip>
+                        <Tooltip title={<div className="dash-tooltip">Should first bullet be progressively disclosed or does it appear with slide.</div>}>
+                            <div
+                                className={`ribbon-toggle ${BoolCast(activeItem.presentation_zoomText) ? 'active' : ''}`}
+                                style={{
+                                    border: `solid 1px ${SnappingManager.userColor}`,
+                                    color: SnappingManager.userColor,
+                                    background: BoolCast(activeItem.presentation_zoomText) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+                                }}
+                                onClick={() => {
+                                    activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText);
+                                }}>
+                                Zoom Text Selections
+                            </div>
+                        </Tooltip>
+                    </div>
                 </div>
             );
+        }
+        return null;
+    }
+    @computed get progressivizeDropdown() {
+        const { activeItem } = this;
+        if (activeItem && this.targetDoc) {
             return (
                 <div className="presBox-option-block">
-                    <div className="presBox-ribbon">
-                        <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
-                            <div className="presBox-subheading">Progressivize Collection</div>
-                            <input
-                                className="presBox-checkbox"
-                                style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
-                                type="checkbox"
-                                onChange={() => {
+                    <div className="presBox-toggles presBox-ribbon">
+                        <Tooltip title={<div className="dash-tooltip">whether progressivization is active for this slide</div>}>
+                            <div
+                                className={`ribbon-toggle ${Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? 'active' : ''}`}
+                                style={{
+                                    border: `solid 1px ${SnappingManager.userColor}`,
+                                    color: SnappingManager.userColor,
+                                    background: Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+                                }}
+                                onClick={() => {
                                     activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
                                     activeItem.presentation_hideBefore = activeItem.presentation_indexed !== undefined;
                                     const tagDoc = PresBox.targetRenderedDoc(this.activeItem);
@@ -1667,62 +1699,51 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
                                     if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
                                     else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`);
+                                }}>
+                                Enable
+                            </div>
+                        </Tooltip>
+                        <Tooltip title={<div className="dash-tooltip">Should first bullet be progressively disclosed or does it appear with slide.</div>}>
+                            <div
+                                className={`ribbon-toggle ${!NumCast(activeItem.presentation_indexedStart) ? 'active' : ''}`}
+                                style={{
+                                    border: `solid 1px ${SnappingManager.userColor}`,
+                                    color: SnappingManager.userColor,
+                                    background: !NumCast(activeItem.presentation_indexedStart) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
                                 }}
-                                checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined}
-                            />
-                        </div>
-                        <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
-                            <div className="presBox-subheading">Progressivize First Bullet</div>
-                            <input
-                                className="presBox-checkbox"
-                                style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
-                                type="checkbox"
-                                onChange={() => {
+                                onClick={() => {
                                     activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1;
-                                }}
-                                checked={!NumCast(activeItem.presentation_indexedStart)}
-                            />
-                        </div>
-                        <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
-                            <div className="presBox-subheading">Expand Current Bullet</div>
-                            <input
-                                className="presBox-checkbox"
-                                style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }}
-                                type="checkbox"
-                                onChange={() => {
-                                    activeItem.presBulletExpand = !activeItem.presBulletExpand;
-                                }}
-                                checked={BoolCast(activeItem.presBulletExpand)}
-                            />
-                        </div>
-
-                        <div className="ribbon-box">
-                            Bullet Effect
+                                }}>
+                                All Bullets
+                            </div>
+                        </Tooltip>
+                        <Tooltip title={<div className="dash-tooltip">Whether the active bullet expands when active.</div>}>
                             <div
-                                className="presBox-dropdown"
-                                onClick={action(e => {
-                                    e.stopPropagation();
-                                    this._openBulletEffectDropdown = !this._openBulletEffectDropdown;
-                                })}
+                                className={`ribbon-toggle ${BoolCast(activeItem.presBulletExpand) ? 'active' : ''}`}
                                 style={{
+                                    border: `solid 1px ${SnappingManager.userColor}`,
                                     color: SnappingManager.userColor,
-                                    background: SnappingManager.userVariantColor,
-                                    borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5,
-                                    border: this._openBulletEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`,
+                                    background: BoolCast(activeItem.presBulletExpand) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+                                }}
+                                onClick={() => {
+                                    activeItem.presBulletExpand = !activeItem.presBulletExpand;
                                 }}>
-                                {effect?.toString()}
-                                <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" />
-                                <div
-                                    className="presBox-dropdownOptions"
-                                    style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }}
-                                    onPointerDown={e => e.stopPropagation()}>
-                                    {Object.values(PresEffect)
-                                        .filter(v => isNaN(Number(v)))
-                                        .map(pEffect => bulletEffect(pEffect))}
-                                </div>
+                                Expand Active
                             </div>
-                        </div>
+                        </Tooltip>
                     </div>
+                    <Dropdown
+                        color={SnappingManager.userColor}
+                        formLabel="Effect"
+                        toolTip="Animation effect to use when bullet activates"
+                        formLabelPlacement="left"
+                        closeOnSelect
+                        items={Object.values(PresEffect).map(v => ({ text: v.toString(), val: v }))}
+                        selectedVal={StrCast(activeItem.presBulletEffect, PresMovement.None)}
+                        setSelectedVal={val => this.updateEffect(val as PresEffect, true)}
+                        dropdownType={DropdownType.SELECT}
+                        type={Type.TERT}
+                    />
                 </div>
             );
         }
@@ -1733,6 +1754,77 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         return <div />;
     }
 
+    /**
+     * This chatbox is for getting slide effect transition suggestions from gpt and visualizing them
+     */
+    @computed get aiEffects() {
+        return (
+            <div className="presBox-gpt-chat" style={{ display: SnappingManager.PropertiesWidth < 1 || !this._showAIGallery ? 'none' : undefined }}>
+                {/* Custom */}
+                <div className="pres-chat">
+                    <div className="pres-chatbox-container-ai">
+                        <ReactTextareaAutosize
+                            placeholder="Use AI to suggest effects. Leave blank for random results."
+                            className="pres-chatbox"
+                            ref={r => {
+                                setTimeout(() => {
+                                    if (r && !r.textContent) {
+                                        r.style.height = '';
+                                        r.style.height = r.scrollHeight + 'px';
+                                    }
+                                });
+                            }}
+                            value={this._animationChat}
+                            onChange={e => {
+                                e.currentTarget.style.height = '';
+                                e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px';
+                                this.setAnimationChat(e.target.value);
+                            }}
+                            onKeyDown={e => {
+                                this.stopDictation();
+                                e.stopPropagation();
+                            }}
+                        />
+                    </div>
+                    <Button
+                        style={{ alignSelf: 'flex-end' }}
+                        text="Send"
+                        type={Type.TERT}
+                        icon={this._isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
+                        iconPlacement="right"
+                        color={SnappingManager.userVariantColor}
+                        onClick={this.customizeAnimations}
+                    />
+                </div>
+                <div style={{ alignItems: 'center' }}>
+                    Click a box to use the effect.
+                    {/* Preview Animations  */}
+                    <div className="presBox-effects">
+                        {this.generatedAnimations.map((elem, i) => (
+                            <div
+                                key={i}
+                                className="presBox-effect-container"
+                                onClick={() => {
+                                    this.updateEffect(elem.effect, false);
+                                    this.updateEffectDirection(elem.direction);
+                                    this.updateEffectTiming(this.activeItem, {
+                                        type: SpringType.CUSTOM,
+                                        stiffness: elem.stiffness,
+                                        damping: elem.damping,
+                                        mass: elem.mass,
+                                    });
+                                }}>
+                                <SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite>
+                                    <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} />
+                                </SlideEffect>
+                            </div>
+                        ))}
+                    </div>
+                </div>
+            </div>
+        );
+    }
+
     @computed get transitionDropdown() {
         const { activeItem } = this;
         // Retrieving spring timing properties
@@ -1827,29 +1919,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             this._openBulletEffectDropdown = false;
                         })}>
                         <div className="presBox-option-block">
-                            <Dropdown
-                                color={SnappingManager.userColor}
-                                formLabel="Navigation"
-                                formLabelPlacement="left"
-                                closeOnSelect
-                                items={movementItems}
-                                selectedVal={this.movementName(activeItem)}
-                                setSelectedVal={val => this.updateMovement(val as PresMovement)}
-                                dropdownType={DropdownType.SELECT}
-                                type={Type.TERT}
-                            />
-                            <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? undefined : 'none' }}>
-                                <Tooltip title={<div>How much (%) of screen target should occupy</div>}>
-                                    <div className="presBox-subheading">ZOOM %</div>
-                                </Tooltip>
-                                <div className="presBox-subheading-slider">{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}</div>
-                                <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
-                                    <input style={{ maxWidth: 25 }} className="presBox-input" readOnly type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />
-                                    <span>%</span>
-                                </div>
-                            </div>
                             <div className="ribbon-doubleButton">
-                                <div className="presBox-subheading">ZOOM/EFFECT TIME</div>
+                                <Tooltip title={<div>How long the transition (view navigation and slide animation effect) lasts</div>}>
+                                    <div className="presBox-subheading">SPEED</div>
+                                </Tooltip>
                                 <div className="presBox-subheading-slider">
                                     {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.updateTransitionTime)}
                                     <div className="slider-headers">
@@ -1858,153 +1931,55 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     </div>
                                 </div>
                                 <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
-                                    <input style={{ maxWidth: 25 }} className="presBox-input" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} />
+                                    <input className="presBox-inputNumber" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} />
                                     <span>s</span>
                                 </div>
                             </div>
-                            {/* Easing function */}
                             <Dropdown
                                 color={SnappingManager.userColor}
-                                formLabel="Timing Function"
+                                formLabel="View"
                                 formLabelPlacement="left"
                                 closeOnSelect
-                                items={easeItems}
-                                selectedVal={this.activeItem.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'}
-                                setSelectedVal={val => {
-                                    if (typeof val === 'string') {
-                                        if (val !== 'custom') {
-                                            this.setEaseFunc(this.activeItem, val);
-                                        } else {
-                                            this.setBezierEditorVisibility(true);
-                                            this.setEaseFunc(this.activeItem, TIMING_DEFAULT_MAPPINGS.ease);
-                                        }
-                                    }
-                                }}
+                                items={movementItems}
+                                selectedVal={this.movementName(activeItem)}
+                                setSelectedVal={val => this.updateMovement(val as PresMovement)}
                                 dropdownType={DropdownType.SELECT}
                                 type={Type.TERT}
                             />
-                            {/* Custom */}
-                            <div
-                                className="presBox-show-hide-dropdown"
-                                style={{ alignSelf: 'flex-start' }}
-                                onClick={e => {
-                                    e.stopPropagation();
-                                    this.setBezierEditorVisibility(!this._showBezierEditor);
-                                }}>
-                                TIMING EDITOR
-                                <FontAwesomeIcon icon={this._showBezierEditor ? 'chevron-down' : 'chevron-up'} />
+                            <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? undefined : 'none' }}>
+                                <Tooltip title={<div>How much (%) of screen target should occupy</div>}>
+                                    <div className="presBox-subheading">ZOOM %</div>
+                                </Tooltip>
+                                <div className="presBox-subheading-slider">{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}</div>
+                                <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
+                                    <input className="presBox-inputNumber" readOnly type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />
+                                    <span>%</span>
+                                </div>
                             </div>
+                            {/* Easing function */}
+                            {[PresMovement.None, PresMovement.Jump, ''].includes(StrCast(activeItem.presentation_movement) as PresMovement) ? null : (
+                                <Dropdown
+                                    color={SnappingManager.userColor}
+                                    formLabel="Timing"
+                                    formLabelPlacement="left"
+                                    closeOnSelect
+                                    items={easeItems}
+                                    selectedVal={this.activeItem.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'}
+                                    setSelectedVal={val => typeof val === 'string' && this.setEaseFunc(this.activeItem, val !== 'custom' ? val : TIMING_DEFAULT_MAPPINGS.ease)}
+                                    dropdownType={DropdownType.SELECT}
+                                    type={Type.TERT}
+                                />
+                            )}
                         </div>
                     </div>
 
                     {/* Cubic bezier editor */}
-                    {this._showBezierEditor && (
+                    {StrCast(activeItem.presentation_easeFunc).includes('cubic-bezier') && (
                         <div className="presBox-option-block" style={{ paddingTop: 0, alignItems: 'center' }}>
                             <CubicBezierEditor setFunc={this.setBezierControlPoints} currPoints={this.currCPoints} />
                         </div>
                     )}
 
-                    {effect === PresEffect.None ? null : (
-                        <div className="presBox-previewContainer">
-                            <Button
-                                type={Type.TERT}
-                                tooltip="show preview of slide animation effect"
-                                size={Size.SMALL}
-                                color={SnappingManager.userColor}
-                                background={'transparent'}
-                                onClick={action(() => {
-                                    this._showPreview = false;
-                                    setTimeout(action(() => { this._showPreview = true; }) ); // prettier-ignore
-                                })}
-                                text="Preview Effect"
-                            />
-                            <div className="presBox-option-block presBox-option-center">
-                                <div className="presBox-effect-container">
-                                    {!this._showPreview ? null : (
-                                        <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig}>
-                                            <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} />
-                                        </SlideEffect>
-                                    )}
-                                </div>
-                            </div>
-                        </div>
-                    )}
-
-                    {/* This chatbox is for getting slide effect transition suggestions from gpt and visualizing them */}
-                    <div className="presBox-gpt-chat" style={{ display: SnappingManager.PropertiesWidth < 1 ? 'none' : undefined }}>
-                        {/* Custom */}
-                        <div
-                            className="presBox-show-hide-dropdown"
-                            style={{ alignSelf: 'flex-start' }}
-                            onClick={e => {
-                                e.stopPropagation();
-                                this.setShowAIGalleryVisibilty(!this._showAIGallery);
-                            }}>
-                            AI EFFECTS
-                            <FontAwesomeIcon icon={this._showAIGallery ? 'chevron-down' : 'chevron-up'} />
-                        </div>
-                        <div className="pres-chat" style={{ display: this._showAIGallery ? undefined : 'none' }}>
-                            <div className="pres-chatbox-container-ai">
-                                <ReactTextareaAutosize
-                                    placeholder="Use AI to suggest effects. Leave blank for random results."
-                                    className="pres-chatbox"
-                                    ref={r => {
-                                        setTimeout(() => {
-                                            if (r && !r.textContent) {
-                                                r.style.height = '';
-                                                r.style.height = r.scrollHeight + 'px';
-                                            }
-                                        });
-                                    }}
-                                    value={this._animationChat}
-                                    onChange={e => {
-                                        e.currentTarget.style.height = '';
-                                        e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px';
-                                        this.setAnimationChat(e.target.value);
-                                    }}
-                                    onKeyDown={e => {
-                                        this.stopDictation();
-                                        e.stopPropagation();
-                                    }}
-                                />
-                            </div>
-                            <Button
-                                style={{ alignSelf: 'flex-end' }}
-                                text="Send"
-                                type={Type.TERT}
-                                icon={this._isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />}
-                                iconPlacement="right"
-                                color={SnappingManager.userVariantColor}
-                                onClick={this.customizeAnimations}
-                            />
-                        </div>
-                        <div style={{ display: this._showAIGallery ? 'flex' : 'none', alignItems: 'center' }}>
-                            Click a box to use the effect.
-                            {/* Preview Animations  */}
-                            <div className="presBox-effects">
-                                {this.generatedAnimations.map((elem, i) => (
-                                    <div
-                                        key={i}
-                                        className="presBox-effect-container"
-                                        onClick={() => {
-                                            this.updateEffect(elem.effect, false);
-                                            this.updateEffectDirection(elem.direction);
-                                            this.updateEffectTiming(this.activeItem, {
-                                                type: SpringType.CUSTOM,
-                                                stiffness: elem.stiffness,
-                                                damping: elem.damping,
-                                                mass: elem.mass,
-                                            });
-                                        }}>
-                                        <SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite>
-                                            <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} />
-                                        </SlideEffect>
-                                    </div>
-                                ))}
-                            </div>
-                        </div>
-                    </div>
-
                     <div
                         className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`}
                         onPointerDown={StopEvent}
@@ -2017,10 +1992,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         })}>
                         <div className="presBox-option-block">
                             {/* Effect dropdown */}
-                            <div>
+                            <div style={{ display: 'flex' }}>
                                 <Dropdown
                                     color={SnappingManager.userColor}
-                                    formLabel="Slide Effect"
+                                    formLabel="Effect"
+                                    toolTip="Animation effect to apply when transitiong to slide"
                                     formLabelPlacement="left"
                                     closeOnSelect
                                     items={effectItems}
@@ -2033,58 +2009,68 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     dropdownType={DropdownType.SELECT}
                                     type={Type.TERT}
                                 />
+
+                                <div
+                                    className={`ribbon-toggle ${this._showAIGallery ? 'active' : ''}`}
+                                    style={{
+                                        border: `solid 1px ${SnappingManager.userColor}`,
+                                        color: SnappingManager.userColor,
+                                        background: this._showAIGallery ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor,
+                                    }}
+                                    onClick={() => this.setShowAIGalleryVisibilty(!this._showAIGallery)}>
+                                    MORE
+                                </div>
                             </div>
+                            {this.aiEffects}
                             <div className="presBox-gpt-chat">
                                 {/* Effect direction */}
                                 {/* Only applies to certain effects */}
                                 {(effect === PresEffect.Flip || effect === PresEffect.Bounce || effect === PresEffect.Roll) && (
-                                    <>
-                                        <div className="ribbon-doubleButton">
-                                            <div className="presBox-subheading">EFFECT DIRECTION</div>
-                                            <div style={{ width: '100%' }}>
-                                                <div className="presBox-icon-list" style={{ width: 'fit-content', margin: 'auto' }}>
-                                                    <IconButton
-                                                        type={Type.TERT}
-                                                        color={activeItem.presentation_effectDirection === PresEffectDirection.Left ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
-                                                        tooltip="Left"
-                                                        icon={<FaArrowRight size="16px" />}
-                                                        onClick={() => this.updateEffectDirection(PresEffectDirection.Left)}
-                                                    />
-                                                    <IconButton
-                                                        type={Type.TERT}
-                                                        color={activeItem.presentation_effectDirection === PresEffectDirection.Right ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
-                                                        tooltip="Right"
-                                                        icon={<FaArrowLeft size="16px" />}
-                                                        onClick={() => this.updateEffectDirection(PresEffectDirection.Right)}
-                                                    />
-                                                    {effect !== PresEffect.Roll && (
-                                                        <>
-                                                            <IconButton
-                                                                type={Type.TERT}
-                                                                color={activeItem.presentation_effectDirection === PresEffectDirection.Top ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
-                                                                tooltip="Top"
-                                                                icon={<FaArrowDown size="16px" />}
-                                                                onClick={() => this.updateEffectDirection(PresEffectDirection.Top)}
-                                                            />
-                                                            <IconButton
-                                                                type={Type.TERT}
-                                                                color={activeItem.presentation_effectDirection === PresEffectDirection.Bottom ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
-                                                                tooltip="Bottom"
-                                                                icon={<FaArrowUp size="16px" />}
-                                                                onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)}
-                                                            />
-                                                        </>
-                                                    )}
-                                                </div>
+                                    <div className="ribbon-doubleButton">
+                                        <div className="presBox-subheading">DIRECTION</div>
+                                        <div style={{ width: '100%' }}>
+                                            <div className="presBox-icon-list" style={{ width: 'fit-content', margin: 'auto' }}>
+                                                <IconButton
+                                                    type={Type.TERT}
+                                                    color={activeItem.presentation_effectDirection === PresEffectDirection.Left ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+                                                    tooltip="Left"
+                                                    icon={<FaArrowRight size="16px" />}
+                                                    onClick={() => this.updateEffectDirection(PresEffectDirection.Left)}
+                                                />
+                                                <IconButton
+                                                    type={Type.TERT}
+                                                    color={activeItem.presentation_effectDirection === PresEffectDirection.Right ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+                                                    tooltip="Right"
+                                                    icon={<FaArrowLeft size="16px" />}
+                                                    onClick={() => this.updateEffectDirection(PresEffectDirection.Right)}
+                                                />
+                                                {effect !== PresEffect.Roll && (
+                                                    <>
+                                                        <IconButton
+                                                            type={Type.TERT}
+                                                            color={activeItem.presentation_effectDirection === PresEffectDirection.Top ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+                                                            tooltip="Top"
+                                                            icon={<FaArrowDown size="16px" />}
+                                                            onClick={() => this.updateEffectDirection(PresEffectDirection.Top)}
+                                                        />
+                                                        <IconButton
+                                                            type={Type.TERT}
+                                                            color={activeItem.presentation_effectDirection === PresEffectDirection.Bottom ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor}
+                                                            tooltip="Bottom"
+                                                            icon={<FaArrowUp size="16px" />}
+                                                            onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)}
+                                                        />
+                                                    </>
+                                                )}
                                             </div>
                                         </div>
-                                    </>
+                                    </div>
                                 )}
                                 {effect !== PresEffect.Lightspeed && (
                                     <>
                                         <Dropdown
                                             color={SnappingManager.userColor}
-                                            formLabel="Effect Timing"
+                                            formLabel="Springiness"
                                             formLabelPlacement="left"
                                             closeOnSelect
                                             items={effectTimings}
@@ -2095,20 +2081,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         />
 
                                         <div style={{ display: SnappingManager.PropertiesWidth < 1 ? 'none' : undefined }}>
-                                            {/* Custom */}
-                                            <div
-                                                className="presBox-show-hide-dropdown"
-                                                style={{ display: effect !== PresEffect.None ? undefined : 'none' }}
-                                                onClick={e => {
-                                                    e.stopPropagation();
-                                                    this.setSpringEditorVisibility(!this._showSpringEditor);
-                                                }}>
-                                                SPRING SETTINGS
-                                                <FontAwesomeIcon icon={this._showSpringEditor ? 'chevron-down' : 'chevron-up'} />
-                                            </div>
-                                            {/* Spring settings */}
                                             {/* No spring settings for jackinthebox (lightspeed) */}
-                                            {this._showSpringEditor && effect !== PresEffect.None && (
+                                            {StrCast(activeItem.presentation_effectTiming).includes('custom') && effect !== PresEffect.None && (
                                                 <>
                                                     <div className="presBox-springSlider">
                                                         <span>Tension</span>
@@ -2150,30 +2124,35 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 )}
                             </div>
                         </div>
+                    </div>
 
-                        {/* Toggles */}
-                        <div className="presBox-option-block">
-                            <Toggle
-                                formLabel="Play Audio Annotation"
-                                toggleType={ToggleType.SWITCH}
-                                toggleStatus={BoolCast(activeItem.presentation_playAudio)}
-                                onClick={() => {
-                                    activeItem.presentation_playAudio = !BoolCast(activeItem.presentation_playAudio);
-                                }}
-                                color={SnappingManager.userColor}
-                            />
-                            <Toggle
-                                formLabel="Zoom Text Selections"
-                                toggleType={ToggleType.SWITCH}
-                                toggleStatus={BoolCast(activeItem.presentation_zoomText)}
-                                onClick={() => {
-                                    activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText);
-                                }}
+                    {effect === PresEffect.None ? null : (
+                        <div className="presBox-previewContainer">
+                            <Button
+                                type={Type.TERT}
+                                tooltip="show preview of slide animation effect"
+                                size={Size.SMALL}
                                 color={SnappingManager.userColor}
+                                background={'transparent'}
+                                onClick={action(() => {
+                                    this._showPreview = false;
+                                    setTimeout(action(() => { this._showPreview = true; }) ); // prettier-ignore
+                                })}
+                                text="Preview Effect"
                             />
-                            <Button text="Apply to all" type={Type.TERT} color={SnappingManager.userVariantColor} onClick={() => this.applyTo(this.childDocs)} />
+                            <div className="presBox-option-block presBox-option-center">
+                                <div className="presBox-effect-container">
+                                    {!this._showPreview ? null : (
+                                        <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig}>
+                                            <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} />
+                                        </SlideEffect>
+                                    )}
+                                </div>
+                            </div>
                         </div>
-                    </div>
+                    )}
+
+                    <Button text="Apply to all slides" type={Type.TERT} color={SnappingManager.userVariantColor} onClick={() => this.applyTo(this.childDocs)} />
                 </>
             );
         }
@@ -2199,7 +2178,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     <div id="startTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}>
                                         <input
                                             className="presBox-input"
-                                            style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
                                             type="number"
                                             readOnly
                                             value={NumCast(activeItem.config_clipStart).toFixed(2)}
@@ -2226,7 +2204,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         <input
                                             className="presBox-input"
                                             onKeyDown={e => e.stopPropagation()}
-                                            style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
                                             type="number"
                                             readOnly
                                             value={configClipEnd.toFixed(2)}
-- 
cgit v1.2.3-70-g09d2


From 392143a84250df3742bc2f52d358fec25877e4de Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Thu, 6 Feb 2025 14:54:13 -0500
Subject: from last - fixes to showing ease funcs/preview only when
 appropriate.

---
 src/client/views/nodes/trails/PresBox.tsx | 22 +++++++++++++---------
 1 file changed, 13 insertions(+), 9 deletions(-)

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

diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 28afe1df4..7c23ca8e1 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -153,6 +153,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
     });
 
+    @computed get showEaseFunctions() {
+        return ![PresMovement.None, PresMovement.Jump, ''].includes(StrCast(this.activeItem?.presentation_movement) as PresMovement);
+    }
+
     @computed
     get currCPoints() {
         return EaseFuncToPoints(this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease');
@@ -1957,7 +1961,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 </div>
                             </div>
                             {/* Easing function */}
-                            {[PresMovement.None, PresMovement.Jump, ''].includes(StrCast(activeItem.presentation_movement) as PresMovement) ? null : (
+                            {!this.showEaseFunctions ? null : (
                                 <Dropdown
                                     color={SnappingManager.userColor}
                                     formLabel="Timing"
@@ -1974,7 +1978,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     </div>
 
                     {/* Cubic bezier editor */}
-                    {StrCast(activeItem.presentation_easeFunc).includes('cubic-bezier') && (
+                    {this.showEaseFunctions && StrCast(activeItem.presentation_easeFunc).includes('cubic-bezier') && (
                         <div className="presBox-option-block" style={{ paddingTop: 0, alignItems: 'center' }}>
                             <CubicBezierEditor setFunc={this.setBezierControlPoints} currPoints={this.currCPoints} />
                         </div>
@@ -2066,7 +2070,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         </div>
                                     </div>
                                 )}
-                                {effect !== PresEffect.Lightspeed && (
+                                {![PresEffect.Lightspeed, PresEffect.Fade, PresEffect.None, ''].includes(effect) && (
                                     <>
                                         <Dropdown
                                             color={SnappingManager.userColor}
@@ -2089,10 +2093,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                                         <div onPointerDown={e => e.stopPropagation()}>
                                                             {/* prettier-ignore */}
                                                             <Slider min={1} max={1000} step={5} size="small" 
-                                                        value={timingConfig.stiffness}
-                                                        onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number })}
-                                                        valueLabelDisplay="auto"
-                                                    />
+                                                                value={timingConfig.stiffness}
+                                                                onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number })}
+                                                                valueLabelDisplay="auto"
+                                                            />
                                                         </div>
                                                     </div>
                                                     <div className="presBox-springSlider">
@@ -2126,14 +2130,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </div>
                     </div>
 
-                    {effect === PresEffect.None ? null : (
+                    {[PresEffect.None, PresEffect.Fade, ''].includes(effect) ? null : (
                         <div className="presBox-previewContainer">
                             <Button
                                 type={Type.TERT}
                                 tooltip="show preview of slide animation effect"
                                 size={Size.SMALL}
                                 color={SnappingManager.userColor}
-                                background={'transparent'}
+                                background="transparent"
                                 onClick={action(() => {
                                     this._showPreview = false;
                                     setTimeout(action(() => { this._showPreview = true; }) ); // prettier-ignore
-- 
cgit v1.2.3-70-g09d2


From 132fb0c203f426c508eeb23dd33b508e0608d19f Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 10 Feb 2025 09:38:32 -0500
Subject: code cleanup in createDocumentTool. added childLayoutFitWidth to
 comparisonBox so flashcards can fill window.

---
 src/ClientUtils.ts                                 |  9 ++++++
 .../views/collections/CollectionNoteTakingView.tsx |  2 +-
 src/client/views/nodes/ComparisonBox.tsx           |  6 ++--
 src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx |  2 --
 .../views/nodes/chatbot/agentsystem/Agent.ts       |  2 +-
 .../nodes/chatbot/tools/CreateDocumentTool.ts      | 25 ++++-----------
 src/client/views/nodes/trails/PresBox.tsx          |  1 -
 src/server/ApiManagers/AssistantManager.ts         | 37 ++++++++++++----------
 8 files changed, 43 insertions(+), 41 deletions(-)

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

diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts
index e5dbb81db..8c9dd0a32 100644
--- a/src/ClientUtils.ts
+++ b/src/ClientUtils.ts
@@ -365,6 +365,15 @@ export namespace ClientUtils {
     }
 }
 
+/**
+ * Removes specified keys from an object and returns the result in the 'omit' field of the return value.
+ * The keys that were removed ared retuned in the 'extract' field of the return value.
+ * @param obj  - object to remove keys from
+ * @param keys - list of key field names to remove
+ * @param pattern - optional pattern to specify keys to removed
+ * @param addKeyFunc - optional function to call with object after keys have been removed
+ * @returns a tuple object containint 'omit' (oject after keys have been removed) and 'extact' (object containing omitted fields)
+ */
 export function OmitKeys(obj: object, keys: string[], pattern?: string, addKeyFunc?: (dup: object) => void): { omit: { [key: string]: unknown }; extract: { [key: string]: unknown } } {
     const omit: { [key: string]: unknown } = { ...obj };
     const extract: { [key: string]: unknown } = {};
diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx
index 323a679cc..4c23d4c48 100644
--- a/src/client/views/collections/CollectionNoteTakingView.tsx
+++ b/src/client/views/collections/CollectionNoteTakingView.tsx
@@ -2,7 +2,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, reacti
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { ClientUtils, DivHeight, lightOrDark, returnZero, smoothScroll } from '../../../ClientUtils';
-import { Doc, Field, FieldType, Opt, StrListCast } from '../../../fields/Doc';
+import { Doc, Opt, StrListCast } from '../../../fields/Doc';
 import { DocData } from '../../../fields/DocSymbols';
 import { Copy, Id } from '../../../fields/FieldSymbols';
 import { List } from '../../../fields/List';
diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx
index 53fbd11c5..cb0831d3c 100644
--- a/src/client/views/nodes/ComparisonBox.tsx
+++ b/src/client/views/nodes/ComparisonBox.tsx
@@ -10,7 +10,7 @@ import { emptyFunction } from '../../../Utils';
 import { Doc, Opt } from '../../../fields/Doc';
 import { Animation, DocData } from '../../../fields/DocSymbols';
 import { RichTextField } from '../../../fields/RichTextField';
-import { BoolCast, DocCast, NumCast, RTFCast, StrCast, toList } from '../../../fields/Types';
+import { BoolCast, Cast, DocCast, NumCast, RTFCast, StrCast, toList } from '../../../fields/Types';
 import { nullAudio } from '../../../fields/URLField';
 import { GPTCallType, gptAPICall, gptImageLabel } from '../../apis/gpt/GPT';
 import { DocUtils, FollowLinkScript } from '../../documents/DocUtils';
@@ -677,6 +677,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
             </div>
         </Tooltip>
     );
+    childFitWidth = () => Cast(this.Document.childLayoutFitWidth, 'boolean') ?? Cast(this.Document.childLayoutFitWidth, 'boolean');
+
     displayDoc = (whichSlot: string) => {
         const whichDoc = DocCast(this.dataDoc[whichSlot]);
         const targetDoc = DocCast(whichDoc?.annotationOn, whichDoc);
@@ -696,7 +698,7 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent<FieldViewProps>()
                     isDocumentActive={returnFalse}
                     isContentActive={this.childActiveFunc}
                     showTags={undefined}
-                    fitWidth={undefined} // set to returnTrue to make images fill the comparisonBox-- should be a user option
+                    fitWidth={this.childFitWidth} // set to returnTrue to make images fill the comparisonBox-- should be a user option
                     ignoreUsePath={layoutString ? true : undefined}
                     moveDocument={whichSlot === this.frontKey ? this.moveDocFront : this.moveDocBack}
                     removeDocument={whichSlot === this.frontKey ? this.remDocFront : this.remDocBack}
diff --git a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
index c69cd8e89..eb0431b85 100644
--- a/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
+++ b/src/client/views/nodes/MapBox/MapBoxInfoWindow.tsx
@@ -41,7 +41,6 @@
 //     };
 
 //     _stack: CollectionStackingView | CollectionNoteTakingView | null | undefined;
-//     childLayoutFitWidth = (doc: Doc) => doc.type === DocumentType.RTF;
 //     addDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.AddDocToList(this.props.place, 'data', d), true as boolean);
 //     removeDoc = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((p, d) => p && Doc.RemoveDocFromList(this.props.place, 'data', d), true as boolean);
 //     render() {
@@ -69,7 +68,6 @@
 //                             chromeHidden={true}
 //                             childHideResizeHandles={true}
 //                             childHideDecorationTitle={true}
-//                             childLayoutFitWidth={this.childLayoutFitWidth}
 //                             // childDocumentsActive={returnFalse}
 //                             removeDocument={this.removeDoc}
 //                             addDocument={this.addDoc}
diff --git a/src/client/views/nodes/chatbot/agentsystem/Agent.ts b/src/client/views/nodes/chatbot/agentsystem/Agent.ts
index 25e471ce8..13fdac7f5 100644
--- a/src/client/views/nodes/chatbot/agentsystem/Agent.ts
+++ b/src/client/views/nodes/chatbot/agentsystem/Agent.ts
@@ -54,7 +54,7 @@ export class Agent {
         history: () => string,
         csvData: () => { filename: string; id: string; text: string }[],
         addLinkedUrlDoc: (url: string, id: string) => void,
-        addLinkedDoc: (doc_type: supportedDocumentTypes, data: unknown, options: DocumentOptions, id: string) => void,
+        addLinkedDoc: (doc_type: supportedDocumentTypes, data: unknown, options: DocumentOptions, id?: string) => void,
         // eslint-disable-next-line @typescript-eslint/no-unused-vars
         createCSVInDash: (url: string, title: string, id: string, data: string) => void
     ) {
diff --git a/src/client/views/nodes/chatbot/tools/CreateDocumentTool.ts b/src/client/views/nodes/chatbot/tools/CreateDocumentTool.ts
index b919b242c..07b515fea 100644
--- a/src/client/views/nodes/chatbot/tools/CreateDocumentTool.ts
+++ b/src/client/views/nodes/chatbot/tools/CreateDocumentTool.ts
@@ -1,8 +1,8 @@
-import { v4 as uuidv4 } from 'uuid';
 import { BaseTool } from './BaseTool';
 import { Observation } from '../types/types';
 import { ParametersType } from '../types/tool_types';
 import { DocumentOptions } from '../../../../documents/Documents';
+import { OmitKeys } from '../../../../../ClientUtils';
 
 export enum supportedDocumentTypes {
     flashcard = 'flashcard',
@@ -360,9 +360,9 @@ type CreateListDocToolParamsType = typeof createListDocToolParams;
 
 // Tool class for creating documents
 export class CreateDocTool extends BaseTool<CreateListDocToolParamsType> {
-    private _addLinkedDoc: (doc_type: supportedDocumentTypes, data: unknown, options: DocumentOptions, id: string) => void;
+    private _addLinkedDoc: (doc_type: supportedDocumentTypes, data: unknown, options: DocumentOptions) => void;
 
-    constructor(addLinkedDoc: (doc_type: supportedDocumentTypes, data: unknown, options: DocumentOptions, id: string) => void) {
+    constructor(addLinkedDoc: (doc_type: supportedDocumentTypes, data: unknown, options: DocumentOptions) => void) {
         super(
             'createDoc',
             `Creates one or more documents that best fit the user’s request. 
@@ -395,24 +395,13 @@ export class CreateDocTool extends BaseTool<CreateListDocToolParamsType> {
     async execute(args: ParametersType<CreateListDocToolParamsType>): Promise<Observation[]> {
         try {
             const parsedDoc = JSON.parse(args.docs) as ({ doc_type: supportedDocumentTypes; data: unknown } & DocumentOptions)[];
-            parsedDoc.forEach(doc =>
+            parsedDoc.forEach(
+                doc =>
                 this._addLinkedDoc(
                     doc.doc_type,
                     doc.data,
-                    {
-                        title: doc.title,
-                        backgroundColor: doc.backgroundColor,
-                        text_fontColor: doc.text_fontColor,
-                        _width: doc._width,
-                        _height: doc._height,
-                        type_collection: doc.type_collection,
-                        _layout_fitWidth: false,
-                        _layout_autoHeight: true,
-                        x: doc.x,
-                        y: doc.y,
-                    },
-                    uuidv4()
-                )
+                    {...OmitKeys(doc, ["data", "doc_type"]).omit, _layout_fitWidth: false, _layout_autoHeight: true}
+                ) // prettier-ignore
             );
             return [{ type: 'text', text: 'Created document.' }];
         } catch (error) {
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 7c23ca8e1..3ce4dc6cb 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -2989,7 +2989,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 ignoreUnrendered
                                 childDragAction={dropActionType.move}
                                 setContentViewBox={emptyFunction}
-                                // childLayoutFitWidth={returnTrue}
                                 childOpacity={returnOne}
                                 childClickScript={PresBox.navigateToDocScript}
                                 childLayoutTemplate={this.childLayoutTemplate}
diff --git a/src/server/ApiManagers/AssistantManager.ts b/src/server/ApiManagers/AssistantManager.ts
index b53581aa2..86af756a4 100644
--- a/src/server/ApiManagers/AssistantManager.ts
+++ b/src/server/ApiManagers/AssistantManager.ts
@@ -9,7 +9,7 @@
  */
 
 import { Readability } from '@mozilla/readability';
-import axios, { AxiosResponse } from 'axios';
+import axios from 'axios';
 import { spawn } from 'child_process';
 import * as fs from 'fs';
 import { writeFile } from 'fs';
@@ -122,8 +122,6 @@ export default class AssistantManager extends ApiManager {
                 const { query, max_results } = req.body;
                 const MIN_VALID_RESULTS_RATIO = 0.75; // 3/4 threshold
                 let startIndex = 1; // Start at the first result initially
-                let validResults: any[] = [];
-
                 const fetchSearchResults = async (start: number) => {
                     return customsearch.cse.list({
                         q: query,
@@ -135,20 +133,27 @@ export default class AssistantManager extends ApiManager {
                     });
                 };
 
-                const filterResultsByXFrameOptions = async (results: any[]) => {
+                const filterResultsByXFrameOptions = async (
+                    results: {
+                        url: string | null | undefined;
+                        snippet: string | null | undefined;
+                    }[]
+                ) => {
                     const filteredResults = await Promise.all(
-                        results.map(async result => {
-                            try {
-                                const urlResponse: AxiosResponse = await axios.head(result.url, { timeout: 5000 });
-                                const xFrameOptions = urlResponse.headers['x-frame-options'];
-                                if (xFrameOptions && xFrameOptions.toUpperCase() === 'SAMEORIGIN') {
-                                    return result;
+                        results
+                            .filter(result => result.url)
+                            .map(async result => {
+                                try {
+                                    const urlResponse = await axios.head(result.url!, { timeout: 5000 });
+                                    const xFrameOptions = urlResponse.headers['x-frame-options'];
+                                    if (xFrameOptions && xFrameOptions.toUpperCase() === 'SAMEORIGIN') {
+                                        return result;
+                                    }
+                                } catch (error) {
+                                    console.error(`Error checking x-frame-options for URL: ${result.url}`, error);
                                 }
-                            } catch (error) {
-                                console.error(`Error checking x-frame-options for URL: ${result.url}`, error);
-                            }
-                            return null; // Exclude the result if it doesn't match
-                        })
+                                return null; // Exclude the result if it doesn't match
+                            })
                     );
                     return filteredResults.filter(result => result !== null); // Remove null results
                 };
@@ -163,7 +168,7 @@ export default class AssistantManager extends ApiManager {
                         })) || [];
 
                     // Filter the initial results
-                    validResults = await filterResultsByXFrameOptions(initialResults);
+                    let validResults = await filterResultsByXFrameOptions(initialResults);
 
                     // If valid results are less than 3/4 of max_results, fetch more results
                     while (validResults.length < max_results * MIN_VALID_RESULTS_RATIO) {
-- 
cgit v1.2.3-70-g09d2


From 1023d79c64b7146df180cc07c474e688377a63fe Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 10 Feb 2025 12:51:59 -0500
Subject: added dictation buttons to chatbox

---
 src/client/util/DictationManager.ts                |  5 --
 src/client/views/DictationButton.tsx               | 57 ++++++++++++++++++++++
 src/client/views/DictationOverlay.tsx              |  1 -
 .../nodes/chatbot/chatboxcomponents/ChatBox.tsx    | 31 ++++++++++--
 src/client/views/nodes/trails/PresBox.tsx          | 53 ++++++--------------
 5 files changed, 101 insertions(+), 46 deletions(-)
 create mode 100644 src/client/views/DictationButton.tsx

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

diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index 831afe538..897366757 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -71,7 +71,6 @@ export namespace DictationManager {
         let current: string | undefined;
         let sessionResults: string[] = [];
 
-        // eslint-disable-next-line new-cap
         const recognizer: Opt<SpeechRecognition> = webkitSpeechRecognition ? new webkitSpeechRecognition() : undefined;
 
         export type InterimResultHandler = (results: string) => void;
@@ -257,7 +256,6 @@ export namespace DictationManager {
                 if (entry) {
                     let success = false;
                     const { restrictTo } = entry;
-                    // eslint-disable-next-line no-restricted-syntax
                     for (const target of targets) {
                         if (!restrictTo || validate(target, restrictTo)) {
                             // eslint-disable-next-line no-await-in-loop
@@ -268,7 +266,6 @@ export namespace DictationManager {
                     return success;
                 }
 
-                // eslint-disable-next-line no-restricted-syntax
                 for (const depEntry of Dependent) {
                     const regex = depEntry.expression;
                     const matches = regex.exec(phrase);
@@ -276,7 +273,6 @@ export namespace DictationManager {
                     if (matches !== null) {
                         let success = false;
                         const { restrictTo } = depEntry;
-                        // eslint-disable-next-line no-restricted-syntax
                         for (const target of targets) {
                             if (!restrictTo || validate(target, restrictTo)) {
                                 // eslint-disable-next-line no-await-in-loop
@@ -307,7 +303,6 @@ export namespace DictationManager {
         };
 
         const validate = (target: DocumentView, types: DocumentType[]) => {
-            // eslint-disable-next-line no-restricted-syntax
             for (const type of types) {
                 if (tryCast(target, type)) {
                     return true;
diff --git a/src/client/views/DictationButton.tsx b/src/client/views/DictationButton.tsx
new file mode 100644
index 000000000..0ce586df4
--- /dev/null
+++ b/src/client/views/DictationButton.tsx
@@ -0,0 +1,57 @@
+import { IconButton, Type } from '@dash/components';
+import { action, makeObservable, observable } from 'mobx';
+import { observer } from 'mobx-react';
+import * as React from 'react';
+import { BiMicrophone } from 'react-icons/bi';
+import { DictationManager } from '../util/DictationManager';
+import { SnappingManager } from '../util/SnappingManager';
+
+export interface DictationButtonProps {
+    setInput: (val: string) => void;
+    inputRef?: HTMLInputElement | null | undefined;
+}
+@observer
+export class DictationButton extends React.Component<DictationButtonProps> {
+    @observable private _isRecording = false;
+    constructor(props: DictationButtonProps) {
+        super(props);
+        makeObservable(this);
+    }
+
+    stopDictation = action(() => {
+        this._isRecording = false;
+        DictationManager.Controls.stop();
+    });
+
+    render() {
+        return (
+            <IconButton
+                type={Type.TERT}
+                color={this._isRecording ? '#2bcaff' : SnappingManager.userVariantColor}
+                tooltip="Record"
+                icon={<BiMicrophone size="16px" />}
+                onClick={action(() => {
+                    if (!this._isRecording) {
+                        this._isRecording = true;
+                        DictationManager.Controls.listen({
+                            interimHandler: (value: string) => {
+                                this.props.setInput(value);
+                                if (this.props.inputRef) {
+                                    this.props.inputRef.focus();
+                                    this.props.inputRef.scrollLeft = 1000000;
+                                }
+                            },
+                            continuous: { indefinite: false },
+                        }).then(results => {
+                            if (results && [DictationManager.Controls.Infringed].includes(results)) {
+                                DictationManager.Controls.stop();
+                            }
+                        });
+                    } else {
+                        this.stopDictation();
+                    }
+                })}
+            />
+        );
+    }
+}
diff --git a/src/client/views/DictationOverlay.tsx b/src/client/views/DictationOverlay.tsx
index e33049d3b..66831ec7f 100644
--- a/src/client/views/DictationOverlay.tsx
+++ b/src/client/views/DictationOverlay.tsx
@@ -14,7 +14,6 @@ export class DictationOverlay extends React.Component {
     @observable private _dictationDisplayState = false;
     @observable private _dictationListeningState: DictationManager.Controls.ListeningUIStatus = false;
 
-    // eslint-disable-next-line react/no-unused-class-component-methods
     public hasActiveModal = false;
 
     constructor(props: object) {
diff --git a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
index 810e1793b..4453fe140 100644
--- a/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
+++ b/src/client/views/nodes/chatbot/chatboxcomponents/ChatBox.tsx
@@ -22,17 +22,18 @@ import { CollectionViewType, DocumentType } from '../../../../documents/Document
 import { Docs, DocumentOptions } from '../../../../documents/Documents';
 import { DocumentManager } from '../../../../util/DocumentManager';
 import { LinkManager } from '../../../../util/LinkManager';
+import { DictationButton } from '../../../DictationButton';
 import { ViewBoxAnnotatableComponent } from '../../../DocComponent';
 import { DocumentView } from '../../DocumentView';
 import { FieldView, FieldViewProps } from '../../FieldView';
 import { PDFBox } from '../../PDFBox';
 import { Agent } from '../agentsystem/Agent';
+import { supportedDocumentTypes } from '../tools/CreateDocumentTool';
 import { ASSISTANT_ROLE, AssistantMessage, CHUNK_TYPE, Citation, ProcessingInfo, SimplifiedChunk, TEXT_TYPE } from '../types/types';
 import { Vectorstore } from '../vectorstore/Vectorstore';
 import './ChatBox.scss';
 import MessageComponentBox from './MessageComponent';
 import { ProgressBar } from './ProgressBar';
-import { supportedDocumentTypes } from '../tools/CreateDocumentTool';
 
 dotenv.config();
 
@@ -64,6 +65,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
     private vectorstore: Vectorstore;
     private agent: Agent;
     private messagesRef: React.RefObject<HTMLDivElement>;
+    private _textInputRef: HTMLInputElement | undefined | null;
 
     /**
      * Static method that returns the layout string for the field.
@@ -73,6 +75,10 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         return FieldView.LayoutString(ChatBox, fieldKey);
     }
 
+    setChatInput = action((input: string) => {
+        this._inputValue = input;
+    });
+
     /**
      * Constructor initializes the component, sets up OpenAI, vector store, and agent instances,
      * and observes changes in the chat history to save the state in dataDoc.
@@ -820,6 +826,7 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
         this._inputValue = question;
     };
 
+    _dictation: DictationButton | null = null;
     /**
      * Renders the chat interface, including the message list, input field, and other UI elements.
      */
@@ -847,8 +854,19 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                 </div>
 
                 <form onSubmit={this.askGPT} className="chat-input">
-                    <input type="text" name="messageInput" autoComplete="off" placeholder="Type your message here..." value={this._inputValue} onChange={action(e => (this._inputValue = e.target.value))} disabled={this._isLoading} />
-                    <button className="submit-button" type="submit" disabled={this._isLoading || !this._inputValue.trim()}>
+                    <input
+                        ref={r => {
+                            this._textInputRef = r;
+                        }}
+                        type="text"
+                        name="messageInput"
+                        autoComplete="off"
+                        placeholder="Type your message here..."
+                        value={this._inputValue}
+                        onChange={action(e => (this._inputValue = e.target.value))}
+                        disabled={this._isLoading}
+                    />
+                    <button className="submit-button" onClick={() => this._dictation?.stopDictation()} type="submit" disabled={this._isLoading || !this._inputValue.trim()}>
                         {this._isLoading ? (
                             <div className="spinner"></div>
                         ) : (
@@ -858,6 +876,13 @@ export class ChatBox extends ViewBoxAnnotatableComponent<FieldViewProps>() {
                             </svg>
                         )}
                     </button>
+                    <DictationButton
+                        ref={r => {
+                            this._dictation = r;
+                        }}
+                        setInput={this.setChatInput}
+                        inputRef={this._textInputRef}
+                    />
                 </form>
                 {/* Popup for citation */}
                 {this._citationPopup.visible && (
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index 3ce4dc6cb..b98a7f96e 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -7,7 +7,6 @@ import { IReactionDisposer, ObservableSet, action, computed, makeObservable, obs
 import { observer } from 'mobx-react';
 import * as React from 'react';
 import { AiOutlineSend } from 'react-icons/ai';
-import { BiMicrophone } from 'react-icons/bi';
 import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp } from 'react-icons/fa';
 import ReactLoading from 'react-loading';
 import ReactTextareaAutosize from 'react-textarea-autosize';
@@ -26,12 +25,12 @@ import { DocServer } from '../../../DocServer';
 import { getSlideTransitionSuggestions, gptSlideProperties, gptTrailSlideCustomization } from '../../../apis/gpt/PresCustomization';
 import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
 import { Docs } from '../../../documents/Documents';
-import { DictationManager } from '../../../util/DictationManager';
 import { dropActionType } from '../../../util/DropActionTypes';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 import { SerializationHelper } from '../../../util/SerializationHelper';
 import { SnappingManager } from '../../../util/SnappingManager';
 import { UndoManager, undoBatch, undoable } from '../../../util/UndoManager';
+import { DictationButton } from '../../DictationButton';
 import { ViewBoxBaseComponent } from '../../DocComponent';
 import { pinDataTypes as dataTypes } from '../../PinFuncs';
 import { CollectionView } from '../../collections/CollectionView';
@@ -79,6 +78,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed.  Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation.
     _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things
     _presTimer: NodeJS.Timeout | undefined;
+    _animationDictation: DictationButton | null = null;
+    _slideDictation: DictationButton | null = null;
 
     // eslint-disable-next-line no-use-before-define
     @observable public static Instance: PresBox;
@@ -108,7 +109,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _chatActive: boolean = false;
     @observable _animationChat: string = '';
     @observable _chatInput: string = '';
-    @observable _isRecording: boolean = false;
     @observable _isLoading: boolean = false;
 
     @observable generatedAnimations: AnimationSettings[] = [
@@ -147,7 +147,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     setChatInput =              action((input: string) => { this._chatInput = input; }); // prettier-ignore
     setAnimationChat =          action((input: string) => { this._animationChat = input; }); // prettier-ignore
     setIsLoading =              action((input?: boolean) => { this._isLoading = !!input; }); // prettier-ignore
-    setIsRecording =            action((input: boolean) => { this._isRecording = input; }); // prettier-ignore
     setShowAIGalleryVisibilty = action((visible: boolean) => { this._showAIGallery = visible; }); // prettier-ignore
     setBezierControlPoints = action((newPoints: { p1: number[]; p2: number[] }) => {
         this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
@@ -280,24 +279,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         }
     };
 
-    // Recording for GPT customization
-    recordDictation = () => {
-        this.setIsRecording(true);
-        this.setChatInput('');
-        DictationManager.Controls.listen({
-            interimHandler: this.setDictationContent,
-            continuous: { indefinite: false },
-        }).then(results => {
-            if (results && [DictationManager.Controls.Infringed].includes(results)) {
-                DictationManager.Controls.stop();
-            }
-        });
-    };
-    stopDictation = () => {
-        this.setIsRecording(false);
-        DictationManager.Controls.stop();
-    };
-
     setDictationContent = (value: string) => this.setChatInput(value);
 
     customizeAnimations = action(() => {
@@ -310,7 +291,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     customizeWithGPT = action((input: string) => {
         // const testInput = 'change title to Customized Slide, transition for 2.3s with fade in effect';
-        this.setIsRecording(false);
         this.setIsLoading(true);
         const slideDefaults: { [key: string]: FieldResult } = { presentation_transition: 500, config_zoom: 1 };
         const currSlideProperties = gptSlideProperties.reduce(
@@ -1785,7 +1765,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 this.setAnimationChat(e.target.value);
                             }}
                             onKeyDown={e => {
-                                this.stopDictation();
+                                this._animationDictation?.stopDictation();
                                 e.stopPropagation();
                             }}
                         />
@@ -1799,6 +1779,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         color={SnappingManager.userVariantColor}
                         onClick={this.customizeAnimations}
                     />
+                    <DictationButton
+                        ref={r => {
+                            this._animationDictation = r;
+                        }}
+                        setInput={this.setAnimationChat}
+                    />
                 </div>
                 <div style={{ alignItems: 'center' }}>
                     Click a box to use the effect.
@@ -1878,22 +1864,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                         this.setChatInput(e.target.value);
                                     }}
                                     onKeyDown={e => {
-                                        this.stopDictation();
+                                        this._slideDictation?.stopDictation();
                                         e.stopPropagation();
                                     }}
                                 />
-                                <IconButton
-                                    type={Type.TERT}
-                                    color={this._isRecording ? '#2bcaff' : SnappingManager.userVariantColor}
-                                    tooltip="Record"
-                                    icon={<BiMicrophone size="16px" />}
-                                    onClick={() => {
-                                        if (!this._isRecording) {
-                                            this.recordDictation();
-                                        } else {
-                                            this.stopDictation();
-                                        }
+                                <DictationButton
+                                    ref={r => {
+                                        this._slideDictation = r;
                                     }}
+                                    setInput={this.setChatInput}
                                 />
                             </div>
                             <Button
@@ -1904,7 +1883,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 iconPlacement="right"
                                 color={SnappingManager.userVariantColor}
                                 onClick={() => {
-                                    this.stopDictation();
+                                    this._slideDictation?.stopDictation();
                                     this.customizeWithGPT(this._chatInput);
                                 }}
                             />
-- 
cgit v1.2.3-70-g09d2


From 3956e91fe6d2f649a4bf0e557cbaba253dc177b7 Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Mon, 10 Feb 2025 15:11:47 -0500
Subject: fixed presbox numerical text input to be editable

---
 src/client/views/nodes/trails/PresBox.tsx | 17 +++++++++--------
 1 file changed, 9 insertions(+), 8 deletions(-)

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

diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx
index b98a7f96e..9ab5fb1bd 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -1604,7 +1604,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     </div>
                                 </div>
                                 <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
-                                    <input className="presBox-inputNumber" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateDurationTime(e.target.value))} />
+                                    <input className="presBox-inputNumber" type="number" value={duration} onChange={action(e => this.updateDurationTime(e.target.value))} />
                                     <span>s</span>
                                 </div>
                             </div>
@@ -1893,8 +1893,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     {/* Movement */}
                     <div
                         className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`}
-                        onPointerDown={StopEvent}
-                        onPointerUp={StopEvent}
+                        onPointerDown={e => e.stopPropagation()}
+                        onPointerUp={e => e.stopPropagation()}
                         onClick={action(e => {
                             e.stopPropagation();
                             this._openMovementDropdown = false;
@@ -1914,7 +1914,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     </div>
                                 </div>
                                 <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
-                                    <input className="presBox-inputNumber" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} />
+                                    <input className="presBox-inputNumber" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} />
                                     <span>s</span>
                                 </div>
                             </div>
@@ -1935,7 +1935,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                 </Tooltip>
                                 <div className="presBox-subheading-slider">{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}</div>
                                 <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}>
-                                    <input className="presBox-inputNumber" readOnly type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />
+                                    <input className="presBox-inputNumber" type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />
                                     <span>%</span>
                                 </div>
                             </div>
@@ -1957,7 +1957,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     </div>
 
                     {/* Cubic bezier editor */}
-                    {this.showEaseFunctions && StrCast(activeItem.presentation_easeFunc).includes('cubic-bezier') && (
+                    {!this.showEaseFunctions || !StrCast(activeItem.presentation_easeFunc).includes('cubic-bezier') ? null : (
                         <div className="presBox-option-block" style={{ paddingTop: 0, alignItems: 'center' }}>
                             <CubicBezierEditor setFunc={this.setBezierControlPoints} currPoints={this.currCPoints} />
                         </div>
@@ -1965,8 +1965,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
                     <div
                         className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`}
-                        onPointerDown={StopEvent}
-                        onPointerUp={StopEvent}
+                        onPointerDown={e => e.stopPropagation()}
+                        onPointerUp={e => e.stopPropagation()}
                         onClick={action(e => {
                             e.stopPropagation();
                             this._openMovementDropdown = false;
@@ -2004,6 +2004,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                                     MORE
                                 </div>
                             </div>
+
                             {this.aiEffects}
                             <div className="presBox-gpt-chat">
                                 {/* Effect direction */}
-- 
cgit v1.2.3-70-g09d2

