import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; import { action, computed, IReactionDisposer, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { ColorState, SketchPicker } from 'react-color'; import { Bounce, Fade, Flip, LightSpeed, Roll, Rotate, Zoom } from 'react-reveal'; import { Doc, DocListCast, DocListCastAsync, FieldResult } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; import { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { DocumentManager } from '../../../util/DocumentManager'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SelectionManager } from '../../../util/SelectionManager'; import { SettingsManager } from '../../../util/SettingsManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { MarqueeViewBounds } from '../../collections/collectionFreeForm'; import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu'; import { CollectionView } from '../../collections/CollectionView'; import { TabDocView } from '../../collections/TabDocView'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; import { CollectionFreeFormDocumentView } from '../CollectionFreeFormDocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import './PresBox.scss'; import { PresEffect, PresMovement, PresStatus } from './PresEnums'; export interface PinProps { audioRange?: boolean; activeFrame?: number; hidePresBox?: boolean; pinWithView?: PinViewProps; pinDocView?: boolean; // whether the current view specs of the document should be saved the pinned document panelWidth?: number; // panel width and height of the document (used to compute the bounds of the pinned view area) panelHeight?: number; } export interface PinViewProps { bounds: MarqueeViewBounds; scale: number; } @observer export class PresBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } /** * transitions & effects for documents * @param renderDoc * @param layoutDoc */ static renderEffectsDoc(renderDoc: any, layoutDoc: Doc, presDoc: Doc) { const effectProps = { left: presDoc.presEffectDirection === PresEffect.Left, right: presDoc.presEffectDirection === PresEffect.Right, top: presDoc.presEffectDirection === PresEffect.Top, bottom: presDoc.presEffectDirection === PresEffect.Bottom, opposite: true, delay: presDoc.presTransition, // when: this.layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc, }; //prettier-ignore switch (presDoc.presEffect) { default: case PresEffect.None: return renderDoc; case PresEffect.Zoom: return {renderDoc}; case PresEffect.Fade: return {renderDoc}; case PresEffect.Flip: return {renderDoc}; case PresEffect.Rotate: return {renderDoc}; case PresEffect.Bounce: return {renderDoc}; case PresEffect.Roll: return {renderDoc}; case PresEffect.Lightspeed: return {renderDoc}; } } public static EffectsProvider(layoutDoc: Doc, renderDoc: any) { return PresBox.Instance && layoutDoc === PresBox.Instance.childDocs[PresBox.Instance.itemIndex]?.presentationTargetDoc ? PresBox.renderEffectsDoc(renderDoc, layoutDoc, PresBox.Instance.childDocs[PresBox.Instance.itemIndex]) : renderDoc; } private _disposers: { [name: string]: IReactionDisposer } = {}; constructor(props: any) { super(props); if ((Doc.ActivePresentation = this.rootDoc)) runInAction(() => (PresBox.Instance = this)); this.props.Document.presentationFieldKey = this.fieldKey; // provide info to the presElement script so that it can look up rendering information about the presBox } @observable public static Instance: PresBox; @observable _isChildActive = false; @observable _moveOnFromAudio: boolean = true; @observable _presTimer!: NodeJS.Timeout; @observable _presKeyEventsActive: boolean = false; @observable _eleArray: HTMLElement[] = []; @observable _dragArray: HTMLElement[] = []; @observable _pathBoolean: boolean = false; @observable _expandBoolean: boolean = false; @observable static startMarquee: boolean = false; // onclick "+ new slide" in presentation mode, set as true, then when marquee selection finish, onPointerUp automatically triggers PinWithView @observable private transitionTools: boolean = false; @observable private newDocumentTools: boolean = false; @observable private progressivizeTools: boolean = false; @observable private openMovementDropdown: boolean = false; @observable private openEffectDropdown: boolean = false; @observable private presentTools: boolean = false; @computed get isTreeOrStack() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any); } @computed get isTree() { return this.layoutDoc._viewType === CollectionViewType.Tree; } @computed get presFieldKey() { return StrCast(this.layoutDoc.presFieldKey, 'data'); } @computed get childDocs() { return DocListCast(this.rootDoc[this.presFieldKey]); } @observable _treeViewMap: Map = new Map(); @computed get tagDocs() { const tagDocs: Doc[] = []; for (const doc of this.childDocs) { const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); tagDocs.push(tagDoc); } return tagDocs; } @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } @computed get activeItem() { return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); } @computed get targetDoc() { return Cast(this.activeItem?.presentationTargetDoc, Doc, null); } @computed get scrollable(): boolean { if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; else return false; } @computed get panable(): boolean { if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; else return false; } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); } @computed get isPres(): boolean { document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); if (this.selectedDoc?.type === DocumentType.PRES) { document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); document.addEventListener('keydown', PresBox.keyEventsWrapper, true); return true; } return false; } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } _selectedArray = new ObservableSet(); clearSelectedArray = () => this._selectedArray.clear(); addToSelectedArray = (doc: Doc) => this._selectedArray.add(doc); removeFromSelectedArray = (doc: Doc) => this._selectedArray.delete(doc); _unmounting = false; @action componentWillUnmount() { this._unmounting = true; document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); this._presKeyEventsActive = false; this.resetPresentation(); // Turn of progressivize editors this.turnOffEdit(true); Object.values(this._disposers).forEach(disposer => disposer?.()); } @action componentDidMount() { this.props.setContentView?.(this); this._unmounting = false; this.rootDoc._forceRenderEngine = 'timeline'; this.layoutDoc.presStatus = PresStatus.Edit; this.layoutDoc._gridGap = 0; this.layoutDoc._yMargin = 0; this.turnOffEdit(true); DocListCastAsync(Doc.MyTrails.data).then(pres => !pres?.includes(this.rootDoc) && Doc.AddDocToList(Doc.MyTrails, 'data', this.rootDoc)); this._disposers.selection = reaction( () => SelectionManager.Views(), views => views.some(view => view.props.Document === this.rootDoc) && this.updateCurrentPresentation() ); } @action updateCurrentPresentation = (pres?: Doc) => { if (pres) Doc.ActivePresentation = pres; else Doc.ActivePresentation = this.rootDoc; document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); document.addEventListener('keydown', PresBox.keyEventsWrapper, true); this._presKeyEventsActive = true; PresBox.Instance = this; }; // There are still other internal frames and should go through all frames before going to next slide nextInternalFrame = (targetDoc: Doc, activeItem: Doc) => { const currentFrame = Cast(targetDoc?._currentFrame, 'number', null); const childDocs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); targetDoc._viewTransition = 'all 1s'; setTimeout(() => (targetDoc._viewTransition = undefined), 1010); this.nextKeyframe(targetDoc, activeItem); if (activeItem.presProgressivize) CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, targetDoc); else targetDoc.keyFrameEditing = true; }; _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.presEndTime) - NumCast(activeItem.presStartTime); if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) { const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.presStartTime), NumCast(activeItem.presStartTime) + duration); } }; stopTempMedia = (targetDocField: FieldResult) => { const targetDoc = Cast(targetDocField, Doc, null); if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) { const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); targMedia?.ComponentView?.Pause?.(); } }; //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 = (activeNext: Doc) => { const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); console.info('nextSlide', activeNext.title, targetNext?.title); let nextSelected = this.itemIndex + 1; this.gotoDocument(nextSelected, this.activeItem); for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) { if (!this.childDocs[nextSelected].groupWithUp) { break; } else { console.log('Title: ' + this.childDocs[nextSelected].title); this.gotoDocument(nextSelected, this.activeItem, true); } } }; // Called when the user activates 'next' - to move to the next part of the pres. trail @action next = () => { const activeNext = Cast(this.childDocs[this.itemIndex + 1], Doc, null); const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const lastFrame = Cast(targetDoc?.lastFrame, 'number', null); const curFrame = NumCast(targetDoc?._currentFrame); let internalFrames: boolean = false; if (activeItem.presProgressivize || activeItem.zoomProgressivize || targetDoc.scrollProgressivize) internalFrames = true; if (internalFrames && lastFrame !== undefined && curFrame < lastFrame) { // Case 1: There are still other frames and should go through all frames before going to next slide this.nextInternalFrame(targetDoc, activeItem); } else if (this.childDocs[this.itemIndex + 1] !== undefined) { // Case 2: No more frames in current doc and next slide is defined, therefore move to next slide this.nextSlide(activeNext); } else if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) { // Case 3: Last slide and presLoop is toggled ON or it is in Edit mode this.gotoDocument(0, this.activeItem); } }; // Called when the user activates 'back' - to move to the previous part of the pres. trail @action back = () => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const prevItem = Cast(this.childDocs[Math.max(0, this.itemIndex - 1)], Doc, null); const prevTargetDoc = Cast(prevItem.presentationTargetDoc, Doc, null); const lastFrame = Cast(targetDoc.lastFrame, 'number', null); const curFrame = NumCast(targetDoc._currentFrame); let prevSelected = this.itemIndex; // Functionality for group with up let didZoom = activeItem.presMovement; for (; !didZoom && prevSelected > 0 && this.childDocs[prevSelected].groupButton; prevSelected--) { didZoom = this.childDocs[prevSelected].presMovement; } if (lastFrame !== undefined && curFrame >= 1) { // Case 1: There are still other frames and should go through all frames before going to previous slide this.prevKeyframe(targetDoc, activeItem); } else 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.gotoDocument(prevSelected, activeItem); if (NumCast(prevTargetDoc.lastFrame) > 0) prevTargetDoc._currentFrame = NumCast(prevTargetDoc.lastFrame); } 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.gotoDocument(this.childDocs.length - 1, activeItem); } }; //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. public gotoDocument = action((index: number, from?: Doc, group?: boolean) => { Doc.UnBrushAllDocs(); if (index >= 0 && index < this.childDocs.length) { this.rootDoc._itemIndex = index; const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (activeItem.presActiveFrame !== undefined) { const context = DocCast(DocCast(activeItem.presentationTargetDoc).context); context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(activeItem.presActiveFrame)); } if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) { DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia); } if (from?.mediaStop === 'auto' && this.layoutDoc.presStatus !== PresStatus.Edit) { this.stopTempMedia(from.presentationTargetDoc); } // If next slide is audio / video 'Play automatically' then the next slide should be played if (this.layoutDoc.presStatus !== PresStatus.Edit && (targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && activeItem.mediaStart === 'auto') { this.startTempMedia(targetDoc, activeItem); } if (targetDoc) { Doc.linkFollowHighlight(targetDoc.annotationOn instanceof Doc ? [targetDoc, targetDoc.annotationOn] : targetDoc); targetDoc && runInAction(() => { if (activeItem.presMovement === PresMovement.Jump) targetDoc.focusSpeed = 0; else targetDoc.focusSpeed = activeItem.presTransition ? activeItem.presTransition : 500; }); setTimeout(() => (targetDoc.focusSpeed = 500), this.activeItem.presTransition ? NumCast(this.activeItem.presTransition) + 10 : 510); } if (targetDoc?.lastFrame !== undefined) { targetDoc._currentFrame = 0; } if (!group) this.clearSelectedArray(); this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list this.onHideDocument(); //Handles hide after/before } }); _navTimer!: NodeJS.Timeout; navigateToView = (targetDoc: Doc, activeItem: Doc) => { clearTimeout(this._navTimer); const bestTarget = DocumentManager.Instance.getFirstDocumentView(targetDoc)?.props.Document; if (bestTarget) console.log(bestTarget.title, bestTarget.type); else console.log('no best target'); if (bestTarget) this._navTimer = PresBox.navigateToDoc(bestTarget, activeItem, false); }; // navigates to the bestTarget document by making sure it is on screen, // then it applies the view specs stored in activeItem to @action static navigateToDoc(bestTarget: Doc, activeItem: Doc, jumpToDoc: boolean) { if (bestTarget.type === DocumentType.PDF || bestTarget.type === DocumentType.WEB || bestTarget.type === DocumentType.RTF || bestTarget._viewType === CollectionViewType.Stacking) { bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; bestTarget._scrollTop = activeItem.presPinViewScroll; } else if (bestTarget.type === DocumentType.COMPARISON) { bestTarget._clipWidth = activeItem.presPinClipWidth; } else if ([DocumentType.AUDIO, DocumentType.VID].includes(bestTarget.type as any)) { bestTarget._currentTimecode = activeItem.presStartTime; } else { const contentBounds = Cast(activeItem.contentBounds, listSpec('number')); bestTarget._viewTransition = activeItem.presTransition ? `transform ${activeItem.presTransition}ms` : 'all 0.5s'; if (contentBounds) { bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2; bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2; const dv = DocumentManager.Instance.getDocumentView(bestTarget); if (dv) { bestTarget._viewScale = Math.min(dv.props.PanelHeight() / (contentBounds[3] - contentBounds[1]), dv.props.PanelWidth() / (contentBounds[2] - contentBounds[0])); } } else { bestTarget._panX = activeItem.presPinViewX; bestTarget._panY = activeItem.presPinViewY; bestTarget._viewScale = activeItem.presPinViewScale; } } return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); } /** * 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. */ navigateToElement = async (curDoc: Doc) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const srcContext = Cast(targetDoc.context, Doc, null) ?? Cast(Cast(targetDoc.annotationOn, Doc, null)?.context, Doc, null); const presCollection = Cast(this.layoutDoc.presCollection, Doc, null); const collectionDocView = presCollection ? DocumentManager.Instance.getDocumentView(presCollection) : undefined; const includesDoc: boolean = DocListCast(presCollection?.data).includes(targetDoc); const tab = CollectionDockingView.Instance && Array.from(CollectionDockingView.Instance.tabMap).find(tab => tab.DashDoc === srcContext); this.turnOffEdit(); // Handles the setting of presCollection if (includesDoc) { //Case 1: Pres collection should not change as it is already the same } else if (tab !== undefined) { // Case 2: Pres collection should update this.layoutDoc.presCollection = srcContext; } const presStatus = this.rootDoc.presStatus; const selViewCache = Array.from(this._selectedArray); const dragViewCache = Array.from(this._dragArray); const eleViewCache = Array.from(this._eleArray); const self = this; const resetSelection = action(() => { const presDocView = DocumentManager.Instance.getDocumentView(self.rootDoc); if (presDocView) SelectionManager.SelectView(presDocView, false); self.rootDoc.presStatus = presStatus; self.clearSelectedArray(); selViewCache.forEach(doc => self.addToSelectedArray(doc)); self._dragArray.splice(0, self._dragArray.length, ...dragViewCache); self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); }); const openInTab = (doc: Doc, finished?: () => void) => { collectionDocView ? collectionDocView.props.addDocTab(doc, '') : this.props.addDocTab(doc, ''); this.layoutDoc.presCollection = targetDoc; // this still needs some fixing setTimeout(resetSelection, 500); if (doc !== targetDoc) { setTimeout(finished ?? emptyFunction, 100); /// give it some time to create the targetDoc if we're opening up its context } else { finished?.(); } }; // If openDocument is selected then it should open the document for the user if (activeItem.openDocument) { LightboxView.SetLightboxDoc(targetDoc); // openInTab(targetDoc); } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { LightboxView.SetLightboxDoc(undefined); await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true); // documents open in new tab instead of on right } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { LightboxView.SetLightboxDoc(undefined); //awaiting jump so that new scale can be found, since jumping is async await DocumentManager.Instance.jumpToDocument(targetDoc, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection, undefined, true, NumCast(curDoc.presZoom)); // documents open in new tab instead of on right } // After navigating to the document, if it is added as a presPinView then it will // adjust the pan and scale to that of the pinView when it was added. if (activeItem.presPinView) { console.log(targetDoc.title); console.log('presPinView in PresBox.tsx:420'); // if targetDoc is not displayed but one of its aliases is, then we need to modify that alias, not the original target this.navigateToView(targetDoc, activeItem); } }; /** * Uses the viewfinder to progressivize through the different views of a single collection. * @param activeItem: document for which internal zoom is used */ zoomProgressivizeNext = (activeItem: Doc) => { const targetDoc: Doc = this.targetDoc; const srcContext = Cast(targetDoc?.context, Doc, null); const docView = DocumentManager.Instance.getDocumentView(targetDoc); const vfLeft = this.checkList(targetDoc, activeItem['viewfinder-left-indexed']); const vfWidth = this.checkList(targetDoc, activeItem['viewfinder-width-indexed']); const vfTop = this.checkList(targetDoc, activeItem['viewfinder-top-indexed']); const vfHeight = this.checkList(targetDoc, activeItem['viewfinder-height-indexed']); // Case 1: document that is not a Golden Layout tab if (srcContext) { const srcDocView = DocumentManager.Instance.getDocumentView(srcContext); if (srcDocView) { const layoutdoc = Doc.Layout(targetDoc); const panelWidth: number = srcDocView.props.PanelWidth(); const panelHeight: number = srcDocView.props.PanelHeight(); const newPanX = NumCast(targetDoc.x) + NumCast(layoutdoc._width) / 2; const newPanY = NumCast(targetDoc.y) + NumCast(layoutdoc._height) / 2; const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); srcContext._panX = newPanX + (vfLeft + vfWidth / 2); srcContext._panY = newPanY + (vfTop + vfHeight / 2); srcContext._viewScale = newScale; } } // Case 2: document is the containing collection if (docView && !srcContext) { const panelWidth: number = docView.props.PanelWidth(); const panelHeight: number = docView.props.PanelHeight(); const newScale = 0.9 * Math.min(Number(panelWidth) / vfWidth, Number(panelHeight) / vfHeight); targetDoc._panX = vfLeft + vfWidth / 2; targetDoc._panY = vfTop + vfWidth / 2; targetDoc._viewScale = newScale; } const resize = document.getElementById('resizable'); if (resize) { resize.style.width = vfWidth + 'px'; resize.style.height = vfHeight + 'px'; resize.style.top = vfTop + 'px'; resize.style.left = vfLeft + 'px'; } }; /** * For 'Hide Before' and 'Hide After' buttons making sure that * they are hidden each time the presentation is updated. */ @action onHideDocument = () => { this.childDocs.forEach((doc, index) => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); //if (tagDoc) tagDoc.opacity = 1; const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc); const curInd: number = itemIndexes.indexOf(index); if (tagDoc === this.layoutDoc.presCollection) { tagDoc.opacity = 1; } else { if (curDoc.presHideBefore) { if (itemIndexes.length > 1 && curInd !== 0) { tagDoc.opacity = 1; } else { if (index > this.itemIndex) { tagDoc.opacity = 0; } else if (index === this.itemIndex || !curDoc.presHideAfter) { tagDoc.opacity = 1; } } } if (curDoc.presHideAfter) { if (itemIndexes.length > 1 && curInd !== itemIndexes.length - 1) { tagDoc.opacity = 1; } else { if (index < this.itemIndex) { tagDoc.opacity = 0; } else if (index === this.itemIndex || !curDoc.presHideBefore) { tagDoc.opacity = 1; } } } } }); }; //The function that starts or resets presentaton functionally, depending on presStatus of the layoutDoc @action startAutoPres = (startSlide: number) => { this.updateCurrentPresentation(); let activeItem: Doc = this.activeItem; let targetDoc: Doc = this.targetDoc; let duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); const timer = (ms: number) => new Promise(res => (this._presTimer = setTimeout(res, ms))); const load = async () => { // Wrap the loop into an async function for this to work for (var i = startSlide; i < this.childDocs.length; i++) { activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); targetDoc = Cast(activeItem.presentationTargetDoc, Doc, null); duration = NumCast(activeItem.presDuration) + NumCast(activeItem.presTransition); if (duration < 100) { duration = 2500; } if (NumCast(targetDoc.lastFrame) > 0) { for (var f = 0; f < NumCast(targetDoc.lastFrame); f++) { await timer(duration / NumCast(targetDoc.lastFrame)); this.next(); } } await timer(duration); this.next(); // then the created Promise can be awaited if (i === this.childDocs.length - 1) { setTimeout(() => { clearTimeout(this._presTimer); if (this.layoutDoc.presStatus === 'auto' && !this.layoutDoc.presLoop) this.layoutDoc.presStatus = PresStatus.Manual; else if (this.layoutDoc.presLoop) this.startAutoPres(0); }, duration); } } }; this.layoutDoc.presStatus = PresStatus.Autoplay; this.startPresentation(startSlide); this.gotoDocument(startSlide, activeItem); load(); }; // The function pauses the auto presentation @action pauseAutoPres = () => { if (this.layoutDoc.presStatus === PresStatus.Autoplay) { if (this._presTimer) clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; this.layoutDoc.presLoop = false; 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.rootDoc._itemIndex = 0; this.childDocs .map(doc => Cast(doc.presentationTargetDoc, Doc, null)) .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 = (srcContext: Doc, off?: boolean) => { if (off) { this._pathBoolean = false; srcContext.presPathView = false; } else { runInAction(() => (this._pathBoolean = !this._pathBoolean)); srcContext.presPathView = this._pathBoolean; } }; // The function allows for expanding the view of pres on toggle @action toggleExpandMode = () => { runInAction(() => (this._expandBoolean = !this._expandBoolean)); this.rootDoc.expandBoolean = this._expandBoolean; this.childDocs.forEach(doc => { doc.presExpandInlineButton = this._expandBoolean; }); }; /** * 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 */ startPresentation = (startIndex: number) => { this.updateCurrentPresentation(); this.childDocs.map(doc => { const tagDoc = doc.presentationTargetDoc as Doc; if (doc.presHideBefore && this.childDocs.indexOf(doc) > startIndex) { tagDoc.opacity = 0; } if (doc.presHideAfter && this.childDocs.indexOf(doc) < startIndex) { tagDoc.opacity = 0; } }); }; /** * The method called to open the presentation as a minimized view */ @action updateMinimize = async () => { if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.layoutDoc.presStatus = PresStatus.Edit; Doc.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc); CollectionDockingView.AddSplit(this.rootDoc, 'right'); } else { this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); this.rootDoc.x = pt[0] + (this.props.PanelWidth() - 250); this.rootDoc.y = pt[1] + 10; this.rootDoc._height = 30; this.rootDoc._width = 248; Doc.AddDocToList(Doc.MyOverlayDocs, undefined, this.rootDoc); this.props.removeDocument?.(this.layoutDoc); } }; /** * Called when the user changes the view type * Either 'List' (stacking) or 'Slides' (carousel) */ @undoBatch viewChanged = action((e: React.ChangeEvent) => { //@ts-ignore const viewType = e.target.selectedOptions[0].value as CollectionViewType; this.layoutDoc.presFieldKey = this.fieldKey + (viewType === 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(viewType) && (this.rootDoc._pivotField = undefined); this.rootDoc._viewType = viewType; 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; console.log(list); } else { this.childDocs[stopDocIndex - 1].mediaStopTriggerList = new List(); const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList); list.push(activeItem); // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list; console.log(list); } }); setMovementName = action((movement: any, activeItem: Doc): string => { let output: string = 'none'; switch (movement) { case PresMovement.Zoom: output = 'Pan & Zoom'; break; //Pan and zoom case PresMovement.Pan: output = 'Pan'; break; //Pan case PresMovement.Jump: output = 'Jump cut'; break; //Jump Cut case PresMovement.None: output = 'None'; break; //None default: output = 'Zoom'; activeItem.presMovement = 'zoom'; break; //default set as zoom } return output; }); 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.presentationTargetDoc) return true; if (doc.type === DocumentType.LABEL) { const audio = Cast(doc.annotationOn, Doc, null); if (audio) { audio.mediaStart = 'manual'; audio.mediaStop = 'manual'; audio.presStartTime = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */)); audio.presEndTime = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */)); audio.presDuration = audio.presStartTime - audio.presEndTime; TabDocView.PinDoc(audio, { audioRange: true }); setTimeout(() => this.removeDocument(doc), 0); return false; } } else { if (!doc.aliasOf) { const original = Doc.MakeAlias(doc); TabDocView.PinDoc(original); setTimeout(() => this.removeDocument(doc), 0); return false; } else { if (!doc.presentationTargetDoc) doc.title = doc.title + ' - Slide'; doc.aliasOf instanceof Doc && (doc.presentationTargetDoc = doc.aliasOf); doc.presMovement = PresMovement.Zoom; if (this._expandBoolean) doc.presExpandInlineButton = true; } } }); return true; }; childLayoutTemplate = () => (!this.isTreeOrStack ? undefined : DocCast(Doc.UserDoc().presElement)); removeDocument = (doc: Doc) => Doc.RemoveDocFromList(this.rootDoc, this.fieldKey, doc); getTransform = () => this.props.ScreenToLocalTransform().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight panelHeight = () => this.props.PanelHeight() - 40; isContentActive = (outsideReaction?: boolean) => Doc.ActiveTool === InkTool.None && !this.layoutDoc._lockedPosition && (this.layoutDoc.forceActive || this.props.isSelected(outsideReaction) || this._isChildActive || this.props.renderDepth === 0) ? true : false; /** * For sorting the array so that the order is maintained when it is dropped. */ @action sortArray = (): Doc[] => { return 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.presentationTargetDoc, Doc, null); if (curDoc && curDoc === this.activeItem) return (
{index + 1}. {curDoc.title}
); else if (tagDoc) return (
{index + 1}. {curDoc.title}
); else if (curDoc) return (
{index + 1}. {curDoc.title}
); }); } @action selectPres = () => { const presDocView = DocumentManager.Instance.getDocumentView(this.rootDoc); presDocView && SelectionManager.SelectView(presDocView, false); }; //Regular click @action selectElement = async (doc: Doc) => { const context = Cast(doc.context, Doc, null); this.gotoDocument(this.childDocs.indexOf(doc), this.activeItem); if (doc.presPinView || doc.presentationTargetDoc === this.layoutDoc.presCollection) setTimeout(() => this.updateCurrentPresentation(context), 0); else this.updateCurrentPresentation(context); }; //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.removeFromArray(this._eleArray, doc); this.removeFromArray(this._dragArray, doc); } this.selectPres(); }; removeFromArray = (arr: any[], val: any) => { const index: number = arr.indexOf(val); const ret: any[] = arr.splice(index, 1); arr = ret; }; //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, focus: boolean, selectPres = true) => { this.clearSelectedArray(); this.addToSelectedArray(doc); this._eleArray.splice(0, this._eleArray.length, ref); this._dragArray.splice(0, this._dragArray.length, drag); focus && this.selectElement(doc); selectPres && this.selectPres(); }; modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: 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, focus); }; 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; 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.presStatus === '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 (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { this.updateMinimize(); } else if (this.layoutDoc.presStatus === 'edit') { this.clearSelectedArray(); this._eleArray.length = this._dragArray.length = 0; } else this.layoutDoc.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.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1; this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]); } else { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = 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.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1; this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]); } else { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } } handled = true; break; case 'Spacebar': case ' ': if (this.layoutDoc.presStatus === PresStatus.Manual) this.startAutoPres(this.itemIndex); else if (this.layoutDoc.presStatus === PresStatus.Autoplay) if (this._presTimer) clearTimeout(this._presTimer); handled = true; break; case 'a': if ((e.metaKey || e.altKey) && this.layoutDoc.presStatus === 'edit') { this.clearSelectedArray(); this.childDocs.forEach(doc => this.addToSelectedArray(doc)); handled = true; } default: break; } if (handled) { e.stopPropagation(); e.preventDefault(); } }; /** * */ @action viewPaths = () => { const srcContext = Cast(this.rootDoc.presCollection, Doc, null); if (srcContext) { this.togglePath(srcContext); } }; getAllIndexes = (arr: Doc[], val: Doc): number[] => { const indexes = []; for (let i = 0; i < arr.length; i++) { arr[i] === val && indexes.push(i); } return indexes; }; // Adds the index in the pres path graphically @computed get order() { const order: JSX.Element[] = []; const docs: Doc[] = []; const presCollection = Cast(this.rootDoc.presCollection, Doc, null); const dv = DocumentManager.Instance.getDocumentView(presCollection); this.childDocs .filter(doc => Cast(doc.presentationTargetDoc, Doc, null)) .forEach((doc, index) => { const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); const srcContext = Cast(tagDoc.context, Doc, null); 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; const gap = 2; if (presCollection === srcContext) { // Case A: Document is contained within the collection if (docs.includes(tagDoc)) { const prevOccurances: number = this.getAllIndexes(docs, tagDoc).length; docs.push(tagDoc); order.push(
this.selectElement(doc)}>
{index + 1}
); } else { docs.push(tagDoc); order.push(
this.selectElement(doc)}>
{index + 1}
); } } else if (doc.presPinView && presCollection === tagDoc && dv) { // Case B: Document is presPinView and is presCollection const scale: number = 1 / NumCast(doc.presPinViewScale); const height: number = dv.props.PanelHeight() * scale; const width: number = dv.props.PanelWidth() * scale; const indWidth = width / 10; const indHeight = Math.max(height / 10, 15); const indEdge = Math.max(indWidth, indHeight); const indFontSize = indEdge * 0.8; const xLoc: number = NumCast(doc.presPinViewX) - width / 2; const yLoc: number = NumCast(doc.presPinViewY) - height / 2; docs.push(tagDoc); order.push( <>
this.selectElement(doc)}>
{index + 1}
); } }); 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 _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) */ @computed get paths() { let pathPoints = ''; const presCollection = Cast(this.rootDoc.presCollection, Doc, null); this.childDocs.forEach((doc, index) => { const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); const srcContext = Cast(tagDoc?.context, Doc, null); if (tagDoc && presCollection === srcContext) { 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.presPinView && presCollection === tagDoc) { const n1x = NumCast(doc.presPinViewX); const n1y = NumCast(doc.presPinViewY); if ((index = 0)) pathPoints = n1x + ',' + n1y; else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; } }); return ( ); } // Converts seconds to ms and updates presTransition setTransitionTime = (number: String, change?: number) => { let timeInMS = Number(number) * 1000; if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 10000) timeInMS = 10000; this._selectedArray.forEach(doc => (doc.presTransition = timeInMS)); }; // Converts seconds to ms and updates presTransition setZoom = (number: String, change?: number) => { let scale = Number(number) / 100; if (change) scale += change; if (scale < 0.01) scale = 0.01; if (scale > 1.5) scale = 1.5; this._selectedArray.forEach(doc => (doc.presZoom = scale)); }; // Converts seconds to ms and updates presDuration setDurationTime = (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.presDuration = timeInMS)); }; /** * When the movement dropdown is changes */ @undoBatch updateMovement = action((movement: any, all?: boolean) => { (all ? this.childDocs : this._selectedArray).forEach(doc => { switch (movement) { case PresMovement.Zoom: //Pan and zoom doc.presMovement = PresMovement.Zoom; break; case PresMovement.Pan: //Pan doc.presMovement = PresMovement.Pan; break; case PresMovement.Jump: //Jump Cut doc.presJump = true; doc.presMovement = PresMovement.Jump; break; case PresMovement.None: default: doc.presMovement = PresMovement.None; break; } }); }); @undoBatch @action updateHideBefore = (activeItem: Doc) => { activeItem.presHideBefore = !activeItem.presHideBefore; this._selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); }; @undoBatch @action updateHideAfter = (activeItem: Doc) => { activeItem.presHideAfter = !activeItem.presHideAfter; this._selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); }; @undoBatch @action updateOpenDoc = (activeItem: Doc) => { activeItem.openDocument = !activeItem.openDocument; this._selectedArray.forEach(doc => { doc.openDocument = activeItem.openDocument; }); }; @undoBatch @action updateEffectDirection = (effect: any, all?: boolean) => { (all ? this.childDocs : this._selectedArray).forEach(doc => { const tagDoc = doc; // Cast(doc.presentationTargetDoc, Doc, null); switch (effect) { case PresEffect.Left: tagDoc.presEffectDirection = PresEffect.Left; break; case PresEffect.Right: tagDoc.presEffectDirection = PresEffect.Right; break; case PresEffect.Top: tagDoc.presEffectDirection = PresEffect.Top; break; case PresEffect.Bottom: tagDoc.presEffectDirection = PresEffect.Bottom; break; case PresEffect.Center: default: tagDoc.presEffectDirection = PresEffect.Center; break; } }); }; @undoBatch @action updateEffect = (effect: any, all?: boolean) => { (all ? this.childDocs : this._selectedArray).forEach(doc => { const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null); //prettier-ignore switch (effect) { default: case PresEffect.None: tagDoc.presEffect = PresEffect.None; break; case PresEffect.Bounce: tagDoc.presEffect = PresEffect.Bounce; break; case PresEffect.Fade: tagDoc.presEffect = PresEffect.Fade; break; case PresEffect.Flip: tagDoc.presEffect = PresEffect.Flip; break; case PresEffect.Roll: tagDoc.presEffect = PresEffect.Roll; break; case PresEffect.Rotate: tagDoc.presEffect = PresEffect.Rotate; break; } }); }; _batch: UndoManager.Batch | undefined = undefined; @computed get transitionDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const isPresCollection: boolean = targetDoc === this.layoutDoc.presCollection; const isPinWithView: boolean = BoolCast(activeItem.presPinView); if (activeItem && targetDoc) { const type = targetDoc.type; const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; const zoom = activeItem.presZoom ? NumCast(activeItem.presZoom) * 100 : 75; let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); const effect = this.activeItem.presEffect ? this.activeItem.presEffect : 'None'; activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; return (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}>
Movement {isPresCollection || (isPresCollection && isPinWithView) ? (
{this.scrollable ? 'Scroll to pinned view' : !isPinWithView ? 'No movement' : 'Pan & Zoom to pinned view'}
) : (
{ e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> {this.setMovementName(activeItem.presMovement, activeItem)}
e.stopPropagation()} style={{ display: this.openMovementDropdown ? 'grid' : 'none' }}>
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}> None
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}> Pan {'&'} Zoom
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}> Pan
e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}> Jump cut
)}
Zoom (% screen filled)
this.setZoom(e.target.value))} />%
this.setZoom(String(zoom), 0.1))}>
this.setZoom(String(zoom), -0.1))}>
(this._batch = UndoManager.StartBatch('presZoom'))} onPointerUp={() => this._batch?.end()} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); this.setZoom(e.target.value); }} />
Movement Speed
e.stopPropagation()} onChange={action(e => this.setTransitionTime(e.target.value))} /> s
this.setTransitionTime(String(transitionSpeed), 1000))}>
this.setTransitionTime(String(transitionSpeed), -1000))}>
(this._batch = UndoManager.StartBatch('presTransition'))} onPointerUp={() => this._batch?.end()} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); this.setTransitionTime(e.target.value); }} />
Fast
Medium
Slow
Visibility {'&'} Duration
{isPresCollection ? null : (
{'Hide before presented'}
}>
this.updateHideBefore(activeItem)}> Hide before
)} {isPresCollection ? null : (
{'Hide after presented'}
}>
this.updateHideAfter(activeItem)}> Hide after
)}
{'Open in lightbox view'}
}>
this.updateOpenDoc(activeItem)}> Lightbox
{type === DocumentType.AUDIO || type === DocumentType.VID ? null : ( <>
Slide Duration
e.stopPropagation()} onChange={action(e => this.setDurationTime(e.target.value))} /> s
this.setDurationTime(String(duration), 1000))}>
this.setDurationTime(String(duration), -1000))}>
{ this._batch = UndoManager.StartBatch('presDuration'); }} onPointerUp={() => { if (this._batch) this._batch.end(); }} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} />
Short
Medium
Long
)}
{isPresCollection ? null : (
Effects
{ e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> {effect.toString()}
e.stopPropagation()}>
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}> None
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}> Fade In
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}> Flip
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}> Rotate
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}> Bounce
e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}> Roll
Effect direction
{this.effectDirection}
{'Enter from left'}
}>
this.updateEffectDirection(PresEffect.Left)}>
{'Enter from right'}
}>
this.updateEffectDirection(PresEffect.Right)}>
{'Enter from top'}
}>
this.updateEffectDirection(PresEffect.Top)}>
{'Enter from bottom'}
}>
this.updateEffectDirection(PresEffect.Bottom)}>
{'Enter from center'}
}>
this.updateEffectDirection(PresEffect.Center)}>
)}
this.applyTo(this.childDocs)}> Apply to all
); } } @computed get effectDirection(): string { let effect = ''; switch (this.activeItem.presEffectDirection) { case 'left': effect = 'Enter from left'; break; case 'right': effect = 'Enter from right'; break; case 'top': effect = 'Enter from top'; break; case 'bottom': effect = 'Enter from bottom'; break; default: effect = 'Enter from center'; break; } return effect; } @undoBatch @action applyTo = (array: Doc[]) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; this.updateMovement(activeItem.presMovement, true); this.updateEffect(activeItem.presEffect, true); this.updateEffectDirection(activeItem.presEffectDirection, true); array.forEach(doc => { const curDoc = Cast(doc, Doc, null); const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); if (tagDoc && targetDoc) { curDoc.presTransition = activeItem.presTransition; curDoc.presDuration = activeItem.presDuration; curDoc.presHideBefore = activeItem.presHideBefore; curDoc.presHideAfter = activeItem.presHideAfter; } }); }; @computed get presPinViewOptionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const presPinWithViewIcon = ; return ( <> {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : null}
{activeItem.presPinView ? 'Turn off pin with view' : 'Turn on pin with view'}
}>
{ activeItem.presPinView = !activeItem.presPinView; targetDoc.presPinView = activeItem.presPinView; if (activeItem.presPinView) { if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { const scroll = targetDoc._scrollTop; activeItem.presPinView = true; activeItem.presPinViewScroll = scroll; } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { activeItem.presStartTime = targetDoc._currentTimecode; activeItem.presEndTime = NumCast(targetDoc._currentTimecode) + 0.1; } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { const x = targetDoc._panX; const y = targetDoc._panY; const scale = targetDoc._viewScale; activeItem.presPinView = true; activeItem.presPinViewX = x; activeItem.presPinViewY = y; activeItem.presPinViewScale = scale; } else if (targetDoc.type === DocumentType.COMPARISON) { const width = targetDoc._clipWidth; activeItem.presPinClipWidth = width; activeItem.presPinView = true; } } }}> {presPinWithViewIcon}
{activeItem.presPinView ? (
{'Update the pinned view with the view of the selected document'}
}>
{ if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { const scroll = targetDoc._scrollTop; activeItem.presPinViewScroll = scroll; } else if ([DocumentType.AUDIO, DocumentType.VID].includes(targetDoc.type as any)) { activeItem.presStartTime = targetDoc._currentTimecode; activeItem.presStartTime = NumCast(targetDoc._currentTimecode) + 0.1; } else if (targetDoc.type === DocumentType.COMPARISON) { const clipWidth = targetDoc._clipWidth; activeItem.presPinClipWidth = clipWidth; } else { const x = targetDoc._panX; const y = targetDoc._panY; const scale = targetDoc._viewScale; activeItem.presPinViewX = x; activeItem.presPinViewY = y; activeItem.presPinViewScale = scale; } }}> Update
) : null}
); } @computed get panOptionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; return ( <> {this.panable ? (
Pan X
e.stopPropagation()} onChange={action((e: React.ChangeEvent) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} />
Pan Y
e.stopPropagation()} onChange={action((e: React.ChangeEvent) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} />
Scale
e.stopPropagation()} onChange={action((e: React.ChangeEvent) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} />
) : null} ); } @computed get scrollOptionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; return ( <> {this.scrollable ? (
Scroll
e.stopPropagation()} onChange={action((e: React.ChangeEvent) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} />
) : null} ); } @computed get mediaStopSlides() { const activeItem: Doc = this.activeItem; const list = this.childDocs.map((doc, i) => { if (i > this.itemIndex) { return ( ); } }); return list; } @computed get mediaOptionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const clipStart: number = NumCast(activeItem.clipStart); const clipEnd: number = NumCast(activeItem.clipEnd); const duration = Math.round(NumCast(activeItem[`${Doc.LayoutFieldKey(activeItem)}-duration`]) * 10); const mediaStopDocInd: number = NumCast(activeItem.mediaStopDoc); const mediaStopDocStr: string = mediaStopDocInd ? mediaStopDocInd + '. ' + this.childDocs[mediaStopDocInd - 1].title : ''; if (activeItem && targetDoc) { return (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
Start {'&'} End Time
Start time (s)
e.stopPropagation()} onChange={action((e: React.ChangeEvent) => { activeItem.presStartTime = Number(e.target.value); })} />
Duration (s)
{Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10}
End time (s)
e.stopPropagation()} style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }} type="number" value={NumCast(activeItem.presEndTime)} onChange={action((e: React.ChangeEvent) => { activeItem.presEndTime = Number(e.target.value); })} />
{ this._batch = UndoManager.StartBatch('presEndTime'); const endBlock = document.getElementById('endTime'); if (endBlock) { endBlock.style.color = Colors.LIGHT_GRAY; endBlock.style.backgroundColor = Colors.MEDIUM_BLUE; } }} onPointerUp={() => { this._batch?.end(); const endBlock = document.getElementById('endTime'); if (endBlock) { endBlock.style.color = Colors.BLACK; endBlock.style.backgroundColor = Colors.LIGHT_GRAY; } }} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); activeItem.presEndTime = Number(e.target.value); }} /> { this._batch = UndoManager.StartBatch('presStartTime'); const startBlock = document.getElementById('startTime'); if (startBlock) { startBlock.style.color = Colors.LIGHT_GRAY; startBlock.style.backgroundColor = Colors.MEDIUM_BLUE; } }} onPointerUp={() => { this._batch?.end(); const startBlock = document.getElementById('startTime'); if (startBlock) { startBlock.style.color = Colors.BLACK; startBlock.style.backgroundColor = Colors.LIGHT_GRAY; } }} onChange={(e: React.ChangeEvent) => { e.stopPropagation(); activeItem.presStartTime = Number(e.target.value); }} />
{clipStart} s
{clipEnd} s
Playback
Start playing:
(activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} />
On click
(activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} />
Automatically
Stop playing:
(activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} />
At audio end time
(activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} />
On slide change
{/*
activeItem.mediaStop = "afterSlide"} checked={activeItem.mediaStop === "afterSlide"} />
After chosen slide
*/}
); } } @computed get newDocumentToolbarDropdown() { return (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
{ this.layout = 'blank'; this.createNewSlide(this.layout); })} />
{ this.layout = 'title'; this.createNewSlide(this.layout); })}>
Title
Subtitle
{ this.layout = 'header'; this.createNewSlide(this.layout); })}>
Section header
{ this.layout = 'content'; this.createNewSlide(this.layout); })}>
Title
Text goes here
); } @observable openLayouts: boolean = false; @observable addFreeform: boolean = true; @observable layout: string = ''; @observable title: string = ''; @computed get newDocumentDropdown() { return (
e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
Slide Title:

{ e.stopPropagation(); e.preventDefault(); runInAction(() => (this.title = e.target.value)); }}>
Choose type:
(this.addFreeform = !this.addFreeform))}> Text
(this.addFreeform = !this.addFreeform))}> Freeform
Preset layouts:
(this.layout = 'blank'))} />
(this.layout = 'title'))}>
Title
Subtitle
(this.layout = 'header'))}>
Section header
(this.layout = 'content'))}>
Title
Text goes here
(this.layout = 'twoColumns'))}>
Title
Column one text
Column two text
(this.openLayouts = !this.openLayouts))}>
this.createNewSlide(this.layout, this.title, this.addFreeform)}> Create New Slide
); } 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 presCollection = Cast(this.layoutDoc.presCollection, Doc, null); const data = Cast(presCollection?.data, listSpec(Doc)); const presData = Cast(this.rootDoc.data, listSpec(Doc)); if (data && presData) { data.push(doc); TabDocView.PinDoc(doc); this.gotoDocument(this.childDocs.length, this.activeItem); } else { this.props.addDocTab(doc, 'add:right'); } } }; createTemplate = (layout: string, input?: string) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; let x = 0; let y = 0; if (activeItem && targetDoc) { x = NumCast(targetDoc.x); y = NumCast(targetDoc.y) + NumCast(targetDoc._height) + 20; } let doc = undefined; const title = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _fontSize: '24pt' }); const subtitle = Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _fontSize: '16pt' }); const header = Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _fontSize: '20pt' }); const contentTitle = Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _fontSize: '24pt' }); const content = Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _fontSize: '14pt' }); const content1 = Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _fontSize: '14pt' }); const content2 = Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _fontSize: '14pt' }); switch (layout) { case 'blank': doc = Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x: x, y: y }); break; case 'title': doc = Docs.Create.FreeformDocument([title, subtitle], { title: input ? input : 'Title slide', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'header': doc = Docs.Create.FreeformDocument([header], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'content': doc = Docs.Create.FreeformDocument([contentTitle, content], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; case 'twoColumns': doc = Docs.Create.FreeformDocument([contentTitle, content1, content2], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x: x, y: y }); break; default: break; } return doc; }; // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) @computed get presentDropdown() { return (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
{ this.updateMinimize(); this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }) )}> Mini-player
{ this.layoutDoc.presStatus = 'manual'; this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }) )}> Sidebar player
); } // Case in which the document has keyframes to navigate to next key frame @action nextKeyframe = (tagDoc: Doc, curDoc: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); const currentFrame = Cast(tagDoc._currentFrame, 'number', null); if (currentFrame === undefined) { tagDoc._currentFrame = 0; // CollectionFreeFormDocumentView.setupScroll(tagDoc, 0); // CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } // if (tagDoc.editScrollProgressivize) CollectionFreeFormDocumentView.updateScrollframe(tagDoc, currentFrame); CollectionFreeFormDocumentView.updateKeyframe(childDocs, currentFrame || 0, tagDoc); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) + 1); tagDoc.lastFrame = Math.max(NumCast(tagDoc._currentFrame), NumCast(tagDoc.lastFrame)); }; @action prevKeyframe = (tagDoc: Doc, actItem: Doc): void => { const childDocs = DocListCast(tagDoc[Doc.LayoutFieldKey(tagDoc)]); const currentFrame = Cast(tagDoc._currentFrame, 'number', null); if (currentFrame === undefined) { tagDoc._currentFrame = 0; // CollectionFreeFormDocumentView.setupKeyframes(childDocs, 0); } CollectionFreeFormDocumentView.gotoKeyframe(childDocs.slice()); tagDoc._currentFrame = Math.max(0, (currentFrame || 0) - 1); }; /** * Returns the collection type as a string for headers */ @computed get stringType(): string { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; let type: string = ''; if (activeItem) { switch (targetDoc.type) { case DocumentType.PDF: type = 'PDF'; break; case DocumentType.RTF: type = 'Text node'; break; case DocumentType.COL: type = 'Collection'; break; case DocumentType.AUDIO: type = 'Audio'; break; case DocumentType.VID: type = 'Video'; break; case DocumentType.IMG: type = 'Image'; break; case DocumentType.WEB: type = 'Web page'; break; case DocumentType.MAP: type = 'Map'; break; default: type = 'Other node'; break; } } return type; } @observable private openActiveColorPicker: boolean = false; @observable private openViewedColorPicker: boolean = false; @computed get progressivizeDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (activeItem && targetDoc) { const activeFontColor = targetDoc['pres-text-color'] ? StrCast(targetDoc['pres-text-color']) : 'Black'; const viewedFontColor = targetDoc['pres-text-viewed-color'] ? StrCast(targetDoc['pres-text-viewed-color']) : 'Black'; return (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
{this.stringType} selected
Contents
Edit
Active text color
{ this.openActiveColorPicker = !this.openActiveColorPicker; })}>
{this.activeColorPicker}
Viewed font color
(this.openViewedColorPicker = !this.openViewedColorPicker))}>
{this.viewedColorPicker}
Zoom
Edit
Scroll
Edit
Frames
{ e.stopPropagation(); this.prevKeyframe(targetDoc, activeItem); }}>
(targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing))}> {NumCast(targetDoc._currentFrame)}
{ e.stopPropagation(); this.nextKeyframe(targetDoc, activeItem); }}>
{'Last frame'}
}>
{NumCast(targetDoc.lastFrame)}
{this.frameListHeader} {this.frameList}
console.log(' TODO: play frames')}> Play
); } } @undoBatch @action switchActive = (color: ColorState) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const val = String(color.hex); targetDoc['pres-text-color'] = val; return true; }; @undoBatch @action switchPresented = (color: ColorState) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const val = String(color.hex); targetDoc['pres-text-viewed-color'] = val; return true; }; @computed get activeColorPicker() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; return !this.openActiveColorPicker ? null : ( ); } @computed get viewedColorPicker() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; return !this.openViewedColorPicker ? null : ( ); } @action turnOffEdit = (paths?: boolean) => { // Turn off paths if (paths) { const srcContext = Cast(this.rootDoc.presCollection, Doc, null); if (srcContext) this.togglePath(srcContext, true); } // Turn off the progressivize editors for each document this.childDocs.forEach(doc => { doc.editSnapZoomProgressivize = false; doc.editZoomProgressivize = false; const targetDoc = Cast(doc.presentationTargetDoc, Doc, null); if (targetDoc) { targetDoc.editZoomProgressivize = false; // targetDoc.editScrollProgressivize = false; } }); }; //Toggle whether the user edits or not @action editZoomProgressivize = (e: React.MouseEvent) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (!targetDoc.editZoomProgressivize) { if (!activeItem.zoomProgressivize) activeItem.zoomProgressivize = true; targetDoc.zoomProgressivize = true; targetDoc.editZoomProgressivize = true; activeItem.editZoomProgressivize = true; } else { targetDoc.editZoomProgressivize = false; activeItem.editZoomProgressivize = false; } }; //Toggle whether the user edits or not @action editScrollProgressivize = (e: React.MouseEvent) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; if (!targetDoc.editScrollProgressivize) { if (!targetDoc.scrollProgressivize) { targetDoc.scrollProgressivize = true; activeItem.scrollProgressivize = true; } targetDoc.editScrollProgressivize = true; } else { targetDoc.editScrollProgressivize = false; } }; //Progressivize Zoom @action progressivizeScroll = (e: React.MouseEvent) => { e.stopPropagation(); const activeItem: Doc = this.activeItem; activeItem.scrollProgressivize = !activeItem.scrollProgressivize; const targetDoc: Doc = this.targetDoc; targetDoc.scrollProgressivize = !targetDoc.scrollProgressivize; // CollectionFreeFormDocumentView.setupScroll(targetDoc, NumCast(targetDoc._currentFrame)); if (targetDoc.editScrollProgressivize) { targetDoc.editScrollProgressivize = false; targetDoc._currentFrame = 0; targetDoc.lastFrame = 0; } }; //Progressivize Zoom @action progressivizeZoom = (e: React.MouseEvent) => { e.stopPropagation(); const activeItem: Doc = this.activeItem; activeItem.zoomProgressivize = !activeItem.zoomProgressivize; const targetDoc: Doc = this.targetDoc; targetDoc.zoomProgressivize = !targetDoc.zoomProgressivize; CollectionFreeFormDocumentView.setupZoom(activeItem, targetDoc); if (activeItem.editZoomProgressivize) { activeItem.editZoomProgressivize = false; targetDoc._currentFrame = 0; targetDoc.lastFrame = 0; } }; //Progressivize Child Docs @action editProgressivize = (e: React.MouseEvent) => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; targetDoc._currentFrame = targetDoc.lastFrame; if (!targetDoc.editProgressivize) { if (!activeItem.presProgressivize) { activeItem.presProgressivize = true; targetDoc.presProgressivize = true; } targetDoc.editProgressivize = true; } else { targetDoc.editProgressivize = false; } }; @action progressivizeChild = (e: React.MouseEvent) => { e.stopPropagation(); const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); if (!activeItem.presProgressivize) { targetDoc.keyFrameEditing = false; activeItem.presProgressivize = true; targetDoc.presProgressivize = true; targetDoc._currentFrame = 0; docs.forEach((doc, i) => CollectionFreeFormDocumentView.setupKeyframes([doc], i, true)); targetDoc.lastFrame = targetDoc.lastFrame ? NumCast(targetDoc.lastFrame) : docs.length - 1; } else { // targetDoc.editProgressivize = false; activeItem.presProgressivize = false; targetDoc.presProgressivize = false; targetDoc._currentFrame = 0; targetDoc.keyFrameEditing = true; } }; @action checkMovementLists = (doc: Doc, xlist: any, ylist: any) => { const x: List = xlist; const y: List = ylist; const tags: JSX.Element[] = []; let pathPoints = ''; //List of all of the pathpoints that need to be added for (let i = 0; i < x.length - 1; i++) { if (y[i] || x[i]) { if (i === 0) pathPoints = x[i] - 11 + ',' + (y[i] + 33); else pathPoints = pathPoints + ' ' + (x[i] - 11) + ',' + (y[i] + 33); tags.push(
{i}
); } } tags.push( ); return tags; }; @observable toggleDisplayMovement = (doc: Doc) => { if (doc.displayMovement) doc.displayMovement = false; else doc.displayMovement = true; }; @action checkList = (doc: Doc, list: any): number => { const x: List = list; if (x && x.length >= NumCast(doc._currentFrame) + 1) { return x[NumCast(doc._currentFrame)]; } else if (x) { x.length = NumCast(doc._currentFrame) + 1; x[NumCast(doc._currentFrame)] = x[NumCast(doc._currentFrame) - 1]; return x[NumCast(doc._currentFrame)]; } else return 100; }; @computed get progressivizeChildDocs() { const targetDoc: Doc = this.targetDoc; const docs = DocListCast(targetDoc[Doc.LayoutFieldKey(targetDoc)]); const tags: JSX.Element[] = []; docs.forEach((doc, index) => { if (doc['x-indexed'] && doc['y-indexed']) { tags.push(
{this.checkMovementLists(doc, doc['x-indexed'], doc['y-indexed'])}
); } tags.push(
{ if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0; }} onPointerOver={() => { if (NumCast(targetDoc._currentFrame) < NumCast(doc.appearFrame)) doc.opacity = 0.5; }} onClick={e => { this.toggleDisplayMovement(doc); e.stopPropagation(); }} style={{ backgroundColor: doc.displayMovement ? Colors.LIGHT_BLUE : '#c8c8c8', top: NumCast(doc.y), left: NumCast(doc.x) }}>
{ e.stopPropagation(); this.prevAppearFrame(doc, index); }} />
{NumCast(doc.appearFrame)}
{ e.stopPropagation(); this.nextAppearFrame(doc, index); }} />
); }); return tags; } @action nextAppearFrame = (doc: Doc, i: number): void => { // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); const appearFrame = Cast(doc.appearFrame, 'number', null); if (appearFrame === undefined) { doc.appearFrame = 0; } doc.appearFrame = appearFrame + 1; this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); }; @action prevAppearFrame = (doc: Doc, i: number): void => { // const activeItem = Cast(this.childDocs[this.itemIndex], Doc, null); // const targetDoc = Cast(activeItem?.presentationTargetDoc, Doc, null); const appearFrame = Cast(doc.appearFrame, 'number', null); if (appearFrame === undefined) { doc.appearFrame = 0; } doc.appearFrame = Math.max(0, appearFrame - 1); this.updateOpacityList(doc['opacity-indexed'], NumCast(doc.appearFrame)); }; @action updateOpacityList = (list: any, frame: number) => { const x: List = list; if (x && x.length >= frame) { for (let i = 0; i < x.length; i++) { if (i < frame) { x[i] = 0; } else if (i >= frame) { x[i] = 1; } } list = x; } else { x.length = frame + 1; for (let i = 0; i < x.length; i++) { if (i < frame) { x[i] = 0; } else if (i >= frame) { x[i] = 1; } } list = x; } }; @computed get moreInfoDropdown() { return
; } @computed get toolbarWidth(): number { const width = this.props.PanelWidth(); return width; } @action toggleProperties = () => { if (SettingsManager.propertiesWidth > 0) { SettingsManager.propertiesWidth = 0; } else { SettingsManager.propertiesWidth = 250; } }; @computed get toolbar() { const propIcon = SettingsManager.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; const propTitle = SettingsManager.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.ActivePresentation; const activeColor = Colors.LIGHT_BLUE; const inactiveColor = Colors.WHITE; return mode === CollectionViewType.Carousel3D ? null : (
{/*
{"Add new slide"}
}>
this.newDocumentTools = !this.newDocumentTools)}>
*/}
{'View paths'}
}>
1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }} className={'toolbar-button'} onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}>
{isMini ? null : ( <>
{/*
{this._expandBoolean ? "Minimize all" : "Expand all"}
}>
*/}
{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}
}>
{propTitle}
}>
0 ? activeColor : inactiveColor }} />
)}
); } /** * 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.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; return (
{isMini ? null : ( )}
{ if (this.childDocs.length) { this.layoutDoc.presStatus = 'manual'; this.gotoDocument(this.itemIndex, this.activeItem); } })}>
200 ? 'inline-flex' : 'none' }}>  Present
{mode === CollectionViewType.Carousel3D || isMini ? null : (
{ if (this.childDocs.length) this.presentTools = !this.presentTools; })}> {this.presentDropdown}
)}
{this.playButtons}
); } @action getList = (list: any): List => { const x: List = list; return x; }; @action updateList = (list: any): List => { const targetDoc: Doc = this.targetDoc; const x: List = list; x.length + 1; x[x.length - 1] = NumCast(targetDoc._scrollY); return x; }; @action newFrame = () => { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const type: string = StrCast(targetDoc.type); if (!activeItem.frameList) activeItem.frameList = new List(); switch (type) { case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB: this.updateList(activeItem.frameList); break; case DocumentType.COL: break; default: break; } }; @computed get frameListHeader() { return (
  Frames {this.panable ? Panable : this.scrollable ? Scrollable : null}
{'Add frame by example'}
}>
{ e.stopPropagation(); this.newFrame(); }}> e.stopPropagation()} />
{'Edit in collection'}
}>
{ e.stopPropagation(); console.log('New frame'); }}> e.stopPropagation()} />
); } @computed get frameList() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const frameList: List = this.getList(activeItem.frameList); if (frameList) { const frameItems = frameList.map(value =>
); return
{frameItems}
; } else return null; } @computed get playButtonFrames() { const targetDoc: Doc = this.targetDoc; return ( <> {this.targetDoc ? (
= 0 ? 'inline-flex' : 'none' }}>
{NumCast(targetDoc._currentFrame)}
{NumCast(targetDoc.lastFrame)}
) : null} ); } @computed get playButtons() { const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1; const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; // Case 1: There are still other frames and should go through all frames before going to next slide return (
{'Loop'}
}>
(this.layoutDoc.presLoop = !this.layoutDoc.presLoop)}>
{ this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}>
{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
}>
{ this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }}>
{'Click to return to 1st slide'}
}>
this.gotoDocument(0, this.activeItem)}> 1
this.gotoDocument(0, this.activeItem)} style={{ display: this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}> Slide {this.itemIndex + 1} / {this.childDocs.length} {this.playButtonFrames}
{this.props.PanelWidth() > 250 ? (
{ this.layoutDoc.presStatus = 'edit'; clearTimeout(this._presTimer); }) )}> EXIT
) : (
(this.layoutDoc.presStatus = 'edit')))}>
)}
); } @action startOrPause = () => { if (this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startAutoPres(this.itemIndex); else this.pauseAutoPres(); }; @action prevClicked = (e: PointerEvent) => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }; @action nextClicked = (e: PointerEvent) => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presStatus = PresStatus.Manual; } }; @undoBatch @action exitClicked = (e: PointerEvent) => { this.updateMinimize(); this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); }; @action startMarqueeCreateSlide = () => { PresBox.startMarquee = true; }; AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => { 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(sorted); // this is a flat array of Docs } } return this.childDocs; }; RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => { if (!this._unmounting && this.isTree) { this._treeViewMap.delete(treeViewDoc); this.dataDoc[this.presFieldKey] = new List(this.sort(this._treeViewMap)); } return this.childDocs; }; // TODO: [AL] implement sort function for an array of numbers (e.g. arr[1,2,4] v arr[1,2,1]) sort = (treeViewMap: Map) => [...treeViewMap.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]); render() { // calling this method for keyEvents this.isPres; // needed to ensure that the childDocs are loaded for looking up fields this.childDocs.slice(); const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.ActivePresentation; const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1; const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; return DocListCast(Doc.MyOverlayDocs?.data).includes(this.rootDoc) ? (
e.stopPropagation()}>
{'Loop'}
}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}>
{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}>
{'Click to return to 1st slide'}
}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}> 1
Slide {this.itemIndex + 1} / {this.childDocs.length} {this.playButtonFrames}
setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}> EXIT
) : (
{this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown}
{mode !== CollectionViewType.Invalid ? ( ) : null}
{ // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack {'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}
}> }
); } } ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) { const srcContext = Cast(bestTarget.context, Doc, null) ?? Cast(Cast(bestTarget.annotationOn, Doc, null)?.context, Doc, null); const openInTab = (doc: Doc, finished?: () => void) => { CollectionDockingView.AddSplit(doc, 'right'); finished?.(); }; DocumentManager.Instance.jumpToDocument(bestTarget, true, openInTab, srcContext ? [srcContext] : [], undefined, undefined, undefined, () => PresBox.navigateToDoc(bestTarget, activeItem, true), undefined, true, NumCast(activeItem.presZoom)); });