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 { Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; import { Animation } 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 } from '../../../../Utils'; import { DocServer } from '../../../DocServer'; 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 { SerializationHelper } from '../../../util/SerializationHelper'; import { SettingsManager } from '../../../util/SettingsManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; import { CollectionDockingView } from '../../collections/CollectionDockingView'; import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm'; import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; import { CollectionView } from '../../collections/CollectionView'; import { TabDocView } from '../../collections/TabDocView'; import { TreeView } from '../../collections/TreeView'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; import { LightboxView } from '../../LightboxView'; import { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; const { Howl } = require('howler'); 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() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } static navigateToDocScript: ScriptField; constructor(props: any) { super(props); if (!PresBox.navigateToDocScript) { PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(self.presentation_targetDoc, self)')!; } } private _disposers: { [name: string]: IReactionDisposer } = {}; public selectedArray = new ObservableSet(); _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 @observable public static Instance: PresBox; @observable _isChildActive = false; @observable _moveOnFromAudio: boolean = true; @observable _presTimer!: NodeJS.Timeout; @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 = new Map(); @observable _presKeyEvents: boolean = false; @observable _forceKeyEvents: boolean = false; @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.rootDoc[this.presFieldKey]); } @computed get tagDocs() { return this.childDocs.map(doc => Cast(doc.presentation_targetDoc, Doc, null)); } @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } @computed get activeItem() { return DocCast(this.childDocs[NumCast(this.rootDoc._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.rootDoc); } @computed get isPres() { return this.selectedDoc === this.rootDoc; } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } 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)); @action 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.rootDoc) && 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 } ); this.props.setContentView?.(this); this._unmounting = false; if (this.props.renderDepth > 0) { runInAction(() => { this.rootDoc._forceRenderEngine = computeTimelineLayout.name; this.layoutDoc._gridGap = 0; this.layoutDoc._yMargin = 0; }); } this.turnOffEdit(true); this._disposers.selection = reaction( () => SelectionManager.Views().slice(), views => (!PresBox.Instance || views.some(view => view.props.Document === this.rootDoc)) && this.updateCurrentPresentation(), { fireImmediately: true } ); this._disposers.editing = reaction( () => this.layoutDoc.presentation_status === PresStatus.Edit, editing => { if (editing) { this.childDocs.forEach(doc => { if (doc.presentation_indexed !== undefined) { this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined)); doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1); } }); } } ); } @action updateCurrentPresentation = (pres?: Doc) => { Doc.ActivePresentation = pres ?? this.rootDoc; 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?.(); } }; //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(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: Doc[], normallyNextSlide: Doc) => { console.log(childrenToRun, normallyNextSlide, 'runSUBFUNC'); if (childrenToRun === undefined) { console.log('children undefined'); return; } if (childrenToRun[0] === normallyNextSlide) { return; } 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 = () => { 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?.docView && this.activeItem.presBulletExpand) { targetView.docView._animateScalingTo = 1.2; targetView.docView._animateScaleTime = 400; Doc.AddUnHighlightWatcher(() => { targetView.docView!._animateScaleTime = undefined; targetView!.docView!._animateScalingTo = 0; }); } 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.rootDoc[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.rootDoc._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.rootDoc._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, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) { const bestTarget = bestTargetView?.rootDoc ?? (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)); setTimeout(() => (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)?.props.CollectionFreeFormDocumentView?.().props.CollectionFreeFormView; if (ffview?.childDocs) { PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime); ffview.rootDoc._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 Doc.GetProto(bestTarget)[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?.inkable || (!pinDataTypes && (activeItem.config_fillColor !== undefined || activeItem.color !== undefined))) { if (bestTarget.fillColor !== activeItem.config_fillColor) { Doc.GetProto(bestTarget).fillColor = activeItem.config_fillColor; changed = true; } if (bestTarget.color !== activeItem.config_color) { Doc.GetProto(bestTarget).color = activeItem.config_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([]); 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; const contentBounds = Cast(activeItem.config_viewBounds, listSpec('number')); if (contentBounds) { const dv = DocumentManager.Instance.getDocumentView(bestTarget)?.ComponentView; dv?.brushView?.({ panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }, transTime); } } } 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([...oldItems, ...hiddenItems, ...newItems]); Doc.GetProto(bestTarget)[fkey + '_annotations'] = newList; } if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.config_pinLayoutData !== undefined)) { changed = true; const layoutField = Doc.LayoutFieldKey(bestTarget); const transitioned = new Set(); 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.GetProto(doc).data = field); data.text && (Doc.GetProto(doc).text = tfield); Doc.AddDocToList(Doc.GetProto(bestTarget), 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); } } 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(DocListCast(Doc.GetProto(targetDoc)[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( 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(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([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?.(); console.log('Finish Slide Nav: ' + targetDoc.title); 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.rootDoc); 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); }; 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; } const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined; const presTime = NumCast(activeItem.presentation_transition, effect ? 750 : 500); const options: DocFocusOptions = { 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: 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.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.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, tagDoc); let opacity: Opt = 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); }); }; _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.SetLightboxDoc(undefined); Doc.RemFromMyOverlay(this.rootDoc); 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.rootDoc.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.rootDoc); clearTimeout(this._presTimer); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0); this.props.removeDocument?.(this.layoutDoc); return PresBox.OpenPresMinimized(this.rootDoc, [pt[0] + (this.props.PanelWidth() - 250), pt[1] + 10]); }; exitMinimize = () => { if (Doc.IsInMyOverlay(this.layoutDoc)) { Doc.RemFromMyOverlay(this.rootDoc); CollectionDockingView.AddSplit(this.rootDoc, 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.rootDoc._pivotField = undefined); this.rootDoc._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(); 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; TabDocView.PinDoc(audio, { audioRange: true }); setTimeout(() => this.removeDocument(doc), 0); return false; } } else { 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.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; /** * 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 (
{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); }; focusElement = (doc: Doc, options: DocFocusOptions) => { 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.rootDoc._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; 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.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.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.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.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 @computed get order() { const order: JSX.Element[] = []; const docs: Doc[] = []; const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement(); const dv = DocumentManager.Instance.getDocumentView(presCollection); this.childDocs .filter(doc => Cast(doc.presentation_targetDoc, Doc, null)) .forEach((doc, index) => { const tagDoc = Cast(doc.presentation_targetDoc, Doc, null); const srcContext = Cast(tagDoc.embedContainer, 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.config_pinView && presCollection === tagDoc && dv) { // Case B: Document is presPinView and is presCollection const scale: number = 1 / NumCast(doc.config_viewScale); 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.config_panX) - width / 2; const yLoc: number = NumCast(doc.config_panY) - 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 _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) */ @computed get paths() { let pathPoints = ''; this.childDocs.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 ( ); } getPaths = (collection: Doc) => this.paths; // needs to be smarter and figure out the paths to draw for this specific collection. or better yet, draw everything in an overlay layer instad of within a collection // 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 @action updateHideBefore = (activeItem: Doc) => { activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore; this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore)); }; @undoBatch @action updateHide = (activeItem: Doc) => { activeItem.presentation_hide = !activeItem.presentation_hide; this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide)); }; @undoBatch @action updateHideAfter = (activeItem: Doc) => { activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter; this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter)); }; @undoBatch @action updateOpenDoc = (activeItem: Doc) => { activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox; this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox)); }; @undoBatch @action updateEaseFunc = (activeItem: Doc) => { activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear'; this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc)); }; @undoBatch @action updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect)); @undoBatch @action updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect))); 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 ( { PresBox._sliderBatch = UndoManager.StartBatch('pres slider'); document.addEventListener('pointerup', PresBox.endBatch, true); e.stopPropagation(); }} onChange={e => { e.stopPropagation(); change(e.target.value); }} /> ); }; @undoBatch @action 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 (
Hide before presented
}>
this.updateHideBefore(activeItem)}> Hide before
{'Hide while presented'}
}>
this.updateHide(activeItem)}> Hide
{'Hide after presented'}}>
this.updateHideAfter(activeItem)}> Hide after
{'Open in lightbox view'}}>
this.updateOpenDoc(activeItem)}> Lightbox
Transition movement style}>
this.updateEaseFunc(activeItem)}> {`${StrCast(activeItem.presEaseFunc, 'ease')}`}
{[DocumentType.AUDIO, DocumentType.VID].includes(targetType as any as DocumentType) ? null : ( <>
Slide Duration
e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s
this.updateDurationTime(String(duration), 1000)}>
this.updateDurationTime(String(duration), -1000)}>
{PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)}
Short
Medium
Long
)} ); } } @computed get progressivizeDropdown() { const activeItem = this.activeItem; if (activeItem && this.targetDoc) { const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None; const bulletEffect = (effect: PresEffect) => (
this.updateEffect(effect, true)}> {effect}
); return (
Progressivize Collection
{ 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(`self.presentation_targetDoc.annotationOn["${dataField}"]`); else activeItem.data = ComputedField.MakeFunction(`self.presentation_targetDoc["${dataField}"]`); }} checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false} />
Progressivize First Bullet
(activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1)} checked={!NumCast(activeItem.presentation_indexedStart)} />
Expand Current Bullet
(activeItem.presBulletExpand = !activeItem.presBulletExpand)} checked={BoolCast(activeItem.presBulletExpand)} />
Bullet Effect
{ 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()}
e.stopPropagation()}> {bulletEffect(PresEffect.None)} {bulletEffect(PresEffect.Fade)} {bulletEffect(PresEffect.Flip)} {bulletEffect(PresEffect.Rotate)} {bulletEffect(PresEffect.Bounce)} {bulletEffect(PresEffect.Roll)}
); } return null; } @computed get transitionDropdown() { const activeItem = this.activeItem; const preseEffect = (effect: PresEffect) => (
this.updateEffect(effect, false)}> {effect}
); const presMovement = (movement: PresMovement) => (
this.updateMovement(movement)}> {movement}
); 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 ( {direction}}>
this.updateEffectDirection(direction)}> {icon ? : null}
); }; 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 = activeItem.presentation_effect ? activeItem.presentation_effect : PresMovement.None; return (
{ e.stopPropagation(); this._openMovementDropdown = false; this._openEffectDropdown = false; this._openBulletEffectDropdown = false; })}>
Movement
{ e.stopPropagation(); this._openMovementDropdown = !this._openMovementDropdown; })} style={{ color: SettingsManager.userColor, background: SettingsManager.userVariantColor, borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, border: this._openMovementDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`, }}> {this.movementName(activeItem)}
{presMovement(PresMovement.None)} {presMovement(PresMovement.Center)} {presMovement(PresMovement.Zoom)} {presMovement(PresMovement.Pan)} {presMovement(PresMovement.Jump)}
Zoom (% screen filled)
this.updateZoom(e.target.value)} />%
this.updateZoom(String(zoom), 0.1)}>
this.updateZoom(String(zoom), -0.1)}>
{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}
Transition Time
e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s
this.updateTransitionTime(String(transitionSpeed), 1000)}>
this.updateTransitionTime(String(transitionSpeed), -1000)}>
{PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.updateTransitionTime)}
Fast
Medium
Slow
Effects
Play Audio Annotation
(activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} checked={BoolCast(activeItem.presPlayAudio)} />
Zoom Text Selections
(activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))} checked={BoolCast(activeItem.presentation_zoomText)} />
{ e.stopPropagation(); this._openEffectDropdown = !this._openEffectDropdown; })} style={{ color: SettingsManager.userColor, background: SettingsManager.userVariantColor, borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5, border: this._openEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`, }}> {effect?.toString()}
e.stopPropagation()}> {preseEffect(PresEffect.None)} {preseEffect(PresEffect.Fade)} {preseEffect(PresEffect.Flip)} {preseEffect(PresEffect.Rotate)} {preseEffect(PresEffect.Bounce)} {preseEffect(PresEffect.Roll)}
Effect direction
{StrCast(this.activeItem.presentation_effectDirection)}
{presDirection(PresEffectDirection.Left, 'angle-right', 1, 2, {})} {presDirection(PresEffectDirection.Right, 'angle-left', 3, 2, {})} {presDirection(PresEffectDirection.Top, 'angle-down', 2, 1, {})} {presDirection(PresEffectDirection.Bottom, 'angle-up', 2, 3, {})} {presDirection(PresEffectDirection.Center, '', 2, 2, { width: 10, height: 10, alignSelf: 'center' })}
this.applyTo(this.childDocs)}> Apply to all
); } } @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 (
e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}>
Start {'&'} End Time
Start time (s)
e.stopPropagation()} onChange={action(e => (activeItem.config_clipStart = Number(e.target.value)))} />
Duration (s)
{Math.round((config_clipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10}
End time (s)
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)))} />
{ 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) => { e.stopPropagation(); activeItem.config_clipEnd = Number(e.target.value); }} /> { 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) => { e.stopPropagation(); activeItem.config_clipStart = Number(e.target.value); }} />
{clipStart.toFixed(2)} s
{clipEnd.toFixed(2)} s
Playback
Start playing:
(activeItem.presentation_mediaStart = 'manual')} checked={activeItem.presentation_mediaStart === 'manual'} />
On click
(activeItem.presentation_mediaStart = 'auto')} checked={activeItem.presentation_mediaStart === 'auto'} />
Automatically
Stop playing:
(activeItem.presentation_mediaStop = 'manual')} checked={activeItem.presentation_mediaStop === 'manual'} />
At media end time
(activeItem.presentation_mediaStop = 'auto')} checked={activeItem.presentation_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 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.rootDoc.data, listSpec(Doc)); if (data && config_data) { data.push(doc); TabDocView.PinDoc(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, _layout_fitContentsToBox: true, x, y }); case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y }); case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y }); case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _layout_fitContentsToBox: true, x, y }) } }; // 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.enterMinimize(); this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }) )}> Mini-player
{ this.layoutDoc.presentation_status = 'manual'; this.initializePresState(this.itemIndex); this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }) )}> Sidebar player
); } @action turnOffEdit = (paths?: boolean) => paths && this.togglePath(true); // Turn off paths @computed get toolbarWidth(): number { return this.props.PanelWidth(); } @action toggleProperties = () => (SettingsManager.propertiesWidth = SettingsManager.propertiesWidth > 0 ? 0 : 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._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.rootDoc) ? null : (
{/*
{"Add new slide"}
}>
this.newDocumentTools = !this.newDocumentTools)}>
*/} View paths
}>
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}>
{isMini ? null : ( <>
{this._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._type_collection) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; return (
{isMini ? null : ( )}
{ if (this.childDocs.length) { this.layoutDoc.presentation_status = 'manual'; this.initializePresState(this.itemIndex); 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}
); } @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.rootDoc); // Case 1: There are still other frames and should go through all frames before going to next slide return (
{'Loop'}
}>
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}>
setupMoveUpEvents( this, e, returnFalse, emptyFunction, () => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presentation_status = PresStatus.Manual; } e.stopPropagation(); }, false, false ) }>
{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
}>
setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.startOrPause(true), false, false)}>
setupMoveUpEvents( this, e, returnFalse, emptyFunction, () => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); this.layoutDoc.presentation_status = PresStatus.Manual; } e.stopPropagation(); }, false, false ) }>
{'Click to return to 1st slide'}}>
setupMoveUpEvents( this, e, returnFalse, emptyFunction, () => { this.nextSlide(0); }, false, false ) }> 1
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}
{this.props.PanelWidth() > 250 ? (
{ this.layoutDoc.presentation_status = PresStatus.Edit; clearTimeout(this._presTimer); }) )}> EXIT
) : (
setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.exitClicked, false, false)}>
)} ); } @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 @action 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(sorted); // this is a flat array of Docs } } }; 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(this.sort(this._treeViewMap)); } }; sort = (treeView_Map: Map) => [...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.rootDoc._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
e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}>
{'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.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}
}>
setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.startOrPause(true), 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.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length}
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) { PresBox.NavigateToTarget(bestTarget, activeItem); });