import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { Tooltip } from '@mui/material';
import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { Doc, DocListCast, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc';
import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols';
import { Copy, Id } from '../../../../fields/FieldSymbols';
import { InkField } from '../../../../fields/InkField';
import { List } from '../../../../fields/List';
import { ObjectField } from '../../../../fields/ObjectField';
import { listSpec } from '../../../../fields/Schema';
import { ComputedField, ScriptField } from '../../../../fields/ScriptField';
import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types';
import { AudioField } from '../../../../fields/URLField';
import { emptyFunction, emptyPath, lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent, stringHash } from '../../../../Utils';
import { DocServer } from '../../../DocServer';
import { Docs } from '../../../documents/Documents';
import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes';
import { DocumentManager } from '../../../util/DocumentManager';
import { dropActionType } from '../../../util/DragManager';
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';
import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm';
import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline';
import { CollectionView } from '../../collections/CollectionView';
import { TreeView } from '../../collections/TreeView';
import { ViewBoxBaseComponent, ViewBoxInterface } from '../../DocComponent';
import { Colors } from '../../global/globalEnums';
import { LightboxView } from '../../LightboxView';
import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView';
import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView';
import { ScriptingBox } from '../ScriptingBox';
import './PresBox.scss';
import ReactLoading from 'react-loading';
import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums';
import ReactTextareaAutosize from 'react-textarea-autosize';
import { Button, Dropdown, DropdownType, IconButton, Toggle, ToggleType, Type } from 'browndash-components';
import { BiMicrophone } from 'react-icons/bi';
import { AiOutlineSend } from 'react-icons/ai';
import { getSlideTransitionSuggestions, gptSlideProperties, gptTrailSlideCustomization } from '../../../apis/gpt/customization';
import { DictationManager } from '../../../util/DictationManager';
import CubicBezierEditor, { TIMING_DEFAULT_MAPPINGS } from './CubicBezierEditor';
import Slider from '@mui/material/Slider';
import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp, FaCompressArrowsAlt } from 'react-icons/fa';
import { effectTimings, SpringType, springMappings, effectItems, easeItems, movementItems, SpringSettings, presEffectDefaultTimings, AnimationSettings, springPreviewColors } from './SpringUtils';
import SlideEffect from './SlideEffect';
import { IoMdInformationCircleOutline } from 'react-icons/io';

export interface pinDataTypes {
    scrollable?: boolean;
    dataviz?: number[];
    pannable?: boolean;
    type_collection?: boolean;
    inkable?: boolean;
    filters?: boolean;
    pivot?: boolean;
    temporal?: boolean;
    clippable?: boolean;
    datarange?: boolean;
    dataview?: boolean;
    poslayoutview?: boolean;
    dataannos?: boolean;
    map?: boolean;
}
export interface PinProps {
    audioRange?: boolean;
    activeFrame?: number;
    currentFrame?: number;
    hidePresBox?: boolean;
    pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected)
    pinDocLayout?: boolean; // pin layout info (width/height/x/y)
    pinAudioPlay?: boolean; // pin audio annotation
    pinData?: pinDataTypes;
}

@observer
export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() {
    public static LayoutString(fieldKey: string) {
        return FieldView.LayoutString(PresBox, fieldKey);
    }
    static navigateToDocScript: ScriptField;

    constructor(props: FieldViewProps) {
        super(props);
        makeObservable(this);
        if (!PresBox.navigateToDocScript) {
            PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, self)')!;
        }
    }

    private _disposers: { [name: string]: IReactionDisposer } = {};
    public selectedArray = new ObservableSet<Doc>();
    _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
    _presTimer: NodeJS.Timeout | undefined;

    @observable public static Instance: PresBox;

    @observable _isChildActive = false;
    @observable _moveOnFromAudio: boolean = true;

    @observable _eleArray: HTMLElement[] = [];
    @observable _dragArray: HTMLElement[] = [];
    @observable _pathBoolean: boolean = false;
    @observable _expandBoolean: boolean = false;
    @observable _transitionTools: boolean = false;
    @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;
    @observable _forceKeyEvents: boolean = false;

    // 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 generatedAnimations: AnimationSettings[] = [
        // default presets
        {
            effect: PresEffect.Bounce,
            direction: PresEffectDirection.Left,
            stiffness: 400,
            damping: 15,
            mass: 1,
        },
        {
            effect: PresEffect.Fade,
            direction: PresEffectDirection.Left,
            stiffness: 100,
            damping: 15,
            mass: 1,
        },
        {
            effect: PresEffect.Flip,
            direction: PresEffectDirection.Left,
            stiffness: 100,
            damping: 15,
            mass: 1,
        },
        {
            effect: PresEffect.Rotate,
            direction: PresEffectDirection.Left,
            stiffness: 100,
            damping: 15,
            mass: 1,
        },
    ];

    @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 setChatActive = (active: boolean) => {};

    @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[] }) => {
        this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`);
    };

    @computed
    get currCPoints() {
        let strPoints = this.activeItem.presEaseFunc ? StrCast(this.activeItem.presEaseFunc) : 'ease';
        if (!strPoints.startsWith('cubic')) {
            switch (StrCast(this.activeItem.presEaseFunc)) {
                case 'linear':
                    strPoints = 'cubic-bezier(0.0, 0.0, 1.0, 1.0)';
                    break;
                case 'ease':
                    strPoints = 'cubic-bezier(0.25, 0.1, 0.25, 1.0)';
                    break;
                case 'ease-in':
                    strPoints = 'cubic-bezier(0.42, 0, 1.0, 1.0)';
                    break;
                case 'ease-out':
                    strPoints = 'cubic-bezier(0, 0, 0.58, 1.0)';
                    break;
                case 'ease-in-out':
                    strPoints = 'cubic-bezier(0.42, 0, 0.58, 1.0)';
                    break;
                default:
                    strPoints = 'cubic-bezier(0.25, 0.1, 0.25, 1.0)';
            }
        }
        const components = strPoints
            .split('(')[1]
            .split(')')[0]
            .split(',')
            .map(elem => parseFloat(elem));
        return {
            p1: [components[0], components[1]],
            p2: [components[2], components[3]],
        };
    }

    @computed
    get isTreeOrStack() {
        return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._type_collection) as any);
    }
    @computed get isTree() {
        return this.layoutDoc._type_collection === CollectionViewType.Tree;
    }
    @computed get presFieldKey() {
        return StrCast(this.layoutDoc.presFieldKey, 'data');
    }
    @computed get childDocs() {
        return DocListCast(this.Document[this.presFieldKey]);
    }
    @computed get tagDocs() {
        return this.childDocs.map(doc => Cast(doc.presentation_targetDoc, Doc, null));
    }
    @computed get itemIndex() {
        return NumCast(this.Document._itemIndex);
    }
    @computed get activeItem() {
        return DocCast(this.childDocs[NumCast(this.Document._itemIndex)]);
    }
    @computed get targetDoc() {
        return Cast(this.activeItem?.presentation_targetDoc, Doc, null);
    }
    public static targetRenderedDoc = (doc: Doc) => {
        const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null);
        return targetDoc?.layout_unrendered ? DocCast(targetDoc.annotationOn) : targetDoc;
    };
    @computed get scrollable() {
        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 (SelectionManager.Views.length) return SelectionManager.Views[0];
        if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.Document);
    }
    @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;
        if (this._presTimer) clearTimeout(this._presTimer);
        document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
        this.resetPresentation();
        this.turnOffEdit(true);
        Object.values(this._disposers).forEach(disposer => disposer?.());
    }

    componentDidMount() {
        this._disposers.keyboard = reaction(
            () => this.selectedDoc,
            selected => {
                document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
                (this._presKeyEvents = selected === this.Document) && document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
            },
            { fireImmediately: true }
        );
        this._disposers.forcekeyboard = reaction(
            () => this._forceKeyEvents,
            force => {
                if (force) {
                    document.removeEventListener('keydown', PresBox.keyEventsWrapper, true);
                    document.addEventListener('keydown', PresBox.keyEventsWrapper, true);
                    this._presKeyEvents = true;
                }
                this._forceKeyEvents = false;
            },
            { fireImmediately: true }
        );
        // Casted to viewboxinterface
        this._props.setContentViewBox?.(this as ViewBoxInterface);
        this._unmounting = false;
        this.turnOffEdit(true);
        this._disposers.selection = reaction(
            () => SelectionManager.Views.slice(),
            views => (!PresBox.Instance || views.some(view => view.Document === this.Document)) && this.updateCurrentPresentation(),
            { fireImmediately: true }
        );
        this._disposers.editing = reaction(
            () => this.layoutDoc.presentation_status === PresStatus.Edit,
            editing => editing && 
                this.childDocs.filter(doc => doc.presentation_indexed !== undefined).forEach(doc => {
                    this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined));
                    doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1);
                }) // prettier-ignore
        );
    }

    @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);
        if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
            const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
            targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.config_clipStart), NumCast(activeItem.config_clipStart) + duration);
        }
    };

    stopTempMedia = (targetDocField: FieldResult) => {
        const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField);
        if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) {
            const targMedia = DocumentManager.Instance.getDocumentView(targetDoc);
            targMedia?.ComponentView?.Pause?.();
        }
    };

    // 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 = (abort: boolean) => {
        this.setIsRecording(false);
        DictationManager.Controls.stop(!abort);
    };

    setDictationContent = (value: string) => {
        console.log('Dictation value', value);
        this.setChatInput(value);
    };

    @action
    customizeAnimations = async (input: string) => {
        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);
    };

    @action
    customizeWithGPT = async (input: string) => {
        // const testInput = 'change title to Customized Slide, transition for 2.3s with fade in effect';
        this.setIsRecording(false);
        this.setIsLoading(true);

        let currSlideProperties: { [key: string]: any } = {};
        for (const key of gptSlideProperties) {
            if (this.activeItem[key]) {
                currSlideProperties[key] = this.activeItem[key];
            } else {
                // default values
                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 (let 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);
    };

    //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
    // No more frames in current doc and next slide is defined, therefore move to next slide
    nextSlide = (slideNum?: number) => {
        const nextSlideInd = slideNum ?? this.itemIndex + 1;
        let curSlideInd = nextSlideInd;
        //CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.());
        this.clearSelectedArray();
        const doGroupWithUp =
            (nextSelected: number, force = false) =>
            () => {
                if (nextSelected < this.childDocs.length) {
                    if (force || this.childDocs[nextSelected].presentation_groupWithUp) {
                        this.addToSelectedArray(this.childDocs[nextSelected]);
                        const serial = nextSelected + 1 < this.childDocs.length && NumCast(this.childDocs[nextSelected + 1].presentation_groupWithUp) > 1;
                        if (serial) {
                            this.gotoDocument(nextSelected, this.activeItem, true, async () => {
                                const waitTime = NumCast(this.activeItem.presentation_duration);
                                await new Promise<void>(res => setTimeout(() => res(), Math.max(0, waitTime)));
                                doGroupWithUp(nextSelected + 1)();
                            });
                        } else {
                            this.gotoDocument(nextSelected, this.activeItem, true);
                            curSlideInd = this.itemIndex;
                            doGroupWithUp(nextSelected + 1)();
                        }
                    }
                }
            };
        doGroupWithUp(curSlideInd, true)();
    };

    // docs within a slide target that will be progressively revealed
    progressivizedItems = (doc: Doc) => {
        const targetList = PresBox.targetRenderedDoc(doc);
        if (doc.presentation_indexed !== undefined && targetList) {
            const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '_annotations']);
            return listItems.filter(doc => !doc.layout_unrendered);
        }
    };

    // go to documents chain
    runSubroutines = (childrenToRun: Opt<Doc[]>, normallyNextSlide: Doc) => {
        if (childrenToRun && childrenToRun[0] !== normallyNextSlide) {
            childrenToRun.forEach(child => DocumentManager.Instance.showDocument(child, {}));
        }
    };

    // Called when the user activates 'next' - to move to the next part of the pres. trail
    @action
    next = () => {
        console.log('next slide');
        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) {
                    if (!first) {
                        const listItemDoc = listItems[presIndexed];
                        const targetView = listItems && DocumentManager.Instance.getFirstDocumentView(listItemDoc);
                        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.opacity = undefined;
                        this.activeItem.presentation_indexed = presIndexed + 1;
                    }
                    return true;
                }
            }
        };
        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.Document[StrCast(this.presFieldKey, 'data')]);
            const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex;

            // before moving onto next slide, run the subroutines :)
            const currentDoc = this.childDocs[this.itemIndex];
            //could i do this.childDocs[this.itemIndex] for first arg?
            this.runSubroutines(TreeView.GetRunningChildren.get(currentDoc)?.(), this.childDocs[this.itemIndex + 1]);

            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.presentation_status === 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;
        }
        return this.itemIndex;
    };

    // Called when the user activates 'back' - to move to the previous part of the pres. trail
    @action
    back = () => {
        const activeItem: Doc = this.activeItem;
        let prevSelected = this.itemIndex;
        // Functionality for group with up
        let didZoom = activeItem.presentation_movement;
        for (; prevSelected > 0 && this.childDocs[Math.max(0, prevSelected - 1)].presentation_groupWithUp; prevSelected--) {
            didZoom = didZoom === 'none' ? this.childDocs[prevSelected].presentation_movement : didZoom;
        }
        if (activeItem && this.childDocs[this.itemIndex - 1] !== undefined) {
            // Case 2: There are no other frames so it should go to the previous slide
            prevSelected = Math.max(0, prevSelected - 1);
            this.nextSlide(prevSelected);
            this.Document._itemIndex = prevSelected;
        } else if (this.childDocs[this.itemIndex - 1] === undefined && this.layoutDoc.presLoop) {
            // Case 3: Pres loop is on so it should go to the last slide
            this.nextSlide(this.childDocs.length - 1);
        }
        return this.itemIndex;
    };

    //The function that is called when a document is clicked or reached through next or back.
    //it'll also execute the necessary actions if presentation is playing.
    @undoBatch
    public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => {
        Doc.UnBrushAllDocs();
        if (index >= 0 && index < this.childDocs.length) {
            this.Document._itemIndex = index;
            if (from?.mediaStopTriggerList && this.layoutDoc.presentation_status !== PresStatus.Edit) {
                DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia);
            }
            if (from?.presentation_mediaStop === 'auto' && this.layoutDoc.presentation_status !== PresStatus.Edit) {
                this.stopTempMedia(from.presentation_targetDoc);
            }
            // If next slide is audio / video 'Play automatically' then the next slide should be played
            if (this.layoutDoc.presentation_status !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.presentation_mediaStart === 'auto') {
                this.startTempMedia(this.targetDoc, this.activeItem);
            }
            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
        }
    });
    static pinDataTypes(target?: Doc): pinDataTypes {
        const targetType = target?.type as any;
        const inkable = [DocumentType.INK].includes(targetType);
        const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._type_collection === CollectionViewType.Stacking;
        const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._type_collection === CollectionViewType.Freeform);
        const map = [DocumentType.MAP].includes(targetType);
        const temporal = [DocumentType.AUDIO, DocumentType.VID].includes(targetType);
        const clippable = [DocumentType.COMPARISON].includes(targetType);
        const datarange = [DocumentType.FUNCPLOT].includes(targetType);
        const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined;
        const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined;
        const type_collection = targetType === DocumentType.COL;
        const filters = true;
        const pivot = true;
        const dataannos = false;
        return { scrollable, pannable, inkable, type_collection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos };
    }

    @action
    playAnnotation = (anno: AudioField) => {};
    @action
    static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) {
        const bestTarget = bestTargetView?.Document ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc);
        if (!bestTarget) return;
        let changed = false;
        if (pinDocLayout) {
            if (
                bestTarget.x !== NumCast(activeItem.config_x, NumCast(bestTarget.x)) ||
                bestTarget.y !== NumCast(activeItem.config_y, NumCast(bestTarget.y)) ||
                bestTarget.rotation !== NumCast(activeItem.config_rotation, NumCast(bestTarget.rotation)) ||
                bestTarget.width !== NumCast(activeItem.config_width, NumCast(bestTarget.width)) ||
                bestTarget.height !== NumCast(activeItem.config_height, NumCast(bestTarget.height))
            ) {
                bestTarget._dataTransition = `all ${transTime}ms`;
                bestTarget.x = NumCast(activeItem.config_x, NumCast(bestTarget.x));
                bestTarget.y = NumCast(activeItem.config_y, NumCast(bestTarget.y));
                bestTarget.rotation = NumCast(activeItem.config_rotation, NumCast(bestTarget.rotation));
                bestTarget.width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
                bestTarget.height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
                bestTarget[TransitionTimer] && clearTimeout(bestTarget[TransitionTimer]);
                bestTarget[TransitionTimer] = setTimeout(() => (bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined), transTime + 10);
                changed = true;
            }
        }

        const activeFrame = activeItem.config_activeFrame ?? activeItem.config_currentFrame;
        if (activeFrame !== undefined) {
            const transTime = NumCast(activeItem.presentation_transition, 500);
            const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc);
            const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext;
            if (context) {
                const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView;
                if (ffview?.childDocs) {
                    PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime);
                    ffview.layoutDoc._currentFrame = NumCast(activeFrame);
                }
            }
        }
        if ((pinDataTypes?.dataview && activeItem.config_data !== undefined) || (!pinDataTypes && activeItem.config_data !== undefined)) {
            bestTarget._dataTransition = `all ${transTime}ms`;
            const fkey = Doc.LayoutFieldKey(bestTarget);
            const setData = bestTargetView?.ComponentView?.setData;
            if (setData) setData(activeItem.config_data);
            else {
                const bestTargetData = bestTarget[DocData];
                const current = bestTargetData[fkey];
                const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as Field)) : undefined;
                if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current;
                bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data;
            }
            bestTarget[fkey + '_usePath'] = activeItem.config_usePath;
            setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10);
        }
        if (pinDataTypes?.datarange || (!pinDataTypes && activeItem.config_xRange !== undefined)) {
            if (bestTarget.xRange !== activeItem.config_xRange) {
                bestTarget.xRange = (activeItem.config_xRange as ObjectField)?.[Copy]();
                changed = true;
            }
            if (bestTarget.yRange !== activeItem.config_yRange) {
                bestTarget.yRange = (activeItem.config_yRange as ObjectField)?.[Copy]();
                changed = true;
            }
        }
        if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.config_clipWidth !== undefined)) {
            const fkey = '_' + Doc.LayoutFieldKey(bestTarget);
            if (bestTarget[fkey + '_clipWidth'] !== activeItem.config_clipWidth) {
                bestTarget[fkey + '_clipWidth'] = activeItem.config_clipWidth;
                changed = true;
            }
        }
        if (pinDataTypes?.map || (!pinDataTypes && activeItem.config_latitude !== undefined)) {
            if (bestTarget.latitude !== activeItem.config_latitude) {
                Doc.SetInPlace(bestTarget, 'latitude', NumCast(activeItem.config_latitude), true);
                changed = true;
            }
            if (bestTarget.longitude !== activeItem.config_longitude) {
                Doc.SetInPlace(bestTarget, 'longitude', NumCast(activeItem.config_longitude), true);
                changed = true;
            }
            if (bestTarget.zoom !== activeItem.config_map_zoom) {
                Doc.SetInPlace(bestTarget, 'map_zoom', NumCast(activeItem.config_map_zoom), true);
                changed = true;
            }
            if (bestTarget.map_type !== activeItem.config_map_type) {
                Doc.SetInPlace(bestTarget, 'map_type', StrCast(activeItem.config_map_type), true);
                changed = true;
            }
            if (bestTarget.map !== activeItem.config_map) {
                Doc.SetInPlace(bestTarget, 'map', StrCast(activeItem.config_map), true);
                changed = true;
            }
        }
        if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.config_clipStart !== undefined)) {
            if (bestTarget._layout_currentTimecode !== activeItem.config_clipStart) {
                bestTarget._layout_currentTimecode = activeItem.config_clipStart;
                changed = true;
            }
        }
        if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.timecodeToShow !== undefined)) {
            if (bestTarget._layout_currentTimecode !== activeItem.timecodeToShow) {
                bestTarget._layout_currentTimecode = activeItem.timecodeToShow;
                changed = true;
            }
        }
        if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) {
            if (bestTarget.fillColor !== activeItem.config_fillColor) {
                bestTarget[DocData].fillColor = StrCast(activeItem.config_fillColor, StrCast(bestTarget.fillColor));
                changed = true;
            }
            if (bestTarget.color !== activeItem.config_color) {
                bestTarget[DocData].color = StrCast(activeItem.config_color, StrCast(bestTarget.color));
                changed = true;
            }
            if (bestTarget.width !== activeItem.width) {
                bestTarget._width = NumCast(activeItem.config_width, NumCast(bestTarget.width));
                changed = true;
            }
            if (bestTarget.height !== activeItem.height) {
                bestTarget._height = NumCast(activeItem.config_height, NumCast(bestTarget.height));
                changed = true;
            }
        }
        if ((pinDataTypes?.type_collection && activeItem.config_viewType !== undefined) || (!pinDataTypes && activeItem.config_viewType !== undefined)) {
            if (bestTarget._type_collection !== activeItem.config_viewType) {
                bestTarget._type_collection = activeItem.config_viewType;
                changed = true;
            }
        }

        if ((pinDataTypes?.filters && activeItem.config_docFilters !== undefined) || (!pinDataTypes && activeItem.config_docFilters !== undefined)) {
            if (bestTarget.childFilters !== activeItem.config_docFilters) {
                bestTarget.childFilters = ObjectField.MakeCopy(activeItem.config_docFilters as ObjectField) || new List<string>([]);
                changed = true;
            }
        }

        if ((pinDataTypes?.pivot && activeItem.config_pivotField !== undefined) || (!pinDataTypes && activeItem.config_pivotField !== undefined)) {
            if (bestTarget.pivotField !== activeItem.config_pivotField) {
                bestTarget.pivotField = activeItem.config_pivotField;
                bestTarget._prevFilterIndex = 1; // need to revisit this...see CollectionTimeView
                changed = true;
            }
        }
        if (bestTargetView?.ComponentView?.restoreView?.(activeItem)) {
            changed = true;
        }

        if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.config_scrollTop !== undefined)) {
            if (bestTarget._layout_scrollTop !== activeItem.config_scrollTop) {
                bestTarget._layout_scrollTop = activeItem.config_scrollTop;
                changed = true;
            }
        }
        if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.config_annotations !== undefined)) {
            const fkey = Doc.LayoutFieldKey(bestTarget);
            const oldItems = DocListCast(bestTarget[fkey + '_annotations']).filter(doc => doc.layout_unrendered);
            const newItems = DocListCast(activeItem.config_annotations).map(doc => {
                doc.hidden = false;
                return doc;
            });
            const hiddenItems = DocListCast(bestTarget[fkey + '_annotations'])
                .filter(doc => !doc.layout_unrendered && !newItems.includes(doc))
                .map(doc => {
                    doc.hidden = true;
                    return doc;
                });
            const newList = new List<Doc>([...oldItems, ...hiddenItems, ...newItems]);
            bestTarget[DocData][fkey + '_annotations'] = newList;
        }
        if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.config_pinLayoutData !== undefined)) {
            changed = true;
            const layoutField = Doc.LayoutFieldKey(bestTarget);
            const transitioned = new Set<Doc>();
            StrListCast(activeItem.config_pinLayoutData)
                .map(str => JSON.parse(str) as { id: string; x: number; y: number; back: string; fill: string; w: number; h: number; data: string; text: string })
                .forEach(async data => {
                    const doc = DocCast(DocServer.GetCachedRefField(data.id));
                    if (doc) {
                        transitioned.add(doc);
                        const field = !data.data ? undefined : await SerializationHelper.Deserialize(data.data);
                        const tfield = !data.text ? undefined : await SerializationHelper.Deserialize(data.text);
                        doc._dataTransition = `all ${transTime}ms`;
                        doc.x = data.x;
                        doc.y = data.y;
                        data.back && (doc._backgroundColor = data.back);
                        data.fill && (doc._fillColor = data.fill);
                        doc._width = data.w;
                        doc._height = data.h;
                        data.data && (doc[DocData].data = field);
                        data.text && (doc[DocData].text = tfield);
                        Doc.AddDocToList(bestTarget[DocData], layoutField, doc);
                    }
                });
            setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10);
        }
        if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !bestTarget.isGroup) {
            const contentBounds = Cast(activeItem.config_viewBounds, 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] };
                bestTarget._freeform_panX = viewport.panX;
                bestTarget._freeform_panY = viewport.panY;
                const dv = DocumentManager.Instance.getDocumentView(bestTarget);
                if (dv) {
                    changed = true;
                    const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv._props.PanelWidth() / viewport.width, dv._props.PanelHeight() / viewport.height);
                    activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale);
                    dv.ComponentView?.brushView?.(viewport, transTime, 2500);
                }
            } else {
                if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) {
                    bestTarget._freeform_panX = activeItem.config_panX ?? bestTarget._freeform_panX;
                    bestTarget._freeform_panY = activeItem.config_panY ?? bestTarget._freeform_panY;
                    bestTarget._freeform_scale = activeItem.config_viewScale ?? bestTarget._freeform_scale;
                    changed = true;
                }
            }
        }
        if (changed) {
            return bestTargetView?.setViewTransition('all', transTime);
        }
    }

    /// copies values from the targetDoc (which is the prototype of the pinDoc) to
    /// reserved fields on the pinDoc so that those values can be restored to the
    /// target doc when navigating to it.
    @action
    static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) {
        pinDoc.presentation = true;
        pinDoc.config = '';
        if (pinProps.pinDocLayout) {
            pinDoc.config_pinLayout = true;
            pinDoc.config_x = NumCast(targetDoc.x);
            pinDoc.config_y = NumCast(targetDoc.y);
            pinDoc.config_rotation = NumCast(targetDoc.rotation);
            pinDoc.config_width = NumCast(targetDoc.width);
            pinDoc.config_height = NumCast(targetDoc.height);
        }
        if (pinProps.pinAudioPlay) pinDoc.presPlayAudio = true;
        if (pinProps.pinData) {
            pinDoc.config_pinData =
                pinProps.pinData.scrollable ||
                pinProps.pinData.temporal ||
                pinProps.pinData.pannable ||
                pinProps.pinData.type_collection ||
                pinProps.pinData.clippable ||
                pinProps.pinData.datarange ||
                pinProps.pinData.dataview ||
                pinProps.pinData.poslayoutview ||
                pinProps?.activeFrame !== undefined;
            const fkey = Doc.LayoutFieldKey(targetDoc);
            if (pinProps.pinData.dataview) {
                pinDoc.config_usePath = targetDoc[fkey + '_usePath'];
                pinDoc.config_data = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data;
            }
            if (pinProps.pinData.dataannos) {
                const fkey = Doc.LayoutFieldKey(targetDoc);
                pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fkey + '_annotations']).filter(doc => !doc.layout_unrendered));
            }
            if (pinProps.pinData.inkable) {
                pinDoc.config_fillColor = targetDoc.fillColor;
                pinDoc.config_color = targetDoc.color;
                pinDoc.config_width = targetDoc._width;
                pinDoc.config_height = targetDoc._height;
            }
            if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop;
            if (pinProps.pinData.clippable) {
                const fkey = Doc.LayoutFieldKey(targetDoc);
                pinDoc.config_clipWidth = targetDoc[fkey + '_clipWidth'];
            }
            if (pinProps.pinData.datarange) {
                pinDoc.config_xRange = undefined; //targetDoc?.xrange;
                pinDoc.config_yRange = undefined; //targetDoc?.yrange;
            }
            if (pinProps.pinData.map) {
                // pinDoc.config_latitude = targetDoc?.latitude;
                // pinDoc.config_longitude = targetDoc?.longitude;
                pinDoc.config_map_zoom = targetDoc?.map_zoom;
                pinDoc.config_map_type = targetDoc?.map_type;
                //...
            }
            if (pinProps.pinData.poslayoutview)
                pinDoc.config_pinLayoutData = new List<string>(
                    DocListCast(targetDoc[fkey] as ObjectField).map(d =>
                        JSON.stringify({
                            id: d[Id],
                            x: NumCast(d.x),
                            y: NumCast(d.y),
                            w: NumCast(d._width),
                            h: NumCast(d._height),
                            fill: StrCast(d._fillColor),
                            back: StrCast(d._backgroundColor),
                            data: SerializationHelper.Serialize(d.data instanceof ObjectField ? d.data[Copy]() : ''),
                            text: SerializationHelper.Serialize(d.text instanceof ObjectField ? d.text[Copy]() : ''),
                        })
                    )
                );
            if (pinProps.pinData.type_collection) pinDoc.config_viewType = targetDoc._type_collection;
            if (pinProps.pinData.filters) pinDoc.config_docFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField);
            if (pinProps.pinData.pivot) pinDoc.config_pivotField = targetDoc._pivotField;
            if (pinProps.pinData.pannable) {
                pinDoc.config_panX = NumCast(targetDoc._freeform_panX);
                pinDoc.config_panY = NumCast(targetDoc._freeform_panY);
                pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1);
            }
            if (pinProps.pinData.temporal) {
                pinDoc.config_clipStart = targetDoc._layout_currentTimecode;
                const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1);
                pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration);
            }
        }
        if (pinProps?.pinViewport) {
            // If pinWithView option set then update scale and x / y props of slide
            const bounds = pinProps.pinViewport;
            pinDoc.config_pinView = true;
            pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1);
            pinDoc.config_panX = bounds.left + bounds.width / 2;
            pinDoc.config_panY = bounds.top + bounds.height / 2;
            pinDoc.config_viewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]);
        }
    }

    @action
    static reversePin(pinDoc: Doc, targetDoc: Doc) {
        // const fkey = Doc.LayoutFieldKey(targetDoc);
        pinDoc.config_data = targetDoc.data;

        console.log(pinDoc.presData);
    }

    /**
     * This method makes sure that cursor navigates to the element that
     * has the option open and last in the group.
     * Design choice: If the next document is not in presCollection or
     * presCollection itself then if there is a presCollection it will add
     * a new tab. If presCollection is undefined it will open the document
     * on the right.
     */
    navigateToActiveItem = (afterNav?: () => void) => {
        const activeItem: Doc = this.activeItem;

        const targetDoc: Doc = this.targetDoc;
        const finished = () => {
            afterNav?.();
            targetDoc[Animation] = undefined;
        };
        const selViewCache = Array.from(this.selectedArray);
        const dragViewCache = Array.from(this._dragArray);
        const eleViewCache = Array.from(this._eleArray);
        const resetSelection = action(() => {
            if (!this._props.isSelected()) {
                const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
                if (presDocView) SelectionManager.SelectView(presDocView, false);
                this.clearSelectedArray();
                selViewCache.forEach(doc => this.addToSelectedArray(doc));
                this._dragArray.splice(0, this._dragArray.length, ...dragViewCache);
                this._eleArray.splice(0, this._eleArray.length, ...eleViewCache);
            }
            finished();
        });
        PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection);
    };

    public static PanelName = 'PRESBOX';

    static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) {
        if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) {
            (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.();
            return;
        }
        console.log('pres_effect_dir', StrCast(activeItem.presentation_effectDirection));
        const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined;
        // default with effect: 750ms else 500ms
        const presTime = NumCast(activeItem.presentation_transition, effect ? 750 : 500);
        const options: FocusViewOptions = {
            willPan: activeItem.presentation_movement !== PresMovement.None,
            willZoomCentered: activeItem.presentation_movement === PresMovement.Zoom || activeItem.presentation_movement === PresMovement.Jump || activeItem.presentation_movement === PresMovement.Center,
            zoomScale: activeItem.presentation_movement === PresMovement.Center ? 0 : NumCast(activeItem.config_zoom, 1),
            zoomTime: activeItem.presentation_movement === PresMovement.Jump ? 0 : Math.min(Math.max(effect ? 750 : 500, (effect ? 0.2 : 1) * presTime), presTime),
            effect: activeItem,
            noSelect: true,
            openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft,
            anchorDoc: activeItem,
            easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any,
            zoomTextSelections: BoolCast(activeItem.presentation_zoomText),
            playAudio: BoolCast(activeItem.presPlayAudio),
            playMedia: activeItem.presentation_mediaStart === 'auto',
        };
        if (activeItem.presentation_openInLightbox) {
            const context = DocCast(targetDoc.annotationOn) ?? targetDoc;
            if (!DocumentManager.Instance.getLightboxDocumentView(context)) {
                LightboxView.Instance.SetLightboxDoc(context);
            }
        }
        if (targetDoc) {
            if (activeItem.presentation_targetDoc instanceof Doc) activeItem.presentation_targetDoc[Animation] = undefined;

            DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => {
                // if target or the doc it annotates is not in the lightbox, then close the lightbox
                if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) {
                    LightboxView.Instance.SetLightboxDoc(undefined);
                }
                DocumentManager.Instance.showDocument(targetDoc, options, finished);
            });
        } else finished?.();
    }

    /**
     * For 'Hide Before' and 'Hide After' buttons making sure that
     * they are hidden each time the presentation is updated.
     */
    @action
    doHideBeforeAfter = () => {
        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;
            if (curDoc.presentation_hide) {
                if (index !== this.itemIndex) {
                    opacity = 1;
                }
            }
            const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement();
            if (curDoc.presentation_hideBefore && index === hidingIndBef) {
                if (index > this.itemIndex) {
                    opacity = 0;
                } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) {
                    opacity = 1;
                    setTimeout(() => (tagDoc._dataTransition = undefined), 1000);
                }
            }
            const hidingIndAft =
                itemIndexes
                    .slice()
                    .reverse()
                    .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement();
            if (curDoc.presentation_hideAfter && index === hidingIndAft) {
                if (index < this.itemIndex) {
                    opacity = 0;
                } else if (index === this.itemIndex || !curDoc.presentation_hideBefore) {
                    opacity = 1;
                }
            }
            const hidingInd = itemIndexes.find(item => item === this.itemIndex);
            if (curDoc.presentation_hide && index === hidingInd) {
                if (index === this.itemIndex) {
                    opacity = 0;
                }
            }
            opacity !== undefined && (tagDoc.opacity = opacity === 1 ? undefined : opacity);
        });
    };

    _exitTrail: Opt<() => void>;
    PlayTrail = (docs: Doc[]) => {
        const savedStates = docs.map(doc => {
            switch (doc.type) {
                case DocumentType.COL:
                    if (doc._type_collection === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) };
                    break;
                case DocumentType.INK:
                    if (doc.data instanceof InkField) {
                        return { type: doc.type, doc, data: doc.data?.[Copy](), fillColor: doc.fillColor, color: doc.color, x: NumCast(doc.x), y: NumCast(doc.y) };
                    }
            }
            return undefined;
        });
        this.startPresentation(0);
        this._exitTrail = () => {
            savedStates
                .filter(savedState => savedState)
                .map(savedState => {
                    switch (savedState?.type) {
                        case CollectionViewType.Freeform:
                            {
                                const { x, y, s, doc } = savedState!;
                                doc._freeform_panX = x;
                                doc._freeform_panY = y;
                                doc._freeform_scale = s;
                            }
                            break;
                        case DocumentType.INK:
                            {
                                const { data, fillColor, color, x, y, doc } = savedState!;
                                doc.x = x;
                                doc.y = y;
                                doc.data = data;
                                doc.fillColor = fillColor;
                                doc.color = color;
                            }
                            break;
                    }
                });
            LightboxView.Instance.SetLightboxDoc(undefined);
            Doc.RemFromMyOverlay(this.Document);
            return PresStatus.Edit;
        };
    };

    // The function pauses the auto presentation
    @action
    pauseAutoPres = () => {
        if (this.layoutDoc.presentation_status === PresStatus.Autoplay) {
            if (this._presTimer) clearTimeout(this._presTimer);
            this.layoutDoc.presentation_status = PresStatus.Manual;
            this.childDocs.forEach(this.stopTempMedia);
        }
    };

    //The function that resets the presentation by removing every action done by it. It also
    //stops the presentaton.
    resetPresentation = () => {
        this.childDocs
            .map(doc => PresBox.targetRenderedDoc(doc))
            .filter(doc => doc instanceof Doc)
            .forEach(doc => {
                try {
                    doc.opacity = 1;
                } catch (e) {
                    console.log('Reset presentation error: ', e);
                }
            });
    };

    // The function allows for viewing the pres path on toggle
    @action togglePath = (off?: boolean) => {
        this._pathBoolean = off ? false : !this._pathBoolean;
        CollectionFreeFormView.ShowPresPaths = this._pathBoolean;
    };

    // The function allows for expanding the view of pres on toggle
    @action toggleExpandMode = () => {
        runInAction(() => (this._expandBoolean = !this._expandBoolean));
        this.Document.expandBoolean = this._expandBoolean;
        this.childDocs.forEach(doc => {
            doc.presentation_expandInlineButton = this._expandBoolean;
        });
    };

    initializePresState = (startIndex: number) => {
        this.childDocs.forEach((doc, index) => {
            const tagDoc = PresBox.targetRenderedDoc(doc);
            if (doc.presentation_hideBefore && index > startIndex) tagDoc.opacity = 0;
            if (doc.presentation_hideAfter && index < startIndex) tagDoc.opacity = 0;
            if (doc.presentation_indexed !== undefined && index >= startIndex) {
                const startInd = NumCast(doc.presentation_indexedStart);
                this.progressivizedItems(doc)
                    ?.slice(startInd)
                    .forEach(indexedDoc => (indexedDoc.opacity = 0));
                doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, startInd);
            }
            // if (doc.presentation_hide && 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.
     * @param startIndex: index that the presentation will start at
     */
    @action
    startPresentation = (startIndex: number) => {
        PresBox.Instance = this;
        clearTimeout(this._presTimer);
        if (this.childDocs.length) {
            this.layoutDoc.presentation_status = PresStatus.Autoplay;
            this.initializePresState(startIndex);
            const func = () => {
                const delay = NumCast(this.activeItem.presentation_duration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presentation_transition);
                this._presTimer = setTimeout(() => {
                    if (!this.next()) this.layoutDoc.presentation_status = this._exitTrail?.() ?? PresStatus.Manual;
                    this.layoutDoc.presentation_status === PresStatus.Autoplay && func();
                }, delay);
            };
            this.gotoDocument(startIndex, this.activeItem, undefined, func);
        }
    };

    /**
     * The method called to open the presentation as a minimized view
     */
    @action
    enterMinimize = () => {
        this.updateCurrentPresentation(this.Document);
        clearTimeout(this._presTimer);
        const pt = this.ScreenToLocalBoxXf().inverse().transformPoint(0, 0);
        this._props.removeDocument?.(this.layoutDoc);
        return PresBox.OpenPresMinimized(this.Document, [pt[0] + (this._props.PanelWidth() - 250), pt[1] + 10]);
    };
    exitMinimize = () => {
        if (Doc.IsInMyOverlay(this.layoutDoc)) {
            Doc.RemFromMyOverlay(this.Document);
            CollectionDockingView.AddSplit(this.Document, OpenWhereMod.right);
        }
        return PresStatus.Edit;
    };

    public static minimizedWidth = 198;
    public static OpenPresMinimized(doc: Doc, pt: number[]) {
        doc.overlayX = pt[0];
        doc.overlayY = pt[1];
        doc._height = 30;
        doc._width = PresBox.minimizedWidth;
        Doc.AddToMyOverlay(doc);
        PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex);
        return (doc.presentation_status = PresStatus.Manual);
    }

    /**
     * Called when the user changes the view type
     * Either 'List' (stacking) or 'Slides' (carousel)
     */
    @undoBatch
    viewChanged = action((e: React.ChangeEvent) => {
        //@ts-ignore
        const type_collection = e.target.selectedOptions[0].value as CollectionViewType;
        this.layoutDoc.presFieldKey = this.fieldKey + (type_collection === CollectionViewType.Tree ? '-linearized' : '');
        // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here
        [CollectionViewType.Tree || CollectionViewType.Stacking].includes(type_collection) && (this.Document._pivotField = undefined);
        this.Document._type_collection = type_collection;
        if (this.isTreeOrStack) {
            this.layoutDoc._gridGap = 0;
        }
    });

    /**
     * Called when the user changes the view type
     * Either 'List' (stacking) or 'Slides' (carousel)
     */
    // @undoBatch
    mediaStopChanged = action((e: React.ChangeEvent) => {
        const activeItem: Doc = this.activeItem;
        //@ts-ignore
        const stopDoc = e.target.selectedOptions[0].value as string;
        const stopDocIndex: number = Number(stopDoc[0]);
        activeItem.mediaStopDoc = stopDocIndex;
        if (this.childDocs[stopDocIndex - 1].mediaStopTriggerList) {
            const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList);
            list.push(activeItem);
            // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list;\
        } else {
            this.childDocs[stopDocIndex - 1].mediaStopTriggerList = new List<Doc>();
            const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList);
            list.push(activeItem);
            // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list;
        }
    });

    movementName = action((activeItem: Doc) => {
        if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Center, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presentation_movement) as any)) {
            return PresMovement.Zoom;
        }
        return StrCast(activeItem.presentation_movement);
    });

    whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isChildActive = isActive)));
    // For dragging documents into the presentation trail
    addDocumentFilter = (docs: Doc[]) => {
        docs.forEach((doc, i) => {
            if (doc.presentation_targetDoc) return true;
            if (doc.type === DocumentType.LABEL) {
                const audio = Cast(doc.annotationOn, Doc, null);
                if (audio) {
                    audio.config_clipStart = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */));
                    audio.config_clipEnd = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */));
                    audio.presentation_duration = audio.config_clipStart - audio.config_clipEnd;
                    this._props.pinToPres(audio, { audioRange: true });
                    setTimeout(() => this.removeDocument(doc), 0);
                    return false;
                }
            } else if (doc.type !== DocumentType.PRES) {
                if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide';
                doc.presentation_targetDoc = doc.createdFrom; // dropped document will be a new embedding of an embedded document somewhere else.
                doc.presentation_movement = PresMovement.Zoom;
                if (this._expandBoolean) doc.presentation_expandInlineButton = true;
            }
        });
        return true;
    };

    childLayoutTemplate = () => Docs.Create.PresElementBoxDocument();
    removeDocument = (doc: Doc | Doc[]) => !(doc instanceof Doc ? [doc] : doc).map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d)).some(p => !p);
    getTransform = () => this.ScreenToLocalBoxXf().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight
    panelHeight = () => this._props.PanelHeight() - 40;
    /**
     * For sorting the array so that the order is maintained when it is dropped.
     */
    sortArray = () => this.childDocs.filter(doc => this.selectedArray.has(doc));

    /**
     * Method to get the list of selected items in the order in which they have been selected
     */
    @computed get listOfSelected() {
        return Array.from(this.selectedArray).map((doc: Doc, index: any) => {
            const curDoc = Cast(doc, Doc, null);
            const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null);
            if (curDoc && curDoc === this.activeItem)
                return (
                    <div key={index} className="selectedList-items">
                        <b>
                            {index + 1}. {curDoc.title}
                        </b>
                    </div>
                );
            else if (tagDoc)
                return (
                    <div key={index} className="selectedList-items">
                        {index + 1}. {curDoc.title}
                    </div>
                );
            else if (curDoc)
                return (
                    <div key={index} className="selectedList-items">
                        {index + 1}. {curDoc.title}
                    </div>
                );
        });
    }

    @action
    selectPres = () => {
        const presDocView = DocumentManager.Instance.getDocumentView(this.Document);
        presDocView && SelectionManager.SelectView(presDocView, false);
    };

    focusElement = (doc: Doc, options: FocusViewOptions) => {
        this.selectElement(doc);
        return undefined;
    };

    //Regular click
    @action
    selectElement = (doc: Doc, noNav = false) => {
        CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => clip?.ComponentView?.Pause?.());
        if (noNav) {
            const index = this.childDocs.indexOf(doc);
            if (index >= 0 && index < this.childDocs.length) {
                this.Document._itemIndex = index;
            }
        } else {
            this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem);
        }
        this.updateCurrentPresentation(DocCast(doc.embedContainer));
    };

    // Command click
    @action
    multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
        if (!this.selectedArray.has(doc)) {
            this.addToSelectedArray(doc);
            this._eleArray.push(ref);
            this._dragArray.push(drag);
        } else {
            this.removeFromSelectedArray(doc);
            this._eleArray.splice(this._eleArray.indexOf(ref));
            this._dragArray.splice(this._dragArray.indexOf(drag));
        }
        this.selectPres();
    };

    //Shift click
    @action
    shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => {
        this.clearSelectedArray();
        // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null);
        if (this.activeItem) {
            for (let i = Math.min(this.itemIndex, this.childDocs.indexOf(doc)); i <= Math.max(this.itemIndex, this.childDocs.indexOf(doc)); i++) {
                this.addToSelectedArray(this.childDocs[i]);
                this._eleArray.push(ref);
                this._dragArray.push(drag);
            }
        }
        this.selectPres();
    };

    //regular click
    @action
    regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: boolean, selectPres = true) => {
        this.clearSelectedArray();
        this.addToSelectedArray(doc);
        this._eleArray.splice(0, this._eleArray.length, ref);
        this._dragArray.splice(0, this._dragArray.length, drag);
        this.selectElement(doc, noNav);
        selectPres && this.selectPres();
    };

    modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: boolean, cmdClick: boolean, shiftClick: boolean) => {
        if (cmdClick) this.multiSelect(doc, ref, drag);
        else if (shiftClick) this.shiftSelect(doc, ref, drag);
        else this.regularSelect(doc, ref, drag, noNav);
    };

    static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance?.keyEvents(e);

    // Key for when the presentaiton is active
    @action
    keyEvents = (e: KeyboardEvent) => {
        if (e.target instanceof HTMLInputElement) return;
        if (e.target instanceof HTMLTextAreaElement) return;
        let handled = false;
        const anchorNode = document.activeElement as HTMLDivElement;
        if (anchorNode && anchorNode.className?.includes('lm_title')) return;
        switch (e.key) {
            case 'Backspace':
                if (this.layoutDoc.presentation_status === 'edit') {
                    undoBatch(
                        action(() => {
                            for (const doc of this.selectedArray) {
                                this.removeDocument(doc);
                            }
                            this.clearSelectedArray();
                            this._eleArray.length = 0;
                            this._dragArray.length = 0;
                        })
                    )();
                    handled = true;
                }
                break;
            case 'Escape':
                if (Doc.IsInMyOverlay(this.layoutDoc)) {
                    this.exitClicked();
                } else if (this.layoutDoc.presentation_status === PresStatus.Edit) {
                    this.clearSelectedArray();
                    this._eleArray.length = this._dragArray.length = 0;
                } else {
                    this.layoutDoc.presentation_status = PresStatus.Edit;
                }
                if (this._presTimer) clearTimeout(this._presTimer);
                handled = true;
                break;
            case 'Down':
            case 'ArrowDown':
            case 'Right':
            case 'ArrowRight':
                if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) {
                    // TODO: update to work properly
                    this.Document._itemIndex = NumCast(this.Document._itemIndex) + 1;
                    this.addToSelectedArray(this.childDocs[this.Document._itemIndex]);
                } else {
                    this.next();
                    if (this._presTimer) {
                        clearTimeout(this._presTimer);
                        this.layoutDoc.presentation_status = PresStatus.Manual;
                    }
                }
                handled = true;
                break;
            case 'Up':
            case 'ArrowUp':
            case 'Left':
            case 'ArrowLeft':
                if (e.shiftKey && this.itemIndex !== 0) {
                    // TODO: update to work properly
                    this.Document._itemIndex = NumCast(this.Document._itemIndex) - 1;
                    this.addToSelectedArray(this.childDocs[this.Document._itemIndex]);
                } else {
                    this.back();
                    if (this._presTimer) {
                        clearTimeout(this._presTimer);
                        this.layoutDoc.presentation_status = PresStatus.Manual;
                    }
                }
                handled = true;
                break;
            case 'Spacebar':
            case ' ':
                if (this.layoutDoc.presentation_status === PresStatus.Manual) this.startOrPause(true);
                else if (this.layoutDoc.presentation_status === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer);
                handled = true;
                break;
            case 'a':
                if ((e.metaKey || e.altKey) && this.layoutDoc.presentation_status === 'edit') {
                    this.clearSelectedArray();
                    this.childDocs.forEach(doc => this.addToSelectedArray(doc));
                    handled = true;
                }
            default:
                break;
        }
        if (handled) {
            e.stopPropagation();
            e.preventDefault();
        }
    };

    getAllIndexes = (arr: Doc[], val: Doc) => arr.map((doc, i) => (doc === val ? i : -1)).filter(i => i !== -1);

    // Adds the index in the pres path graphically
    orderedPathLabels = (collection: Doc) => {
        const order: JSX.Element[] = [];
        const docs = new Set<Doc>();
        const presCollection = collection;
        const dv = DocumentManager.Instance.getDocumentView(presCollection);
        this.childDocs.forEach((doc, index) => {
            const tagDoc = PresBox.targetRenderedDoc(doc);
            const srcContext = Cast(tagDoc.embedContainer, Doc, null);
            const labelCreator = (top: number, left: number, edge: number, fontSize: number) => (
                <div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top, left, width: edge, height: edge, fontSize }} onClick={() => this.selectElement(doc)}>
                    <div className="pathOrder-frame">{index + 1}</div>
                </div>
            );
            if (presCollection === srcContext) {
                const gap = 2;
                const width = NumCast(tagDoc._width) / 10;
                const height = Math.max(NumCast(tagDoc._height) / 10, 15);
                const edge = Math.max(width, height);
                const fontSize = edge * 0.8;
                // Case A: Document is contained within the collection
                if (docs.has(tagDoc)) {
                    const prevOccurences = this.getAllIndexes(Array.from(docs), tagDoc).length;
                    order.push(labelCreator(NumCast(tagDoc.y) + (prevOccurences * (edge + gap) - edge / 2), NumCast(tagDoc.x) - edge / 2, edge, fontSize));
                } else {
                    order.push(labelCreator(NumCast(tagDoc.y) - edge / 2, NumCast(tagDoc.x) - edge / 2, edge, fontSize));
                }
            } else if (doc.config_pinView && presCollection === tagDoc && dv) {
                // Case B: Document is presPinView and is presCollection
                const scale = 1 / NumCast(doc.config_viewScale);
                const viewBounds = NumListCast(doc.config_viewBounds, [0, 0, dv._props.PanelWidth(), dv._props.PanelHeight()]);
                const height = (viewBounds[3] - viewBounds[1]) * scale;
                const width = (viewBounds[2] - viewBounds[0]) * scale;
                const indWidth = width / 10;
                const indHeight = Math.max(height / 10, 15);
                const indEdge = Math.max(indWidth, indHeight);
                const indFontSize = indEdge * 0.8;
                const left = NumCast(doc.config_panX) - width / 2;
                const top = NumCast(doc.config_panY) - height / 2;
                order.push(
                    <>
                        {labelCreator(top - indEdge / 2, left - indEdge / 2, indEdge, indFontSize)}
                        <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }}></div>
                    </>
                );
            }
            docs.add(tagDoc);
        });
        return order;
    };

    /**
     * Method called for viewing paths which adds a single line with
     * points at the center of each document added.
     * Design choice: When this is called it sets _freeform_fitContentsToBox as true so the
     * user can have an overview of all of the documents in the collection.
     * (Design needed for when documents in presentation trail are in another
     * collection)
     */
    pathLines = (collection: Doc) => {
        let pathPoints = '';
        this.childDocs
            .filter(doc => PresBox.targetRenderedDoc(doc)?.embedContainer === collection)
            .forEach((doc, index) => {
                const tagDoc = PresBox.targetRenderedDoc(doc);
                if (tagDoc) {
                    const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2;
                    const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2;
                    if ((index = 0)) pathPoints = n1x + ',' + n1y;
                    else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
                } else if (doc.config_pinView) {
                    const n1x = NumCast(doc.config_panX);
                    const n1y = NumCast(doc.config_panY);
                    if ((index = 0)) pathPoints = n1x + ',' + n1y;
                    else pathPoints = pathPoints + ' ' + n1x + ',' + n1y;
                }
            });
        return (
            <>
                <div className="presPathLabels">{PresBox.Instance?.orderedPathLabels(collection)}</div>
                <svg key="svg" className="presPaths">
                    <defs>
                        <marker id="markerSquare" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
                            <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="white" fillOpacity="0.8" />
                        </marker>
                        <marker id="markerSquareFilled" markerWidth="3" markerHeight="3" refX="1.5" refY="1.5" orient="auto" overflow="visible">
                            <rect x="0" y="0" width="3" height="3" stroke="#69a6db" strokeWidth="1" fill="#69a6db" />
                        </marker>
                        <marker id="markerArrow" markerWidth="3" markerHeight="3" refX="2" refY="4" orient="auto" overflow="visible">
                            <path d="M2,2 L2,6 L6,4 L2,2 Z" stroke="#69a6db" strokeLinejoin="round" strokeWidth="1" fill="white" fillOpacity="0.8" />
                        </marker>
                    </defs>
                    <polyline
                        points={pathPoints}
                        style={{
                            opacity: 1,
                            stroke: '#69a6db',
                            strokeWidth: 5,
                            strokeDasharray: '10 5',
                            boxShadow: '0px 4px 4px rgba(0, 0, 0, 0.25)',
                        }}
                        fill="none"
                        markerStart="url(#markerArrow)"
                        markerMid="url(#markerSquare)"
                        markerEnd="url(#markerSquareFilled)"
                    />
                </svg>
            </>
        );
    };
    // Converts seconds to ms and updates presentation_transition
    public static SetTransitionTime = (number: String, setter: (timeInMS: number) => void, change?: number) => {
        let timeInMS = Number(number) * 1000;
        if (change) timeInMS += change;
        if (timeInMS < 100) timeInMS = 100;
        if (timeInMS > 100000) timeInMS = 100000;
        setter(timeInMS);
    };

    @undoBatch
    updateTransitionTime = (number: String, change?: number) => {
        PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presentation_transition = timeInMS)), change);
    };

    // Converts seconds to ms and updates presentation_transition
    @undoBatch
    updateZoom = (number: String, change?: number) => {
        let scale = Number(number) / 100;
        if (change) scale += change;
        if (scale < 0.01) scale = 0.01;
        if (scale > 1) scale = 1;
        this.selectedArray.forEach(doc => (doc.config_zoom = scale));
    };

    /*
     * Converts seconds to ms and updates presentation_duration
     */
    @undoBatch
    updateDurationTime = (number: String, change?: number) => {
        let timeInMS = Number(number) * 1000;
        if (change) timeInMS += change;
        if (timeInMS < 100) timeInMS = 100;
        if (timeInMS > 20000) timeInMS = 20000;
        this.selectedArray.forEach(doc => (doc.presentation_duration = timeInMS));
    };

    @undoBatch
    updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_movement = movement)));

    @undoBatch
    updateHideBefore = (activeItem: Doc) => {
        activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore;
        this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore));
    };

    @undoBatch
    updateHide = (activeItem: Doc) => {
        activeItem.presentation_hide = !activeItem.presentation_hide;
        this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide));
    };

    @undoBatch
    updateHideAfter = (activeItem: Doc) => {
        activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter;
        this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter));
    };

    @undoBatch
    updateOpenDoc = (activeItem: Doc) => {
        activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox;
        this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox));
    };

    @undoBatch
    updateEaseFunc = (activeItem: Doc) => {
        activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear';
        this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc));
    };

    @undoBatch
    setEaseFunc = (activeItem: Doc, easeFunc: string) => {
        activeItem.presEaseFunc = easeFunc;
        this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc));
    };

    @undoBatch
    updateEffectDirection = (effectDir: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effectDir));

    @undoBatch
    updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect)));

    @undoBatch
    updateEffectTiming = (activeItem: Doc, timing: SpringSettings) => {
        activeItem.presEffectTiming = JSON.stringify(timing);
        this.selectedArray.forEach(doc => (doc.presEffectTiming = activeItem.presEffectTiming));
    };

    static _sliderBatch: any;
    static endBatch = () => {
        PresBox._sliderBatch.end();
        document.removeEventListener('pointerup', PresBox.endBatch, true);
    };

    public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => {
        return (
            <div
                onPointerDown={e => {
                    PresBox._sliderBatch = UndoManager.StartBatch('pres slider');
                    document.addEventListener('pointerup', PresBox.endBatch, true);
                    e.stopPropagation();
                }}>
                <Slider
                    onChange={(e, val) => {
                        e.stopPropagation();
                        change(val.toString());
                    }}
                    step={parseFloat(step)}
                    min={parseFloat(min)}
                    max={parseFloat(max)}
                    value={value}
                    size="small"
                    defaultValue={value}
                    aria-label="Small"
                    valueLabelDisplay="auto"
                />
            </div>
        );
    };

    // Applies the slide transiiton settings to all docs in the array
    @undoBatch
    applyTo = (array: Doc[]) => {
        this.updateMovement(this.activeItem.presentation_movement as PresMovement, true);
        this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true);
        this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true);
        this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true);
        const { presentation_transition, presentation_duration, presentation_hideBefore, presentation_hideAfter } = this.activeItem;
        array.forEach(curDoc => {
            curDoc.presentation_transition = presentation_transition;
            curDoc.presentation_duration = presentation_duration;
            curDoc.presentation_hideBefore = presentation_hideBefore;
            curDoc.presentation_hideAfter = presentation_hideAfter;
        });
    };

    @computed get visibilityDurationDropdown() {
        const activeItem = this.activeItem;
        if (activeItem && this.targetDoc) {
            const targetType = this.targetDoc.type;
            let duration = activeItem.presentation_duration ? NumCast(activeItem.presentation_duration) / 1000 : 0;
            if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration);
            return (
                <div className="presBox-option-block">
                    <div className="presBox-ribbon">
                        <div className="presBox-toggles">
                            <Tooltip title={<div className="dash-tooltip">Hide before presented</div>}>
                                <div
                                    className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`}
                                    style={{
                                        border: `solid 1px ${SettingsManager.userColor}`,
                                        color: SettingsManager.userColor,
                                        background: activeItem.presentation_hideBefore ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
                                    }}
                                    onClick={() => this.updateHideBefore(activeItem)}>
                                    Hide before
                                </div>
                            </Tooltip>
                            <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}>
                                <div
                                    className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`}
                                    style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hide ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
                                    onClick={() => this.updateHide(activeItem)}>
                                    Hide
                                </div>
                            </Tooltip>
                            <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}>
                                <div
                                    className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`}
                                    style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideAfter ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }}
                                    onClick={() => this.updateHideAfter(activeItem)}>
                                    Hide after
                                </div>
                            </Tooltip>

                            <Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}>
                                <div
                                    className="ribbon-toggle"
                                    style={{
                                        border: `solid 1px ${SettingsManager.userColor}`,
                                        color: SettingsManager.userColor,
                                        background: activeItem.presentation_openInLightbox ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor,
                                    }}
                                    onClick={() => this.updateOpenDoc(activeItem)}>
                                    Lightbox
                                </div>
                            </Tooltip>
                        </div>
                        {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as any as DocumentType) ? null : (
                            <>
                                <div className="ribbon-doubleButton">
                                    <div className="presBox-subheading">Slide Duration</div>
                                    <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
                                        <input className="presBox-input" type="number" readOnly={true} value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
                                    </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>
                            </>
                        )}
                    </div>
                </div>
            );
        }
    }
    @computed get progressivizeDropdown() {
        const activeItem = this.activeItem;
        if (activeItem && this.targetDoc) {
            const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None;
            const bulletEffect = (effect: PresEffect) => (
                <div
                    className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
                    onPointerDown={StopEvent}
                    onClick={() => this.updateEffect(effect, true)}>
                    {effect}
                </div>
            );
            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 ${SettingsManager.userColor}` }}
                                type="checkbox"
                                onChange={() => {
                                    activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined;
                                    activeItem.presentation_hideBefore = activeItem.presentation_indexed !== undefined;
                                    const tagDoc = PresBox.targetRenderedDoc(this.activeItem);
                                    const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type;
                                    activeItem.presentation_indexedStart = type === DocumentType.COL ? 1 : 0;
                                    // a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized.
                                    // to avoid creating a new slide to correspond to each of the target's data list, we create a computedField to refernce the target's data list.
                                    let dataField = Doc.LayoutFieldKey(tagDoc);
                                    if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '_annotations';

                                    if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`);
                                    else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`);
                                }}
                                checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false}
                            />
                        </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 ${SettingsManager.userColor}` }}
                                type="checkbox"
                                onChange={() => (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 ${SettingsManager.userColor}` }}
                                type="checkbox"
                                onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)}
                                checked={BoolCast(activeItem.presBulletExpand)}
                            />
                        </div>

                        <div className="ribbon-box">
                            Bullet Effect
                            <div
                                className="presBox-dropdown"
                                onClick={action(e => {
                                    e.stopPropagation();
                                    this._openBulletEffectDropdown = !this._openBulletEffectDropdown;
                                })}
                                style={{
                                    color: SettingsManager.userColor,
                                    background: SettingsManager.userVariantColor,
                                    borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5,
                                    border: this._openBulletEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`,
                                }}>
                                {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: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }}
                                    onPointerDown={e => e.stopPropagation()}>
                                    {Object.values(PresEffect)
                                        .filter(v => isNaN(Number(v)))
                                        .map(effect => bulletEffect(effect))}
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            );
        }
        return null;
    }

    @computed get gptDropdown() {
        const activeItem = this.activeItem;
        return <div></div>;
    }

    @computed get transitionDropdown() {
        const activeItem = this.activeItem;
        // Retrieving spring timing properties
        let timing = StrCast(activeItem.presEffectTiming);
        let timingConfig: SpringSettings | undefined;
        if (timing) {
            timingConfig = JSON.parse(timing);
        }

        if (!timingConfig) {
            timingConfig = {
                type: SpringType.GENTLE,
                stiffness: 100,
                damping: 15,
                mass: 1,
            };
        }

        const presEffect = (effect: PresEffect) => (
            <div
                className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`}
                onPointerDown={StopEvent}
                onClick={() => this.updateEffect(effect, false)}>
                {effect}
            </div>
        );
        const presMovement = (movement: PresMovement) => (
            <div className={`presBox-dropdownOption ${activeItem.presentation_movement === movement ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateMovement(movement)}>
                {movement}
            </div>
        );
        const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => {
            const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SettingsManager.userVariantColor : SettingsManager.userColor;
            return (
                <Tooltip title={<div className="dash-tooltip">{direction}</div>}>
                    <div
                        style={{ ...opts, border: direction === PresEffectDirection.Center ? `solid 2px ${color}` : undefined, borderRadius: '100%', cursor: 'pointer', gridColumn, gridRow, justifySelf: 'center', color }}
                        onClick={() => this.updateEffectDirection(direction)}>
                        {icon ? <FontAwesomeIcon icon={icon as any} /> : null}
                    </div>
                </Tooltip>
            );
        };

        if (activeItem && this.targetDoc) {
            const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5;
            const zoom = NumCast(activeItem.config_zoom, 1) * 100;
            const effect = StrCast(activeItem.presentation_effect) ? StrCast(activeItem.presentation_effect) : PresEffect.None;
            const direction = StrCast(activeItem.presentation_effectDirection);

            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' }}>
                            Customize Slide Properties{' '}
                            <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/trails/')}>
                                <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SettingsManager.userColor} />
                            </div>
                        </span>
                        <div className="pres-chat">
                            <div className="pres-chatbox-container">
                                <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);
                                    }}
                                    onKeyDown={e => {
                                        this.stopDictation(true);
                                        e.stopPropagation();
                                    }}
                                />
                                <IconButton
                                    type={Type.TERT}
                                    color={this.isRecording ? '#2bcaff' : StrCast(Doc.UserDoc().userVariantColor)}
                                    tooltip="Record"
                                    icon={<BiMicrophone size={'16px'} />}
                                    onClick={() => {
                                        if (!this.isRecording) {
                                            this.recordDictation();
                                        } else {
                                            this.stopDictation(true);
                                        }
                                    }}
                                />
                            </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={StrCast(Doc.UserDoc().userVariantColor)}
                                onClick={() => {
                                    this.customizeWithGPT(this.chatInput);
                                }}
                            />
                        </div>
                    </div>
                    {/* Movement */}
                    <div
                        className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`}
                        onPointerDown={StopEvent}
                        onPointerUp={StopEvent}
                        onClick={action(e => {
                            e.stopPropagation();
                            this._openMovementDropdown = false;
                            this._openEffectDropdown = false;
                            this._openBulletEffectDropdown = false;
                        })}>
                        <div
                            className="presBox-option-block"
                            // style={{ padding: '16px' }}
                        >
                            Movement
                            <Dropdown
                                color={StrCast(Doc.UserDoc().userColor)}
                                formLabel={'Movement'}
                                closeOnSelect={true}
                                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 ? 'inline-flex' : 'none' }}>
                                <div className="presBox-subheading">Zoom (% screen filled)</div>
                                <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}>
                                    <input className="presBox-input" readOnly={true} type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />%
                                </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 ${SettingsManager.userColor}` }}>
                                    <input className="presBox-input" type="number" readOnly={true} value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
                                </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={StrCast(Doc.UserDoc().userColor)}
                                formLabel={'Easing Function'}
                                closeOnSelect={true}
                                items={easeItems}
                                selectedVal={this.activeItem.presEaseFunc ? (StrCast(this.activeItem.presEaseFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presEaseFunc)) : '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);
                                        }
                                    }
                                }}
                                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);
                                }}>
                                {`${this.showBezierEditor ? 'Hide' : 'Show'} Timing Editor`}
                                <FontAwesomeIcon icon={this.showBezierEditor ? 'chevron-up' : 'chevron-down'} />
                            </div>
                        </div>
                    </div>

                    {this.showBezierEditor && (
                        <div className="presBox-option-block" style={{ paddingTop: 0 }}>
                            <p className="presBox-submenu-label" style={{ alignSelf: 'flex-start' }}>
                                Custom Timing Function
                            </p>
                            <CubicBezierEditor setFunc={this.setBezierControlPoints} currPoints={this.currCPoints} easeFunc={StrCast(this.activeItem.presEaseFunc)} />
                        </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">
                                <ReactTextareaAutosize
                                    placeholder="Customize prompt for effect suggestions. Leave blank for random results."
                                    className="pres-chatbox"
                                    value={this.animationChat}
                                    onChange={e => {
                                        this.setAnimationChat(e.target.value);
                                    }}
                                    onKeyDown={e => {
                                        this.stopDictation(true);
                                        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={StrCast(Doc.UserDoc().userVariantColor)}
                                onClick={() => {
                                    this.customizeAnimations(this.animationChat);
                                }}
                            />
                        </div>
                    </div>
                    <div
                        className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`}
                        onPointerDown={StopEvent}
                        onPointerUp={StopEvent}
                        onClick={action(e => {
                            e.stopPropagation();
                            this._openMovementDropdown = false;
                            this._openEffectDropdown = false;
                            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 as PresEffectDirection} presEffect={elem.effect as PresEffect} tension={elem.stiffness} friction={elem.damping} mass={elem.mass} infinite>
                                                <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }}></div>
                                            </SlideEffect>
                                        </div>
                                    ))}
                                </div>
                            </div>
                            {/* Effect dropdown */}
                            <Dropdown
                                color={StrCast(Doc.UserDoc().userColor)}
                                formLabel={'Slide Effect'}
                                closeOnSelect={true}
                                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 ${SettingsManager.userColor}` }}>
                                            {StrCast(this.activeItem.presentation_effectDirection)}
                                        </div>
                                    </div>
                                    <div className="presBox-icon-list">
                                        <IconButton
                                            type={Type.TERT}
                                            color={activeItem.presentation_effectDirection === PresEffectDirection.Left ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
                                            tooltip="Left"
                                            icon={<FaArrowRight size={'16px'} />}
                                            onClick={() => this.updateEffectDirection(PresEffectDirection.Left)}
                                        />
                                        <IconButton
                                            type={Type.TERT}
                                            color={activeItem.presentation_effectDirection === PresEffectDirection.Right ? SettingsManager.userVariantColor : SettingsManager.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 ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor}
                                                    tooltip="Top"
                                                    icon={<FaArrowDown size={'16px'} />}
                                                    onClick={() => this.updateEffectDirection(PresEffectDirection.Top)}
                                                />
                                                <IconButton
                                                    type={Type.TERT}
                                                    color={activeItem.presentation_effectDirection === PresEffectDirection.Bottom ? SettingsManager.userVariantColor : SettingsManager.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={StrCast(Doc.UserDoc().userColor)}
                                        formLabel={'Effect Timing'}
                                        closeOnSelect={true}
                                        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();
                                                }}>
                                                <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 });
                                                    }}
                                                    valueLabelDisplay="auto"
                                                />
                                            </div>
                                            <div>Damping</div>
                                            <div
                                                onPointerDown={e => {
                                                    e.stopPropagation();
                                                }}>
                                                <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 });
                                                    }}
                                                    valueLabelDisplay="auto"
                                                />
                                            </div>
                                            <div>Mass</div>
                                            <div
                                                onPointerDown={e => {
                                                    e.stopPropagation();
                                                }}>
                                                <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 });
                                                    }}
                                                    valueLabelDisplay="auto"
                                                />
                                            </div>
                                            Preview Effect
                                            <div className="presBox-option-block presBox-option-center">
                                                <div className="presBox-effect-container">
                                                    <SlideEffect dir={direction as PresEffectDirection} presEffect={effect as PresEffect} tension={timingConfig.stiffness} friction={timingConfig.damping} mass={timingConfig.mass} infinite>
                                                        <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }}></div>
                                                    </SlideEffect>
                                                </div>
                                            </div>
                                        </>
                                    )}
                                </>
                            )}
                        </div>

                        {/* Toggles */}
                        <div className="presBox-option-block">
                            <Toggle
                                formLabel={'Play Audio Annotation'}
                                toggleType={ToggleType.SWITCH}
                                toggleStatus={BoolCast(activeItem.presPlayAudio)}
                                onClick={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))}
                                color={SettingsManager.userColor}
                            />
                            <Toggle
                                formLabel={'Zoom Text Selections'}
                                toggleType={ToggleType.SWITCH}
                                toggleStatus={BoolCast(activeItem.presentation_zoomText)}
                                onClick={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))}
                                color={SettingsManager.userColor}
                            />
                            <Button text="Apply to all" type={Type.TERT} color={StrCast(Doc.UserDoc().userVariantColor)} onClick={() => this.applyTo(this.childDocs)} />
                        </div>
                    </div>
                </>
            );
        }
    }
    @computed get mediaOptionsDropdown() {
        const activeItem = this.activeItem;
        if (activeItem && this.targetDoc) {
            const renderTarget = PresBox.targetRenderedDoc(this.activeItem);
            const clipStart = NumCast(renderTarget.clipStart);
            const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutFieldKey(renderTarget) + '_duration']));
            const config_clipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd);
            return (
                <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                    <div>
                        <div className="ribbon-box">
                            Start {'&'} End Time
                            <div className={'slider-headers'}>
                                <div className="slider-block">
                                    <div className="slider-text" style={{ fontWeight: 500 }}>
                                        Start time (s)
                                    </div>
                                    <div id="startTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
                                        <input
                                            className="presBox-input"
                                            style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
                                            type="number"
                                            readOnly={true}
                                            value={NumCast(activeItem.config_clipStart).toFixed(2)}
                                            onKeyDown={e => e.stopPropagation()}
                                            onChange={action(e => (activeItem.config_clipStart = Number(e.target.value)))}
                                        />
                                    </div>
                                </div>
                                <div className="slider-block">
                                    <div className="slider-text" style={{ fontWeight: 500 }}>
                                        Duration (s)
                                    </div>
                                    <div className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
                                        {Math.round((config_clipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
                                    </div>
                                </div>
                                <div className="slider-block">
                                    <div className="slider-text" style={{ fontWeight: 500 }}>
                                        End time (s)
                                    </div>
                                    <div id="endTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}>
                                        <input
                                            className="presBox-input"
                                            onKeyDown={e => e.stopPropagation()}
                                            style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }}
                                            type="number"
                                            readOnly={true}
                                            value={config_clipEnd.toFixed(2)}
                                            onChange={action(e => (activeItem.config_clipEnd = Number(e.target.value)))}
                                        />
                                    </div>
                                </div>
                            </div>
                            <div className="multiThumb-slider">
                                <input
                                    type="range"
                                    step="0.1"
                                    min={clipStart}
                                    max={clipEnd}
                                    value={config_clipEnd}
                                    style={{ gridColumn: 1, gridRow: 1, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }}
                                    className={`toolbar-slider ${'end'}`}
                                    id="toolbar-slider"
                                    onPointerDown={e => {
                                        this._batch = UndoManager.StartBatch('config_clipEnd');
                                        const endBlock = document.getElementById('endTime');
                                        if (endBlock) {
                                            endBlock.style.backgroundColor = SettingsManager.userVariantColor;
                                        }
                                        e.stopPropagation();
                                    }}
                                    onPointerUp={() => {
                                        this._batch?.end();
                                        const endBlock = document.getElementById('endTime');
                                        if (endBlock) {
                                            endBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
                                        }
                                    }}
                                    onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                                        e.stopPropagation();
                                        activeItem.config_clipEnd = Number(e.target.value);
                                    }}
                                />
                                <input
                                    type="range"
                                    step="0.1"
                                    min={clipStart}
                                    max={clipEnd}
                                    value={NumCast(activeItem.config_clipStart)}
                                    style={{ gridColumn: 1, gridRow: 1 }}
                                    className={`toolbar-slider ${'start'}`}
                                    id="toolbar-slider"
                                    onPointerDown={e => {
                                        this._batch = UndoManager.StartBatch('config_clipStart');
                                        const startBlock = document.getElementById('startTime');
                                        if (startBlock) {
                                            startBlock.style.backgroundColor = SettingsManager.userVariantColor;
                                        }
                                        e.stopPropagation();
                                    }}
                                    onPointerUp={() => {
                                        this._batch?.end();
                                        const startBlock = document.getElementById('startTime');
                                        if (startBlock) {
                                            startBlock.style.backgroundColor = SettingsManager.userBackgroundColor;
                                        }
                                    }}
                                    onChange={(e: React.ChangeEvent<HTMLInputElement>) => {
                                        e.stopPropagation();
                                        activeItem.config_clipStart = Number(e.target.value);
                                    }}
                                />
                            </div>
                            <div className="slider-headers">
                                <div className="slider-text">{clipStart.toFixed(2)} s</div>
                                <div className="slider-text"></div>
                                <div className="slider-text">{clipEnd.toFixed(2)} s</div>
                            </div>
                        </div>
                        <div className="ribbon-final-box">
                            Playback
                            <div className="presBox-subheading">Start playing:</div>
                            <div className="presBox-radioButtons">
                                <div className="checkbox-container">
                                    <input
                                        className="presBox-checkbox"
                                        type="checkbox"
                                        style={{ border: `solid 1px ${SettingsManager.userColor}` }}
                                        onChange={() => (activeItem.presentation_mediaStart = 'manual')}
                                        checked={activeItem.presentation_mediaStart === 'manual'}
                                    />
                                    <div>On click</div>
                                </div>
                                <div className="checkbox-container">
                                    <input
                                        className="presBox-checkbox"
                                        style={{ border: `solid 1px ${SettingsManager.userColor}` }}
                                        type="checkbox"
                                        onChange={() => (activeItem.presentation_mediaStart = 'auto')}
                                        checked={activeItem.presentation_mediaStart === 'auto'}
                                    />
                                    <div>Automatically</div>
                                </div>
                            </div>
                            <div className="presBox-subheading">Stop playing:</div>
                            <div className="presBox-radioButtons">
                                <div className="checkbox-container">
                                    <input
                                        className="presBox-checkbox"
                                        type="checkbox"
                                        style={{ border: `solid 1px ${SettingsManager.userColor}` }}
                                        onChange={() => (activeItem.presentation_mediaStop = 'manual')}
                                        checked={activeItem.presentation_mediaStop === 'manual'}
                                    />
                                    <div>At media end time</div>
                                </div>
                                <div className="checkbox-container">
                                    <input
                                        className="presBox-checkbox"
                                        type="checkbox"
                                        style={{ border: `solid 1px ${SettingsManager.userColor}` }}
                                        onChange={() => (activeItem.presentation_mediaStop = 'auto')}
                                        checked={activeItem.presentation_mediaStop === 'auto'}
                                    />
                                    <div>On slide change</div>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            );
        }
    }
    @computed get newDocumentToolbarDropdown() {
        return (
            <div
                className="presBox-toolbar-dropdown"
                style={{ display: this._newDocumentTools && this.layoutDoc.presentation_status === 'edit' ? 'inline-flex' : 'none' }}
                onClick={e => e.stopPropagation()}
                onPointerUp={e => e.stopPropagation()}
                onPointerDown={e => e.stopPropagation()}>
                <div className="layout-container" style={{ height: 'max-content' }}>
                    <div
                        className="layout"
                        style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
                        onClick={action(() => {
                            this.layout = 'blank';
                            this.createNewSlide(this.layout);
                        })}
                    />
                    <div
                        className="layout"
                        style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
                        onClick={action(() => {
                            this.layout = 'title';
                            this.createNewSlide(this.layout);
                        })}>
                        <div className="title">Title</div>
                        <div className="subtitle">Subtitle</div>
                    </div>
                    <div
                        className="layout"
                        style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
                        onClick={action(() => {
                            this.layout = 'header';
                            this.createNewSlide(this.layout);
                        })}>
                        <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
                            Section header
                        </div>
                    </div>
                    <div
                        className="layout"
                        style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }}
                        onClick={action(() => {
                            this.layout = 'content';
                            this.createNewSlide(this.layout);
                        })}>
                        <div className="title" style={{ alignSelf: 'center' }}>
                            Title
                        </div>
                        <div className="content">Text goes here</div>
                    </div>
                </div>
            </div>
        );
    }

    @observable openLayouts: boolean = false;
    @observable addFreeform: boolean = true;
    @observable layout: string = '';
    @observable title: string = '';

    @computed get newDocumentDropdown() {
        return (
            <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                <div className="ribbon-box">
                    Slide Title: <br></br>
                    <input
                        className="ribbon-textInput"
                        placeholder="..."
                        type="text"
                        name="fname"
                        onChange={e => {
                            e.stopPropagation();
                            e.preventDefault();
                            runInAction(() => (this.title = e.target.value));
                        }}></input>
                </div>
                <div className="ribbon-box">
                    Choose type:
                    <div className="ribbon-doubleButton">
                        <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
                            Text
                        </div>
                        <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}>
                            Freeform
                        </div>
                    </div>
                </div>
                <div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}>
                    Preset layouts:
                    <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}>
                        <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} />
                        <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}>
                            <div className="title">Title</div>
                            <div className="subtitle">Subtitle</div>
                        </div>
                        <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}>
                            <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}>
                                Section header
                            </div>
                        </div>
                        <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}>
                            <div className="title" style={{ alignSelf: 'center' }}>
                                Title
                            </div>
                            <div className="content">Text goes here</div>
                        </div>
                        <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}>
                            <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}>
                                Title
                            </div>
                            <div className="content" style={{ gridColumn: 1, gridRow: 2 }}>
                                Column one text
                            </div>
                            <div className="content" style={{ gridColumn: 2, gridRow: 2 }}>
                                Column two text
                            </div>
                        </div>
                    </div>
                    <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}>
                        <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} />
                    </div>
                </div>
                <div className="ribbon-final-box">
                    <div
                        className={this.title !== '' && ((this.addFreeform && this.layout !== '') || !this.addFreeform) ? 'ribbon-final-button-hidden' : 'ribbon-final-button'}
                        onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}>
                        Create New Slide
                    </div>
                </div>
            </div>
        );
    }

    createNewSlide = (layout?: string, title?: string, freeform?: boolean) => {
        let doc = undefined;
        if (layout) doc = this.createTemplate(layout);
        if (freeform && layout) doc = this.createTemplate(layout, title);
        if (!freeform && !layout) doc = Docs.Create.TextDocument('', { _nativeWidth: 400, _width: 225, title: title });
        if (doc) {
            const tabMap = CollectionDockingView.Instance?.tabMap;
            const tab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc;
            const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? tab;
            const data = Cast(presCollection?.data, listSpec(Doc));
            const config_data = Cast(this.Document.data, listSpec(Doc));
            if (data && config_data) {
                data.push(doc);
                this._props.pinToPres(doc, {});
                this.gotoDocument(this.childDocs.length, this.activeItem);
            } else {
                this._props.addDocTab(doc, OpenWhere.addRight);
            }
        }
    };

    createTemplate = (layout: string, input?: string) => {
        const x = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.x) : 0;
        const y = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.y) + NumCast(this.targetDoc._height) + 20 : 0;
        const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _text_fontSize: '24pt' });
        const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _text_fontSize: '16pt' });
        const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _text_fontSize: '20pt' });
        const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _text_fontSize: '24pt' });
        const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _text_fontSize: '14pt' });
        const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _text_fontSize: '14pt' });
        const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _text_fontSize: '14pt' });
        // prettier-ignore
        switch (layout) {
            case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y });
            case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
            case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
            case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y });
            case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y })
        }
    };

    // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view)
    @computed get presentDropdown() {
        return (
            <div className={`dropdown-play ${this._presentTools ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
                <div
                    className="dropdown-play-button"
                    onClick={undoBatch(
                        action(() => {
                            this.enterMinimize();
                            this.turnOffEdit(true);
                            this.gotoDocument(this.itemIndex, this.activeItem);
                        })
                    )}>
                    Mini-player
                </div>
                <div
                    className="dropdown-play-button"
                    onClick={undoBatch(
                        action(() => {
                            this.layoutDoc.presentation_status = 'manual';
                            this.initializePresState(this.itemIndex);
                            this.turnOffEdit(true);
                            this.gotoDocument(this.itemIndex, this.activeItem);
                        })
                    )}>
                    Sidebar player
                </div>
            </div>
        );
    }

    @action
    turnOffEdit = (paths?: boolean) => paths && this.togglePath(true); // Turn off paths

    @computed
    get toolbarWidth(): number {
        return this._props.PanelWidth();
    }

    @action
    toggleProperties = () => (SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250);

    @action
    openProperties = () => {
        // need to also focus slide
        SettingsManager.Instance.propertiesWidth = 250;
    };

    @computed get toolbar() {
        const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left';
        const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel';
        const mode = StrCast(this.Document._type_collection) as CollectionViewType;
        const isMini: boolean = this.toolbarWidth <= 100;
        const activeColor = SettingsManager.userVariantColor;
        const inactiveColor = lightOrDark(SettingsManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SettingsManager.userBackgroundColor;
        return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.Document) ? null : (
            <div id="toolbarContainer" className={'presBox-toolbar'}>
                {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}>
                    <FontAwesomeIcon icon={"plus"} />
                    <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} />
                </div></Tooltip> */}
                <Tooltip title={<div className="dash-tooltip">View paths</div>}>
                    <div
                        style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }}
                        className="toolbar-button"
                        onClick={this.childDocs.length > 1 ? () => this.togglePath() : undefined}>
                        <FontAwesomeIcon icon={'exchange-alt'} />
                    </div>
                </Tooltip>
                {isMini ? null : (
                    <>
                        <div className="toolbar-divider" />
                        <Tooltip title={<div className="dash-tooltip">{this._presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}>
                            <div className="toolbar-button" style={{ cursor: this._presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}>
                                <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} />
                            </div>
                        </Tooltip>
                        <Tooltip title={<div className="dash-tooltip">{propTitle}</div>}>
                            <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}>
                                <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} />
                            </div>
                        </Tooltip>
                    </>
                )}
            </div>
        );
    }

    /**
     * Top panel containes:
     * viewPicker: The option to choose between List and Slides view for the presentaiton trail
     * presentPanel: The button to start the presentation / open minimized view of the presentation
     */
    @computed get topPanel() {
        const mode = StrCast(this.Document._type_collection) as CollectionViewType;
        const isMini: boolean = this.toolbarWidth <= 100;
        return (
            <div className={`presBox-buttons${Doc.IsInMyOverlay(this.Document) ? ' inOverlay' : ''}`} style={{ background: Doc.ActivePresentation === this.Document ? Colors.LIGHT_BLUE : undefined }}>
                {isMini ? null : (
                    <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presentation_status === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}>
                        <option onPointerDown={StopEvent} value={CollectionViewType.Stacking}>
                            List
                        </option>
                        <option onPointerDown={StopEvent} value={CollectionViewType.Tree}>
                            Tree
                        </option>
                        {Doc.noviceMode ? null : (
                            <option onPointerDown={StopEvent} value={CollectionViewType.Carousel3D}>
                                3D Carousel
                            </option>
                        )}
                    </select>
                )}
                <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}>
                    <span className={`presBox-button ${this.layoutDoc.presentation_status === PresStatus.Edit ? 'present' : ''}`}>
                        <div
                            className="presBox-button-left"
                            onClick={undoBatch(() => {
                                if (this.childDocs.length) {
                                    this.layoutDoc.presentation_status = 'manual';
                                    this.initializePresState(this.itemIndex);
                                    this.gotoDocument(this.itemIndex, this.activeItem);
                                }
                            })}>
                            <FontAwesomeIcon icon={'play-circle'} />
                            <div style={{ display: this._props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}>&nbsp; Present</div>
                        </div>
                        {mode === CollectionViewType.Carousel3D || isMini ? null : (
                            <div
                                className={`presBox-button-right ${this._presentTools ? 'active' : ''}`}
                                onClick={action(() => {
                                    if (this.childDocs.length) this._presentTools = !this._presentTools;
                                })}>
                                <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} />
                                {this.presentDropdown}
                            </div>
                        )}
                    </span>
                    {this.playButtons}
                </div>
            </div>
        );
    }

    @computed get playButtons() {
        const presEnd =
            !this.layoutDoc.presLoop &&
            this.itemIndex === this.childDocs.length - 1 &&
            (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
        const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0;
        const inOverlay = Doc.IsInMyOverlay(this.Document);
        // Case 1: There are still other frames and should go through all frames before going to next slide
        return (
            <div className="presPanelOverlay" style={{ display: this.layoutDoc.presentation_status !== 'edit' ? 'inline-flex' : 'none' }}>
                <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
                    <div
                        className="presPanel-button"
                        style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }}
                        onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
                        <FontAwesomeIcon icon={'redo-alt'} />
                    </div>
                </Tooltip>
                <div className="presPanel-divider" />
                <div
                    className="presPanel-button"
                    style={{ opacity: presStart ? 0.4 : 1 }}
                    onPointerDown={e =>
                        setupMoveUpEvents(
                            this,
                            e,
                            returnFalse,
                            emptyFunction,
                            () => {
                                this.back();
                                if (this._presTimer) {
                                    clearTimeout(this._presTimer);
                                    this.layoutDoc.presentation_status = PresStatus.Manual;
                                }
                                e.stopPropagation();
                            },
                            false,
                            false
                        )
                    }>
                    <FontAwesomeIcon icon={'arrow-left'} />
                </div>
                <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
                    <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.startOrPause(true), false, false)}>
                        <FontAwesomeIcon color={this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'red' : undefined} icon={this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'pause' : 'play'} />
                    </div>
                </Tooltip>
                <div
                    className="presPanel-button"
                    style={{ opacity: presEnd ? 0.4 : 1 }}
                    onPointerDown={e =>
                        setupMoveUpEvents(
                            this,
                            e,
                            returnFalse,
                            emptyFunction,
                            () => {
                                this.next();
                                if (this._presTimer) {
                                    clearTimeout(this._presTimer);
                                    this.layoutDoc.presentation_status = PresStatus.Manual;
                                }
                                e.stopPropagation();
                            },
                            false,
                            false
                        )
                    }>
                    <FontAwesomeIcon icon={'arrow-right'} />
                </div>
                <div className="presPanel-divider"></div>
                <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
                    <div
                        className="presPanel-button"
                        style={{ border: 'solid 1px white' }}
                        onPointerDown={e =>
                            setupMoveUpEvents(
                                this,
                                e,
                                returnFalse,
                                emptyFunction,
                                () => {
                                    this.nextSlide(0);
                                },
                                false,
                                false
                            )
                        }>
                        <b>1</b>
                    </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.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
                </div>
                <div className="presPanel-divider"></div>
                {this._props.PanelWidth() > 250 ? (
                    <div
                        className="presPanel-button-text"
                        onClick={undoBatch(
                            action(() => {
                                this.layoutDoc.presentation_status = PresStatus.Edit;
                                clearTimeout(this._presTimer);
                            })
                        )}>
                        EXIT
                    </div>
                ) : (
                    <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.exitClicked, false, false)}>
                        <FontAwesomeIcon icon={'times'} />
                    </div>
                )}
            </div>
        );
    }

    @action
    startOrPause = (makeActive = true) => {
        makeActive && this.updateCurrentPresentation();
        if (!this.layoutDoc.presentation_status || this.layoutDoc.presentation_status === PresStatus.Manual || this.layoutDoc.presentation_status === PresStatus.Edit) this.startPresentation(this.itemIndex);
        else this.pauseAutoPres();
    };

    @action
    prevClicked = (e: PointerEvent) => {
        this.back();
        if (this._presTimer) {
            clearTimeout(this._presTimer);
            this.layoutDoc.presentation_status = PresStatus.Manual;
        }
    };

    @action
    nextClicked = (e: PointerEvent) => {
        this.next();
        if (this._presTimer) {
            clearTimeout(this._presTimer);
            this.layoutDoc.presentation_status = PresStatus.Manual;
        }
    };

    @undoBatch
    exitClicked = () => {
        this.layoutDoc.presentation_status = this._exitTrail?.() ?? this.exitMinimize();
        clearTimeout(this._presTimer);
    };

    AddToMap = (treeViewDoc: Doc, index: number[]) => {
        if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
        var indexNum = 0;
        for (let i = 0; i < index.length; i++) {
            indexNum += index[i] * 10 ** -i;
        }
        if (this._treeViewMap.get(treeViewDoc) !== indexNum) {
            this._treeViewMap.set(treeViewDoc, indexNum);
            const sorted = this.sort(this._treeViewMap);
            const curList = DocListCast(this.dataDoc[this.presFieldKey]);
            if (sorted.length !== curList.length || sorted.some((doc, ind) => doc !== curList[ind])) {
                this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs
            }
        }
    };

    SlideIndex = (slideDoc: Doc) => DocListCast(this.dataDoc[this.presFieldKey]).indexOf(slideDoc);

    RemFromMap = (treeViewDoc: Doc, index: number[]) => {
        if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element.
        if (!this._unmounting && this.isTree) {
            this._treeViewMap.delete(treeViewDoc);
            this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap));
        }
    };

    sort = (treeView_Map: Map<Doc, number>) => [...treeView_Map.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]);

    render() {
        // needed to ensure that the childDocs are loaded for looking up fields
        this.childDocs.slice();
        const mode = StrCast(this.Document._type_collection) as CollectionViewType;
        const presEnd =
            !this.layoutDoc.presLoop &&
            this.itemIndex === this.childDocs.length - 1 &&
            (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0));
        const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0;
        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
            <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
                <div
                    className="presPanelOverlay"
                    style={{ display: 'inline-flex', height: 30, background: Doc.ActivePresentation === this.Document ? 'green' : '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}>
                    <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}>
                        <div
                            className="presPanel-button"
                            style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }}
                            onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
                            <FontAwesomeIcon icon={'redo-alt'} />
                        </div>
                    </Tooltip>
                    <div className="presPanel-divider"></div>
                    <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}>
                        <FontAwesomeIcon icon={'arrow-left'} />
                    </div>
                    <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}>
                        <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.startOrPause(true), false, false)}>
                            <FontAwesomeIcon icon={this.layoutDoc.presentation_status === 'auto' ? 'pause' : 'play'} />
                        </div>
                    </Tooltip>
                    <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}>
                        <FontAwesomeIcon icon={'arrow-right'} />
                    </div>
                    <div className="presPanel-divider"></div>
                    <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}>
                        <div className="presPanel-button" style={{ border: 'solid 1px white' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}>
                            <b>1</b>
                        </div>
                    </Tooltip>
                    <div className="presPanel-button-text">
                        Slide {this.itemIndex + 1}
                        {this.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(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)}>
                        EXIT
                    </div>
                </div>
            </div>
        ) : (
            <div className="presBox-cont" style={{ minWidth: Doc.IsInMyOverlay(this.Document) ? PresBox.minimizedWidth : undefined }}>
                {this.topPanel}
                {this.toolbar}
                {this.newDocumentToolbarDropdown}
                <div className="presBox-listCont">
                    <div className="Slide">
                        {mode !== CollectionViewType.Invalid ? (
                            <CollectionView
                                {...this._props}
                                PanelWidth={this._props.PanelWidth}
                                PanelHeight={this.panelHeight}
                                childIgnoreNativeSize={true}
                                moveDocument={returnFalse}
                                ignoreUnrendered={true}
                                childDragAction={dropActionType.move}
                                setContentViewBox={emptyFunction}
                                //childLayoutFitWidth={returnTrue}
                                childOpacity={returnOne}
                                childClickScript={PresBox.navigateToDocScript}
                                childLayoutTemplate={this.childLayoutTemplate}
                                childXPadding={Doc.IsComicStyle(this.Document) ? 20 : undefined}
                                filterAddDocument={this.addDocumentFilter}
                                removeDocument={returnFalse}
                                dontRegisterView={true}
                                focus={this.focusElement}
                                ScreenToLocalTransform={this.getTransform}
                                AddToMap={this.AddToMap}
                                RemFromMap={this.RemFromMap}
                                hierarchyIndex={emptyPath as any as number[]}
                            />
                        ) : null}
                    </div>
                    {/* {
                        // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack
                        <Tooltip title={<div className="dash-tooltip">{'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}</div>}>
                            <button className="add-slide-button" onPointerDown={this.startMarqueeCreateSlide}>
                                + NEW SLIDE
                            </button>
                        </Tooltip>
                    } */}
                </div>
                {/* presbox chatbox */}
                {this.chatActive && <div className="presBox-chatbox"></div>}
            </div>
        );
    }
}

ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) {
    PresBox.NavigateToTarget(bestTarget, activeItem);
});
