From 071aa191d37f2805bd717c1d1caf8b2a57c394bc Mon Sep 17 00:00:00 2001
From: bobzel <zzzman@gmail.com>
Date: Wed, 8 Mar 2023 12:33:16 -0500
Subject: added progressive disclosure for collections.

---
 src/client/views/nodes/trails/PresBox.tsx | 170 +++++++++++++++++++++++++-----
 1 file changed, 145 insertions(+), 25 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 cb8b065af..b9397a78a 100644
--- a/src/client/views/nodes/trails/PresBox.tsx
+++ b/src/client/views/nodes/trails/PresBox.tsx
@@ -4,7 +4,7 @@ import { Tooltip } from '@material-ui/core';
 import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx';
 import { observer } from 'mobx-react';
 import { ColorState, SketchPicker } from 'react-color';
-import { AnimationSym, Doc, DocListCast, Field, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
+import { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc';
 import { Copy, Id } from '../../../../fields/FieldSymbols';
 import { InkField, InkTool } from '../../../../fields/InkField';
 import { List } from '../../../../fields/List';
@@ -19,6 +19,7 @@ import { CollectionViewType, DocumentType } from '../../../documents/DocumentTyp
 import { DocumentManager } from '../../../util/DocumentManager';
 import { ScriptingGlobals } from '../../../util/ScriptingGlobals';
 import { SelectionManager } from '../../../util/SelectionManager';
+import { SerializationHelper } from '../../../util/SerializationHelper';
 import { SettingsManager } from '../../../util/SettingsManager';
 import { undoBatch, UndoManager } from '../../../util/UndoManager';
 import { CollectionDockingView } from '../../collections/CollectionDockingView';
@@ -34,7 +35,6 @@ import { FieldView, FieldViewProps } from '../FieldView';
 import { ScriptingBox } from '../ScriptingBox';
 import './PresBox.scss';
 import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
-import { SerializationHelper } from '../../../util/SerializationHelper';
 const { Howl } = require('howler');
 
 export interface pinDataTypes {
@@ -87,6 +87,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @observable _newDocumentTools: boolean = false;
     @observable _openMovementDropdown: boolean = false;
     @observable _openEffectDropdown: boolean = false;
+    @observable _openBulletEffectDropdown: boolean = false;
     @observable _presentTools: boolean = false;
     @observable _treeViewMap: Map<Doc, number> = new Map();
     @observable _presKeyEvents: boolean = false;
@@ -115,6 +116,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get targetDoc() {
         return Cast(this.activeItem?.presentationTargetDoc, Doc, null);
     }
+    public static targetRenderedDoc = (doc: Doc) => {
+        const targetDoc = Cast(doc?.presentationTargetDoc, Doc, null);
+        return targetDoc?.unrendered ? DocCast(targetDoc.annotationOn) : targetDoc;
+    };
     @computed get scrollable() {
         if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._viewType === CollectionViewType.Stacking) return true;
         return false;
@@ -185,6 +190,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             () => SelectionManager.Views(),
             views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation()
         );
+        this._disposers.editing = reaction(
+            () => this.layoutDoc.presStatus === PresStatus.Edit,
+            editing => {
+                if (editing) {
+                    this.childDocs.forEach(doc => {
+                        if (doc.presIndexed !== undefined) {
+                            this.presIndexedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined));
+                            doc.presIndexed = Math.min(this.presIndexedItems(doc)?.length ?? 0, 1);
+                        }
+                    });
+                }
+            }
+        );
     }
 
     @action
@@ -243,18 +261,58 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         doGroupWithUp(curSlideInd, true)();
     };
 
+    presIndexedItems = (doc: Doc) => {
+        const targetList = PresBox.targetRenderedDoc(doc);
+        if (doc.presIndexed !== undefined && targetList) {
+            const listItems = DocListCast(targetList[Doc.LayoutFieldKey(targetList)]);
+            return listItems;
+        }
+    };
     // Called when the user activates 'next' - to move to the next part of the pres. trail
     @action
     next = () => {
+        const progressiveReveal = (first: boolean) => {
+            const presIndexed = Cast(this.activeItem?.presIndexed, '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.presIndexedItems(this.activeItem);
+                if (listItems && presIndexed < listItems.length) {
+                    if (!first) {
+                        const listItemDoc = listItems[presIndexed];
+                        const targetView = listItems && DocumentManager.Instance.getFirstDocumentView(listItemDoc);
+                        Doc.linkFollowUnhighlight();
+                        Doc.HighlightDoc(listItemDoc);
+                        listItemDoc.presEffect = this.activeItem.presBulletEffect;
+                        listItemDoc.presTransition = 500;
+                        targetView?.setAnimEffect(listItemDoc, 500);
+                        if (targetView?.docView && this.activeItem.presBulletExpand) {
+                            targetView.docView._animateScalingTo = 1.1;
+                            Doc.AddUnHighlightWatcher(() => (targetView!.docView!._animateScalingTo = 0));
+                        }
+                        listItemDoc.opacity = undefined;
+                        this.activeItem.presIndexed = presIndexed + 1;
+                    }
+                    return true;
+                } else {
+                    console.log(this.activeItem.presIndexed);
+                }
+            }
+        };
+        if (progressiveReveal(false)) return true;
         if (this.childDocs[this.itemIndex + 1] !== undefined) {
             // Case 1: No more frames in current doc and next slide is defined, therefore move to next slide
             const slides = DocListCast(this.rootDoc[StrCast(this.presFieldKey, 'data')]);
             const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex;
             this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1);
+            progressiveReveal(true); // shows first progressive document, but without a transition effect
         } else {
             if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) {
                 // Case 2: Last slide and presLoop is toggled ON or it is in Edit mode
                 this.nextSlide(0);
+                progressiveReveal(true); // shows first progressive document, but without a transition effect
             }
             return 0;
         }
@@ -492,7 +550,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                 });
             setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
         }
-        if (pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPanX !== undefined || activeItem.presViewScale !== undefined))) {
+        if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.presPinViewBounds !== undefined || activeItem.presPanX !== undefined || activeItem.presViewScale !== undefined))) && !PresBox.targetRenderedDoc(activeItem)._isGroup) {
             const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number'));
             if (contentBounds) {
                 const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] };
@@ -685,7 +743,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     onHideDocument = () => {
         this.childDocs.forEach((doc, index) => {
             const curDoc = Cast(doc, Doc, null);
-            const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null);
+            const tagDoc = PresBox.targetRenderedDoc(curDoc);
             const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc);
             if (curDoc.presHide) {
                 if (index !== this.itemIndex) {
@@ -698,6 +756,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     tagDoc.opacity = 0;
                 } else if (index === this.itemIndex || !curDoc.presHideAfter) {
                     tagDoc.opacity = 1;
+                    setTimeout(() => (tagDoc._dataTransition = undefined), 1000);
                 }
             }
             const hidingIndAft =
@@ -781,7 +840,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     //stops the presentaton.
     resetPresentation = () => {
         this.childDocs
-            .map(doc => Cast(doc.presentationTargetDoc, Doc, null))
+            .map(doc => PresBox.targetRenderedDoc(doc))
             .filter(doc => doc instanceof Doc)
             .forEach(doc => {
                 try {
@@ -807,6 +866,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         });
     };
 
+    initializePresState = (startIndex: number) => {
+        this.childDocs.forEach((doc, index) => {
+            const tagDoc = PresBox.targetRenderedDoc(doc);
+            if (doc.presHideBefore && index > startIndex) tagDoc.opacity = 0;
+            if (doc.presHideAfter && index < startIndex) tagDoc.opacity = 0;
+            if (doc.presIndexed !== undefined && index >= startIndex) {
+                this.presIndexedItems(doc)
+                    ?.slice(1)
+                    .forEach(indexedDoc => (indexedDoc.opacity = 0));
+                doc.presIndexed = Math.min(this.presIndexedItems(doc)?.length ?? 0, 1);
+            }
+            // if (doc.presHide && this.childDocs.indexOf(doc) === startIndex)  tagDoc.opacity = 0;
+        });
+    };
+
     /**
      * The function that starts the presentation at the given index, also checking if actions should be applied
      * directly at start.
@@ -818,12 +892,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         clearTimeout(this._presTimer);
         if (this.childDocs.length) {
             this.layoutDoc.presStatus = PresStatus.Autoplay;
-            this.childDocs.forEach(doc => {
-                const tagDoc = doc.presentationTargetDoc as Doc;
-                if (doc.presHideBefore && this.childDocs.indexOf(doc) > startIndex) tagDoc.opacity = 0;
-                if (doc.presHideAfter && this.childDocs.indexOf(doc) < startIndex) tagDoc.opacity = 0;
-                // if (doc.presHide && this.childDocs.indexOf(doc) === startIndex)  tagDoc.opacity = 0;
-            });
+            this.initializePresState(startIndex);
             const func = () => {
                 const delay = NumCast(this.activeItem.presDuration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presTransition);
                 this._presTimer = setTimeout(() => {
@@ -860,6 +929,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         doc._height = 30;
         doc._width = PresBox.minimizedWidth;
         Doc.AddDocToList(Doc.MyOverlayDocs, undefined, doc);
+        PresBox.Instance.initializePresState(PresBox.Instance.itemIndex);
         return (doc.presStatus = PresStatus.Manual);
     }
 
@@ -1336,7 +1406,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
 
     @undoBatch
     @action
-    updateEffect = (effect: PresEffect, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffect = effect));
+    updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presEffect = effect)));
 
     _batch: UndoManager.Batch | undefined = undefined;
 
@@ -1366,8 +1436,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @computed get transitionDropdown() {
         const activeItem: Doc = this.activeItem;
         const targetDoc: Doc = this.targetDoc;
-        const presEffect = (effect: PresEffect) => (
-            <div className={`presBox-dropdownOption ${activeItem.presEffect === effect || (effect === PresEffect.None && !activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect)}>
+        const presEffect = (effect: PresEffect, bullet: boolean) => (
+            <div className={`presBox-dropdownOption ${activeItem.presEffect === effect || (effect === PresEffect.None && !activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect, bullet)}>
                 {effect}
             </div>
         );
@@ -1395,6 +1465,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
             let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 0;
             if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration);
             const effect = activeItem.presEffect ? activeItem.presEffect : PresMovement.None;
+            const bulletEffect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None;
             // activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : PresMovement.Zoom;
             return (
                 <div
@@ -1405,6 +1476,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         e.stopPropagation();
                         this._openMovementDropdown = false;
                         this._openEffectDropdown = false;
+                        this._openBulletEffectDropdown = false;
                     })}>
                     <div className="ribbon-box">
                         Movement
@@ -1537,12 +1609,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             {effect?.toString()}
                             <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
                             <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this._openEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}>
-                                {presEffect(PresEffect.None)}
-                                {presEffect(PresEffect.Fade)}
-                                {presEffect(PresEffect.Flip)}
-                                {presEffect(PresEffect.Rotate)}
-                                {presEffect(PresEffect.Bounce)}
-                                {presEffect(PresEffect.Roll)}
+                                {presEffect(PresEffect.None, false)}
+                                {presEffect(PresEffect.Fade, false)}
+                                {presEffect(PresEffect.Flip, false)}
+                                {presEffect(PresEffect.Rotate, false)}
+                                {presEffect(PresEffect.Bounce, false)}
+                                {presEffect(PresEffect.Roll, false)}
                             </div>
                         </div>
                         <div className="ribbon-doubleButton" style={{ display: effect === PresEffectDirection.None ? 'none' : 'inline-flex' }}>
@@ -1556,6 +1628,49 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             {presDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})}
                             {presDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })}
                         </div>
+                        <div className="ribbon-Box">Progressive Disclosure</div>
+                        <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
+                            <div className="presBox-subheading">Progressivize Collection</div>
+                            <input
+                                className="presBox-checkbox"
+                                style={{ margin: 10 }}
+                                type="checkbox"
+                                onChange={() => {
+                                    activeItem.presIndexed = activeItem.presIndexed === undefined ? 0 : undefined;
+                                    activeItem.presHideBefore = activeItem.presIndexed !== undefined;
+                                }}
+                                checked={Cast(activeItem.presIndexed, 'number', null) !== undefined ? true : false}
+                            />
+                        </div>
+                        {activeItem.presIndexed === undefined ? null : (
+                            <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}>
+                                <div className="presBox-subheading">Expand Current Bullet</div>
+                                <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)} checked={BoolCast(activeItem.presBulletExpand)} />
+                            </div>
+                        )}
+                        {activeItem.presIndexed === undefined ? null : (
+                            <div className="presBox-subheading">
+                                Progressive effect
+                                <div
+                                    className="presBox-dropdown"
+                                    onClick={action(e => {
+                                        e.stopPropagation();
+                                        this._openBulletEffectDropdown = !this._openBulletEffectDropdown;
+                                    })}
+                                    style={{ borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, border: this._openBulletEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}>
+                                    {bulletEffect?.toString()}
+                                    <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} />
+                                    <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}>
+                                        {presEffect(PresEffect.None, true)}
+                                        {presEffect(PresEffect.Fade, true)}
+                                        {presEffect(PresEffect.Flip, true)}
+                                        {presEffect(PresEffect.Rotate, true)}
+                                        {presEffect(PresEffect.Bounce, true)}
+                                        {presEffect(PresEffect.Roll, true)}
+                                    </div>
+                                </div>
+                            </div>
+                        )}
                     </div>
                     <div className="ribbon-final-box">
                         <div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.childDocs)}>
@@ -1571,7 +1686,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     @action
     applyTo = (array: Doc[]) => {
         this.updateMovement(this.activeItem.presMovement as PresMovement, true);
-        this.updateEffect(this.activeItem.presEffect as PresEffect, true);
+        this.updateEffect(this.activeItem.presEffect as PresEffect, false, true);
+        this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
         this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffectDirection, true);
         const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem;
         array.forEach(curDoc => {
@@ -1952,6 +2068,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     onClick={undoBatch(
                         action(() => {
                             this.layoutDoc.presStatus = 'manual';
+                            this.initializePresState(this.itemIndex);
                             this.turnOffEdit(true);
                             this.gotoDocument(this.itemIndex, this.activeItem);
                         })
@@ -2114,6 +2231,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                             onClick={undoBatch(() => {
                                 if (this.childDocs.length) {
                                     this.layoutDoc.presStatus = 'manual';
+                                    this.initializePresState(this.itemIndex);
                                     this.gotoDocument(this.itemIndex, this.activeItem);
                                 }
                             })}>
@@ -2138,7 +2256,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
     }
 
     @computed get playButtons() {
-        const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1;
+        const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.presIndexedItems(this.activeItem)?.length ?? 0));
         const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
         const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc);
         // Case 1: There are still other frames and should go through all frames before going to next slide
@@ -2226,7 +2344,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                     </div>
                 </Tooltip>
                 <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}>
-                    {`${inOverlay ? '' : 'Slide'} ${this.itemIndex + 1} / ${this.childDocs.length}`}
+                    {inOverlay ? '' : 'Slide'} {this.itemIndex + 1}
+                    {this.activeItem.presIndexed !== undefined ? `.${this.activeItem.presIndexed}/${this.presIndexedItems(this.activeItem)?.length}` : ''} / {this.childDocs.length}
                 </div>
                 <div className="presPanel-divider"></div>
                 {this.props.PanelWidth() > 250 ? (
@@ -2311,7 +2430,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
         // needed to ensure that the childDocs are loaded for looking up fields
         this.childDocs.slice();
         const mode = StrCast(this.rootDoc._viewType) as CollectionViewType;
-        const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1;
+        const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.presIndexedItems(this.activeItem)?.length ?? 0));
         const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
         const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc);
         return this.props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player
@@ -2346,7 +2465,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
                         </div>
                     </Tooltip>
                     <div className="presPanel-button-text">
-                        Slide {this.itemIndex + 1} / {this.childDocs.length}
+                        Slide {this.itemIndex + 1}
+                        {this.activeItem.presIndexed !== undefined ? `.${this.activeItem.presIndexed}/${this.presIndexedItems(this.activeItem)?.length}` : ''} / {this.childDocs.length}
                     </div>
                     <div className="presPanel-divider" />
                     <div className="presPanel-button-text" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}>
-- 
cgit v1.2.3-70-g09d2

