diff options
| author | bobzel <zzzman@gmail.com> | 2023-04-05 22:44:03 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2023-04-05 22:44:03 -0400 |
| commit | 9b41da1af16b982ee8ac2fc09f2f8b5d67eac9fb (patch) | |
| tree | bc3f57cd5b31fd453d272c925f6d5b728ab63bae /src/client/views/nodes/trails/PresBox.tsx | |
| parent | 9dae453967183b294bf4f7444b948023a1d52d39 (diff) | |
| parent | 8f7e99641f84ad15f34ba9e4a60b664ac93d2e5d (diff) | |
Merge branch 'master' into data-visualization-view-naafi
Diffstat (limited to 'src/client/views/nodes/trails/PresBox.tsx')
| -rw-r--r-- | src/client/views/nodes/trails/PresBox.tsx | 3563 |
1 files changed, 1432 insertions, 2131 deletions
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 3bbdce1e4..bfaae8069 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1,49 +1,66 @@ import React = require('react'); import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@material-ui/core'; -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from 'mobx'; +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 { AnimationSym, Doc, DocListCast, FieldResult, Opt, StrListCast } from '../../../../fields/Doc'; +import { Copy, Id } from '../../../../fields/FieldSymbols'; +import { InkField, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; +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 { emptyFunction, returnFalse, returnOne, returnTrue, setupMoveUpEvents } from '../../../../Utils'; +import { AudioField } from '../../../../fields/URLField'; +import { emptyFunction, emptyPath, 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 { MarqueeViewBounds } from '../../collections/collectionFreeForm'; +import { CollectionFreeFormView, computeTimelineLayout, MarqueeViewBounds } from '../../collections/collectionFreeForm'; +import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; 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 { DocFocusOptions, DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; +import { ScriptingBox } from '../ScriptingBox'; import './PresBox.scss'; -import { PresEffect, PresMovement, PresStatus } from './PresEnums'; -import { CollectionFreeFormViewChrome } from '../../collections/CollectionMenu'; - +import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; +const { Howl } = require('howler'); + +export interface pinDataTypes { + scrollable?: boolean; + pannable?: boolean; + viewType?: boolean; + inkable?: boolean; + filters?: boolean; + pivot?: boolean; + temporal?: boolean; + clippable?: boolean; + datarange?: boolean; + dataview?: boolean; + textview?: boolean; + poslayoutview?: boolean; + dataannos?: boolean; +} export interface PinProps { audioRange?: boolean; activeFrame?: number; + currentFrame?: 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; + 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 @@ -51,68 +68,40 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } + static navigateToDocScript: ScriptField; - /** - * 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, - }; - switch (presDoc.presEffect) { - case PresEffect.Zoom: - return <Zoom {...effectProps}>{renderDoc}</Zoom>; - case PresEffect.Fade: - return <Fade {...effectProps}>{renderDoc}</Fade>; - case PresEffect.Flip: - return <Flip {...effectProps}>{renderDoc}</Flip>; - case PresEffect.Rotate: - return <Rotate {...effectProps}>{renderDoc}</Rotate>; - case PresEffect.Bounce: - return <Bounce {...effectProps}>{renderDoc}</Bounce>; - case PresEffect.Roll: - return <Roll {...effectProps}>{renderDoc}</Roll>; - case PresEffect.Lightspeed: - return <LightSpeed {...effectProps}>{renderDoc}</LightSpeed>; - case PresEffect.None: - default: - return renderDoc; + constructor(props: any) { + super(props); + if (!PresBox.navigateToDocScript) { + PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(self.presentationTargetDoc, self)')!; } } - 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 } = {}; + public selectedArray = new ObservableSet<Doc>(); + _batch: UndoManager.Batch | undefined = undefined; // undo batch for dragging sliders which generate multiple scene edit events as the cursor moves + _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation. + _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things @observable public static Instance: PresBox; @observable _isChildActive = false; @observable _moveOnFromAudio: boolean = true; @observable _presTimer!: NodeJS.Timeout; - @observable _presKeyEventsActive: boolean = false; - @observable _selectedArray: ObservableMap = new ObservableMap<Doc, any>(); @observable _eleArray: HTMLElement[] = []; @observable _dragArray: HTMLElement[] = []; @observable _pathBoolean: boolean = false; @observable _expandBoolean: boolean = false; - - private _disposers: { [name: string]: IReactionDisposer } = {}; - - @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; + @observable _transitionTools: boolean = false; + @observable _newDocumentTools: boolean = false; + @observable _openMovementDropdown: boolean = false; + @observable _openEffectDropdown: boolean = false; + @observable _openBulletEffectDropdown: boolean = false; + @observable _presentTools: boolean = false; + @observable _treeViewMap: Map<Doc, number> = new Map(); + @observable _presKeyEvents: boolean = false; + @observable _forceKeyEvents: boolean = false; @computed get isTreeOrStack() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._viewType) as any); } @@ -125,103 +114,111 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get childDocs() { return DocListCast(this.rootDoc[this.presFieldKey]); } - @observable _treeViewMap: Map<Doc, number> = 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; + return this.childDocs.map(doc => Cast(doc.presentationTargetDoc, Doc, null)); } @computed get itemIndex() { return NumCast(this.rootDoc._itemIndex); } @computed get activeItem() { - return Cast(this.childDocs[NumCast(this.rootDoc._itemIndex)], Doc, null); + return DocCast(this.childDocs[NumCast(this.rootDoc._itemIndex)]); } @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; + public static targetRenderedDoc = (doc: Doc) => { + const targetDoc = Cast(doc?.presentationTargetDoc, Doc, null); + return targetDoc?.unrendered ? DocCast(targetDoc.annotationOn) : targetDoc; + }; + @computed get scrollable() { + if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._viewType === CollectionViewType.Stacking) return true; + return false; } - @computed get panable(): boolean { + @computed get panable() { if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; - else return false; - } - 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 + return false; } @computed get selectedDocumentView() { if (SelectionManager.Views().length) return SelectionManager.Views()[0]; - if (this._selectedArray.size) return DocumentManager.Instance.getDocumentView(this.rootDoc); + 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 isPres() { + return this.selectedDoc === this.rootDoc; } @computed get selectedDoc() { return this.selectedDocumentView?.rootDoc; } + isActiveItemTarget = (layoutDoc: Doc) => this.activeItem?.presentationTargetDoc === layoutDoc; + clearSelectedArray = () => this.selectedArray.clear(); + addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc)); + removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc)); - _unmounting = false; @action componentWillUnmount() { this._unmounting = true; + if (this._presTimer) clearTimeout(this._presTimer); 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._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; - this.rootDoc._forceRenderEngine = 'timeline'; - this.layoutDoc.presStatus = PresStatus.Edit; - this.layoutDoc._gridGap = 0; - this.layoutDoc._yMargin = 0; + if (this.props.renderDepth > 0) { + runInAction(() => { + this.rootDoc._forceRenderEngine = computeTimelineLayout.name; + 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() ); + this._disposers.editing = reaction( + () => this.layoutDoc.presStatus === PresStatus.Edit, + editing => { + if (editing) { + this.childDocs.forEach(doc => { + if (doc.presIndexed !== undefined) { + this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined)); + doc.presIndexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1); + } + }); + } + } + ); } @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; + Doc.ActivePresentation = pres ?? this.rootDoc; 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) => { @@ -233,7 +230,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }; stopTempMedia = (targetDocField: FieldResult) => { - const targetDoc = Cast(targetDocField, Doc, null); + 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?.(); @@ -243,80 +240,122 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { //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); - } - } + 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].groupWithUp) { + this.addToSelectedArray(this.childDocs[nextSelected]); + const serial = nextSelected + 1 < this.childDocs.length && NumCast(this.childDocs[nextSelected + 1].groupWithUp) > 1; + if (serial) { + this.gotoDocument(nextSelected, this.activeItem, true, async () => { + const waitTime = NumCast(this.activeItem.presDuration) - NumCast(this.activeItem.presTransition); + await new Promise<void>(res => setTimeout(() => res(), Math.max(0, waitTime))); + doGroupWithUp(nextSelected + 1)(); + }); + } else { + this.gotoDocument(nextSelected, this.activeItem, true); + curSlideInd = this.itemIndex; + doGroupWithUp(nextSelected + 1)(); + } + } + } + }; + doGroupWithUp(curSlideInd, true)(); }; + // docs within a slide target that will be progressively revealed + progressivizedItems = (doc: Doc) => { + const targetList = PresBox.targetRenderedDoc(doc); + if (doc.presIndexed !== 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.unrendered); + } + }; // 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); + const progressiveReveal = (first: boolean) => { + const presIndexed = Cast(this.activeItem?.presIndexed, 'number', null); + if (presIndexed !== undefined) { + const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem); + targetRenderedDoc._dataTransition = 'all 1s'; + targetRenderedDoc.opacity = 1; + setTimeout(() => (targetRenderedDoc._dataTransition = 'inherit'), 1000); + const listItems = this.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.presEffect = this.activeItem.presBulletEffect; + listItemDoc.presTransition = 500; + targetView?.setAnimEffect(listItemDoc, 500); + if (targetView?.docView && this.activeItem.presBulletExpand) { + targetView.docView._animateScalingTo = 1.1; + Doc.AddUnHighlightWatcher(() => (targetView!.docView!._animateScalingTo = 0)); + } + listItemDoc.opacity = undefined; + this.activeItem.presIndexed = presIndexed + 1; + } + return true; + } + } + }; + if (progressiveReveal(false)) return true; + if (this.childDocs[this.itemIndex + 1] !== undefined) { + // Case 1: No more frames in current doc and next slide is defined, therefore move to next slide + const slides = DocListCast(this.rootDoc[StrCast(this.presFieldKey, 'data')]); + const curLast = this.selectedArray.size ? Math.max(...Array.from(this.selectedArray).map(d => slides.indexOf(DocCast(d)))) : this.itemIndex; + this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1); + progressiveReveal(true); // shows first progressive document, but without a transition effect + } else { + if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) { + // Case 2: Last slide and presLoop is toggled ON or it is in Edit mode + this.nextSlide(0); + progressiveReveal(true); // shows first progressive document, but without a transition effect + } + return 0; } + 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; - 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; + for (; prevSelected > 0 && this.childDocs[Math.max(0, prevSelected - 1)].groupWithUp; prevSelected--) { + didZoom = didZoom === 'none' ? this.childDocs[prevSelected].presMovement : didZoom; } - 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) { + 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); + 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.gotoDocument(this.childDocs.length - 1, activeItem); + 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. - public gotoDocument = action((index: number, from?: Doc, group?: boolean) => { + @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; - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; if (from?.mediaStopTriggerList && this.layoutDoc.presStatus !== PresStatus.Edit) { DocListCast(from.mediaStopTriggerList).forEach(this.stopTempMedia); } @@ -324,67 +363,312 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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 (this.layoutDoc.presStatus !== PresStatus.Edit && (this.targetDoc.type === DocumentType.AUDIO || this.targetDoc.type === DocumentType.VID) && this.activeItem.mediaStart === 'auto') { + this.startTempMedia(this.targetDoc, this.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 (!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 presTrail 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?._viewType === CollectionViewType.Stacking; + const pannable = [DocumentType.IMG, DocumentType.PDF].includes(targetType) || (targetType === DocumentType.COL && target?._viewType === CollectionViewType.Freeform); + 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].includes(targetType) && target?.activeFrame === undefined; + const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined; + const textview = [DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; + const viewType = targetType === DocumentType.COL; + const filters = true; + const pivot = true; + const dataannos = false; + return { scrollable, pannable, inkable, viewType, pivot, filters, temporal, clippable, dataview, datarange, textview, poslayoutview, dataannos }; + } + + @action + playAnnotation = (anno: AudioField) => {}; + @action + static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.presPinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) { + const bestTarget = bestTargetView?.rootDoc ?? (targetDoc?.unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc); + if (!bestTarget || activeItem === bestTarget) return; + let changed = false; + if (pinDocLayout) { + if ( + bestTarget.x !== NumCast(activeItem.presX, NumCast(bestTarget.x)) || + bestTarget.y !== NumCast(activeItem.presY, NumCast(bestTarget.y)) || + bestTarget.rotation !== NumCast(activeItem.presRotation, NumCast(bestTarget.rotation)) || + bestTarget.width !== NumCast(activeItem.presWidth, NumCast(bestTarget.width)) || + bestTarget.height !== NumCast(activeItem.presHeight, NumCast(bestTarget.height)) + ) { + bestTarget._dataTransition = `all ${transTime}ms`; + bestTarget.x = NumCast(activeItem.presX, NumCast(bestTarget.x)); + bestTarget.y = NumCast(activeItem.presY, NumCast(bestTarget.y)); + bestTarget.rotation = NumCast(activeItem.presRotation, NumCast(bestTarget.rotation)); + bestTarget.width = NumCast(activeItem.presWidth, NumCast(bestTarget.width)); + bestTarget.height = NumCast(activeItem.presHeight, NumCast(bestTarget.height)); + setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); + changed = true; } - if (targetDoc?.lastFrame !== undefined) { - targetDoc._currentFrame = 0; + } + + const activeFrame = activeItem.presActiveFrame ?? activeItem.presCurrentFrame; + if (activeFrame !== undefined) { + const transTime = NumCast(activeItem.presTransition, 500); + const acontext = activeItem.presActiveFrame !== undefined ? DocCast(DocCast(activeItem.presentationTargetDoc).context) : DocCast(activeItem.presentationTargetDoc); + 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?.datarange || (!pinDataTypes && activeItem.presXRange !== undefined)) { + if (bestTarget.xRange !== activeItem.presXRange) { + bestTarget.xRange = (activeItem.presXRange as ObjectField)?.[Copy](); + changed = true; + } + if (bestTarget.yRange !== activeItem.presYRange) { + bestTarget.yRange = (activeItem.presYRange as ObjectField)?.[Copy](); + changed = true; + } + } + if (pinDataTypes?.clippable || (!pinDataTypes && activeItem.presClipWidth !== undefined)) { + if (bestTarget._clipWidth !== activeItem.presClipWidth) { + bestTarget._clipWidth = activeItem.presClipWidth; + changed = true; + } + } + if (pinDataTypes?.temporal || (!pinDataTypes && activeItem.presStartTime !== undefined)) { + if (bestTarget._currentTimecode !== activeItem.presStartTime) { + bestTarget._currentTimecode = activeItem.presStartTime; + changed = true; + } + } + if (pinDataTypes?.inkable || (!pinDataTypes && (activeItem.presFillColor !== undefined || activeItem.color !== undefined))) { + if (bestTarget.fillColor !== activeItem.presFillColor) { + Doc.GetProto(bestTarget).fillColor = activeItem.presFillColor; + changed = true; + } + if (bestTarget.color !== activeItem.presColor) { + Doc.GetProto(bestTarget).color = activeItem.presColor; + changed = true; + } + if (bestTarget.width !== activeItem.width) { + bestTarget._width = NumCast(activeItem.presWidth, NumCast(bestTarget.width)); + changed = true; + } + if (bestTarget.height !== activeItem.height) { + bestTarget._height = NumCast(activeItem.presHeight, NumCast(bestTarget.height)); + changed = true; + } + } + if ((pinDataTypes?.viewType && activeItem.presViewType !== undefined) || (!pinDataTypes && activeItem.presViewType !== undefined)) { + if (bestTarget._viewType !== activeItem.presViewType) { + bestTarget._viewType = activeItem.presViewType; + changed = true; } - if (!group) this._selectedArray.clear(); - this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //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); - }; + if ((pinDataTypes?.filters && activeItem.presDocFilters !== undefined) || (!pinDataTypes && activeItem.presDocFilters !== undefined)) { + if (bestTarget.docFilters !== activeItem.presDocFilters) { + bestTarget.docFilters = ObjectField.MakeCopy(activeItem.presDocFilters as ObjectField) || new List<string>([]); + changed = true; + } + } - // 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 ((pinDataTypes?.pivot && activeItem.presPivotField !== undefined) || (!pinDataTypes && activeItem.presPivotField !== undefined)) { + if (bestTarget.pivotField !== activeItem.presPivotField) { + bestTarget.pivotField = activeItem.presPivotField; + bestTarget._prevFilterIndex = 1; // need to revisit this...see CollectionTimeView + changed = true; + } + } + + if (pinDataTypes?.scrollable || (!pinDataTypes && activeItem.presViewScroll !== undefined)) { + if (bestTarget._scrollTop !== activeItem.presViewScroll) { + bestTarget._scrollTop = activeItem.presViewScroll; + changed = true; + const contentBounds = Cast(activeItem.presPinViewBounds, 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] }); + } + } + } + if (pinDataTypes?.dataannos || (!pinDataTypes && activeItem.presAnnotations !== undefined)) { + const fkey = Doc.LayoutFieldKey(bestTarget); + const oldItems = DocListCast(bestTarget[fkey + '-annotations']).filter(doc => doc.unrendered); + const newItems = DocListCast(activeItem.presAnnotations).map(doc => { + doc.hidden = false; + return doc; + }); + const hiddenItems = DocListCast(bestTarget[fkey + '-annotations']) + .filter(doc => !doc.unrendered && !newItems.includes(doc)) + .map(doc => { + doc.hidden = true; + return doc; + }); + const newList = new List<Doc>([...oldItems, ...hiddenItems, ...newItems]); + Doc.GetProto(bestTarget)[fkey + '-annotations'] = newList; + } + if ((pinDataTypes?.dataview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) { + bestTarget._dataTransition = `all ${transTime}ms`; + const fkey = Doc.LayoutFieldKey(bestTarget); + Doc.GetProto(bestTarget)[fkey] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + bestTarget[fkey + '-useAlt'] = activeItem.presUseAlt; + setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); + } + if ((pinDataTypes?.textview && activeItem.presData !== undefined) || (!pinDataTypes && activeItem.presData !== undefined)) { + Doc.GetProto(bestTarget)[Doc.LayoutFieldKey(bestTarget)] = activeItem.presData instanceof ObjectField ? activeItem.presData[Copy]() : activeItem.presData; + } + if (pinDataTypes?.poslayoutview || (!pinDataTypes && activeItem.presPinLayoutData !== undefined)) { + changed = true; + const layoutField = Doc.LayoutFieldKey(bestTarget); + const transitioned = new Set<Doc>(); + StrListCast(activeItem.presPinLayoutData) + .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.presPinViewBounds !== undefined || activeItem.presPanX !== undefined || activeItem.presViewScale !== undefined))) && !bestTarget._isGroup) { + const contentBounds = Cast(activeItem.presPinViewBounds, listSpec('number')); if (contentBounds) { - bestTarget._panX = (contentBounds[0] + contentBounds[2]) / 2; - bestTarget._panY = (contentBounds[1] + contentBounds[3]) / 2; + 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._panX = viewport.panX; + bestTarget._panY = viewport.panY; 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])); + const computedScale = NumCast(activeItem.presZoom, 1) * Math.min(dv.props.PanelWidth() / viewport.width, dv.props.PanelHeight() / viewport.height); + activeItem.presMovement === PresMovement.Zoom && (bestTarget._viewScale = computedScale); + dv.ComponentView?.brushView?.(viewport); } } else { - bestTarget._panX = activeItem.presPinViewX; - bestTarget._panY = activeItem.presPinViewY; - bestTarget._viewScale = activeItem.presPinViewScale; + if (bestTarget._panX !== activeItem.presPanX || bestTarget._panY !== activeItem.presPanY || bestTarget._viewScale !== activeItem.presViewScale) { + bestTarget._panX = activeItem.presPanX ?? bestTarget._panX; + bestTarget._panY = activeItem.presPanY ?? bestTarget._panY; + bestTarget._viewScale = activeItem.presViewScale ?? bestTarget._viewScale; + changed = true; + } } } - return setTimeout(() => (bestTarget._viewTransition = undefined), activeItem.presTransition ? NumCast(activeItem.presTransition) + 10 : 510); + 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) { + if (pinProps.pinDocLayout) { + pinDoc.presPinLayout = true; + pinDoc.presX = NumCast(targetDoc.x); + pinDoc.presY = NumCast(targetDoc.y); + pinDoc.presRotation = NumCast(targetDoc.rotation); + pinDoc.presWidth = NumCast(targetDoc.width); + pinDoc.presHeight = NumCast(targetDoc.height); + } + if (pinProps.pinAudioPlay) pinDoc.presPlayAudio = true; + if (pinProps.pinData) { + pinDoc.presPinData = + pinProps.pinData.scrollable || + pinProps.pinData.temporal || + pinProps.pinData.pannable || + pinProps.pinData.viewType || + pinProps.pinData.clippable || + pinProps.pinData.datarange || + pinProps.pinData.dataview || + pinProps.pinData.textview || + pinProps.pinData.poslayoutview || + pinProps?.activeFrame !== undefined; + const fkey = Doc.LayoutFieldKey(targetDoc); + if (pinProps.pinData.dataview) { + pinDoc.presUseAlt = targetDoc[fkey + '-useAlt']; + pinDoc.presData = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; + } + if (pinProps.pinData.dataannos) { + const fkey = Doc.LayoutFieldKey(targetDoc); + pinDoc.presAnnotations = new List<Doc>(DocListCast(Doc.GetProto(targetDoc)[fkey + '-annotations']).filter(doc => !doc.unrendered)); + } + if (pinProps.pinData.textview) pinDoc.presData = targetDoc[Doc.LayoutFieldKey(targetDoc)] instanceof ObjectField ? (targetDoc[Doc.LayoutFieldKey(targetDoc)] as ObjectField)[Copy]() : targetDoc.text; + if (pinProps.pinData.inkable) { + pinDoc.presFillColor = targetDoc.fillColor; + pinDoc.presColor = targetDoc.color; + pinDoc.presWidth = targetDoc._width; + pinDoc.presHeight = targetDoc._height; + } + if (pinProps.pinData.scrollable) pinDoc.presViewScroll = targetDoc._scrollTop; + if (pinProps.pinData.clippable) pinDoc.presClipWidth = targetDoc._clipWidth; + if (pinProps.pinData.datarange) { + pinDoc.presXRange = undefined; //targetDoc?.xrange; + pinDoc.presYRange = undefined; //targetDoc?.yrange; + } + if (pinProps.pinData.poslayoutview) + pinDoc.presPinLayoutData = new List<string>( + DocListCast(targetDoc[fkey] as ObjectField).map(d => + JSON.stringify({ + id: d[Id], + x: NumCast(d.x), + y: NumCast(d.y), + w: NumCast(d._width), + h: NumCast(d._height), + fill: StrCast(d._fillColor), + back: StrCast(d._backgroundColor), + data: SerializationHelper.Serialize(d.data instanceof ObjectField ? d.data[Copy]() : ''), + text: SerializationHelper.Serialize(d.text instanceof ObjectField ? d.text[Copy]() : ''), + }) + ) + ); + if (pinProps.pinData.viewType) pinDoc.presViewType = targetDoc._viewType; + if (pinProps.pinData.filters) pinDoc.presDocFilters = ObjectField.MakeCopy(targetDoc.docFilters as ObjectField); + if (pinProps.pinData.pivot) pinDoc.presPivotField = targetDoc._pivotField; + if (pinProps.pinData.pannable) { + pinDoc.presPanX = NumCast(targetDoc._panX); + pinDoc.presPanY = NumCast(targetDoc._panY); + pinDoc.presViewScale = NumCast(targetDoc._viewScale, 1); + } + if (pinProps.pinData.temporal) { + pinDoc.presStartTime = targetDoc._currentTimecode; + const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}-duration`], NumCast(targetDoc.presStartTime) + 0.1); + pinDoc.presEndTime = 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.presPinView = true; + pinDoc.presViewScale = NumCast(targetDoc._viewScale, 1); + pinDoc.presPanX = bounds.left + bounds.width / 2; + pinDoc.presPanY = bounds.top + bounds.height / 2; + pinDoc.presPinViewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); + } + } /** * This method makes sure that cursor navigates to the element that * has the option open and last in the group. @@ -393,188 +677,161 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { * a new tab. If presCollection is undefined it will open the document * on the right. */ - navigateToElement = async (curDoc: Doc) => { + navigateToActiveItem = (afterNav?: () => void) => { 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.keys()); + const finished = () => { + afterNav?.(); + console.log('Finish Slide Nav: ' + targetDoc.title); + targetDoc[AnimationSym] = undefined; + }; + 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._selectedArray.clear(); - selViewCache.forEach(doc => self._selectedArray.set(doc, undefined)); - 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 (!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); } - }; - // 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); - } + finished(); + }); + PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection); }; - /** - * 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; + static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) { + if (activeItem.presMovement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { + (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); + return; } - 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'; + const effect = activeItem.presEffect && activeItem.presEffect !== PresEffect.None ? activeItem.presEffect : undefined; + const presTime = NumCast(activeItem.presTransition, effect ? 750 : 500); + const options: DocFocusOptions = { + willPan: activeItem.presMovement !== PresMovement.None, + willZoomCentered: activeItem.presMovement === PresMovement.Zoom || activeItem.presMovement === PresMovement.Jump || activeItem.presMovement === PresMovement.Center, + zoomScale: activeItem.presMovement === PresMovement.Center ? 0 : NumCast(activeItem.presZoom, 1), + zoomTime: activeItem.presMovement === 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.presZoomText), + playAudio: BoolCast(activeItem.presPlayAudio), + }; + if (activeItem.presOpenInLightbox) { + const context = DocCast(targetDoc.annotationOn) ?? targetDoc; + if (!DocumentManager.Instance.getLightboxDocumentView(context)) { + LightboxView.SetLightboxDoc(context); + } } - }; + if (targetDoc) { + if (activeItem.presentationTargetDoc instanceof Doc) activeItem.presentationTargetDoc[AnimationSym] = 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 - onHideDocument = () => { + doHideBeforeAfter = () => { this.childDocs.forEach((doc, index) => { const curDoc = Cast(doc, Doc, null); - const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); - //if (tagDoc) tagDoc.opacity = 1; + const tagDoc = PresBox.targetRenderedDoc(curDoc); const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, tagDoc); - const curInd: number = itemIndexes.indexOf(index); - if (tagDoc === this.layoutDoc.presCollection) { - tagDoc.opacity = 1; - } else { - if (itemIndexes.length > 1 && curDoc.presHideBefore && curInd !== 0) { - } else if (curDoc.presHideBefore) { - if (index > this.itemIndex) { - tagDoc.opacity = 0; - } else if (index === this.itemIndex || !curDoc.presHideAfter) { - tagDoc.opacity = 1; - } + let opacity: Opt<number> = index === this.itemIndex ? 1 : undefined; + if (curDoc.presHide) { + if (index !== this.itemIndex) { + opacity = 1; } - if (itemIndexes.length > 1 && curDoc.presHideAfter && curInd !== itemIndexes.length - 1) { - } else if (curDoc.presHideAfter) { - if (index < this.itemIndex) { - tagDoc.opacity = 0; - } else if (index === this.itemIndex || !curDoc.presHideBefore) { - tagDoc.opacity = 1; - } + } + const hidingIndBef = itemIndexes.find(item => item >= this.itemIndex) ?? itemIndexes.slice().reverse().lastElement(); + if (curDoc.presHideBefore && index === hidingIndBef) { + if (index > this.itemIndex) { + opacity = 0; + } else if (index === this.itemIndex || !curDoc.presHideAfter) { + opacity = 1; + setTimeout(() => (tagDoc._dataTransition = undefined), 1000); + } + } + const hidingIndAft = + itemIndexes + .slice() + .reverse() + .find(item => item <= this.itemIndex) ?? itemIndexes.lastElement(); + if (curDoc.presHideAfter && index === hidingIndAft) { + if (index < this.itemIndex) { + opacity = 0; + } else if (index === this.itemIndex || !curDoc.presHideBefore) { + opacity = 1; } } + const hidingInd = itemIndexes.find(item => item === this.itemIndex); + if (curDoc.presHide && index === hidingInd) { + if (index === this.itemIndex) { + opacity = 0; + } + } + opacity !== undefined && (tagDoc.opacity = opacity); }); }; - //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(); + _exitTrail: Opt<() => void>; + PlayTrail = (docs: Doc[]) => { + const savedStates = docs.map(doc => { + switch (doc.type) { + case DocumentType.COL: + if (doc._viewType === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.panX), y: NumCast(doc.panY), s: NumCast(doc.viewScale) }; + 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) }; } - } - - 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); - } } + 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._panX = x; + doc._panY = y; + doc._viewScale = 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.RemoveDocFromList(Doc.MyOverlayDocs, undefined, this.rootDoc); + return PresStatus.Edit; }; - this.layoutDoc.presStatus = PresStatus.Autoplay; - this.startPresentation(startSlide); - this.gotoDocument(startSlide, activeItem); - load(); }; // The function pauses the auto presentation @@ -583,7 +840,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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); } }; @@ -591,9 +847,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { //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)) + .map(doc => PresBox.targetRenderedDoc(doc)) .filter(doc => doc instanceof Doc) .forEach(doc => { try { @@ -605,14 +860,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }; // 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; - } + @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 @@ -624,45 +874,74 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }); }; + initializePresState = (startIndex: number) => { + this.childDocs.forEach((doc, index) => { + const tagDoc = PresBox.targetRenderedDoc(doc); + if (doc.presHideBefore && index > startIndex) tagDoc.opacity = 0; + if (doc.presHideAfter && index < startIndex) tagDoc.opacity = 0; + if (doc.presIndexed !== undefined && index >= startIndex) { + const startInd = NumCast(doc.presIndexedStart); + this.progressivizedItems(doc) + ?.slice(startInd) + .forEach(indexedDoc => (indexedDoc.opacity = 0)); + doc.presIndexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, startInd); + } + // if (doc.presHide && this.childDocs.indexOf(doc) === startIndex) tagDoc.opacity = 0; + }); + }; + /** * The function that starts the presentation at the given index, also checking if actions should be applied * directly at start. * @param startIndex: index that the presentation will start at */ + @action 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; - } - }); + PresBox.Instance = this; + clearTimeout(this._presTimer); + if (this.childDocs.length) { + this.layoutDoc.presStatus = PresStatus.Autoplay; + this.initializePresState(startIndex); + const func = () => { + const delay = NumCast(this.activeItem.presDuration, this.activeItem.type === DocumentType.SCRIPTING ? 0 : 2500) + NumCast(this.activeItem.presTransition); + this._presTimer = setTimeout(() => { + if (!this.next()) this.layoutDoc.presStatus = this._exitTrail?.() ?? PresStatus.Manual; + this.layoutDoc.presStatus === PresStatus.Autoplay && func(); + }, delay); + }; + this.gotoDocument(startIndex, this.activeItem, undefined, func); + } }; /** * The method called to open the presentation as a minimized view */ @action - updateMinimize = async () => { + 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 (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); + 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.AddDocToList(Doc.MyOverlayDocs, undefined, doc); + PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex); + return (doc.presStatus = PresStatus.Manual); + } /** * Called when the user changes the view type @@ -695,38 +974,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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); + // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list;\ } else { this.childDocs[stopDocIndex - 1].mediaStopTriggerList = new List<Doc>(); const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList); list.push(activeItem); // this.childDocs[stopDocIndex - 1].mediaStopTriggerList = list; - 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 + movementName = action((activeItem: Doc) => { + if (![PresMovement.Zoom, PresMovement.Pan, PresMovement.Center, PresMovement.Jump, PresMovement.None].includes(StrCast(activeItem.presMovement) as any)) { + return PresMovement.Zoom; } - return output; + return StrCast(activeItem.presMovement); }); whenChildContentsActiveChanged = action((isActive: boolean) => this.props.whenChildContentsActiveChanged((this._isChildActive = isActive))); @@ -749,7 +1010,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } else { if (!doc.aliasOf) { const original = Doc.MakeAlias(doc); - TabDocView.PinDoc(original); + TabDocView.PinDoc(original, {}); setTimeout(() => this.removeDocument(doc), 0); return false; } else { @@ -766,22 +1027,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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; + isContentActive = (outsideReaction?: boolean) => this.props.isContentActive(outsideReaction); + //.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)); - }; + 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() { - const list = Array.from(this._selectedArray.keys()).map((doc: Doc, index: any) => { + 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) @@ -805,7 +1063,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> ); }); - return list; } @action @@ -814,49 +1071,47 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { presDocView && SelectionManager.SelectView(presDocView, false); }; + focusElement = (doc: Doc, options: DocFocusOptions) => { + this.selectElement(doc); + return undefined; + }; + //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); - - if (this.activeItem.presActiveFrame !== undefined) { - const context = DocCast(DocCast(this.activeItem.presentationTargetDoc).context); - context && CollectionFreeFormViewChrome.gotoKeyFrame(context, NumCast(this.activeItem.presActiveFrame)); - } + 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.context)); }; //Command click @action multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { - if (!this._selectedArray.has(doc)) { - this._selectedArray.set(doc, undefined); + if (!this.selectedArray.has(doc)) { + this.addToSelectedArray(doc); this._eleArray.push(ref); this._dragArray.push(drag); } else { - this._selectedArray.delete(doc); - this.removeFromArray(this._eleArray, doc); - this.removeFromArray(this._dragArray, doc); + this.removeFromSelectedArray(doc); + this._eleArray.splice(this._eleArray.indexOf(ref)); + this._dragArray.splice(this._dragArray.indexOf(drag)); } 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._selectedArray.clear(); + 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._selectedArray.set(this.childDocs[i], undefined); + this.addToSelectedArray(this.childDocs[i]); this._eleArray.push(ref); this._dragArray.push(drag); } @@ -866,24 +1121,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { //regular click @action - regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, selectPres = true) => { - this._selectedArray.clear(); - this._selectedArray.set(doc, undefined); + 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); - focus && this.selectElement(doc); + this.selectElement(doc, noNav); selectPres && this.selectPres(); }; - modifierSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, focus: boolean, cmdClick: boolean, shiftClick: boolean) => { + 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, focus); + else this.regularSelect(doc, ref, drag, noNav); }; - static keyEventsWrapper = (e: KeyboardEvent) => { - PresBox.Instance.keyEvents(e); - }; + static keyEventsWrapper = (e: KeyboardEvent) => PresBox.Instance?.keyEvents(e); // Key for when the presentaiton is active @action @@ -897,10 +1150,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (this.layoutDoc.presStatus === 'edit') { undoBatch( action(() => { - for (const doc of Array.from(this._selectedArray.keys())) { + for (const doc of this.selectedArray) { this.removeDocument(doc); } - this._selectedArray.clear(); + this.clearSelectedArray(); this._eleArray.length = 0; this._dragArray.length = 0; }) @@ -910,11 +1163,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { break; case 'Escape': if (DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc)) { - this.updateMinimize(); - } else if (this.layoutDoc.presStatus === 'edit') { - this._selectedArray.clear(); + this.exitClicked(); + } else if (this.layoutDoc.presStatus === PresStatus.Edit) { + this.clearSelectedArray(); this._eleArray.length = this._dragArray.length = 0; - } else this.layoutDoc.presStatus = 'edit'; + } else { + this.layoutDoc.presStatus = PresStatus.Edit; + } if (this._presTimer) clearTimeout(this._presTimer); handled = true; break; @@ -925,7 +1180,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (e.shiftKey && this.itemIndex < this.childDocs.length - 1) { // TODO: update to work properly this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) + 1; - this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); + this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]); } else { this.next(); if (this._presTimer) { @@ -942,7 +1197,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (e.shiftKey && this.itemIndex !== 0) { // TODO: update to work properly this.rootDoc._itemIndex = NumCast(this.rootDoc._itemIndex) - 1; - this._selectedArray.set(this.childDocs[this.rootDoc._itemIndex], undefined); + this.addToSelectedArray(this.childDocs[this.rootDoc._itemIndex]); } else { this.back(); if (this._presTimer) { @@ -954,14 +1209,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { break; case 'Spacebar': case ' ': - if (this.layoutDoc.presStatus === PresStatus.Manual) this.startAutoPres(this.itemIndex); + if (this.layoutDoc.presStatus === PresStatus.Manual) this.startOrPause(true); 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._selectedArray.clear(); - this.childDocs.forEach(doc => this._selectedArray.set(doc, undefined)); + this.clearSelectedArray(); + this.childDocs.forEach(doc => this.addToSelectedArray(doc)); handled = true; } default: @@ -973,30 +1228,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } }; - /** - * - */ - @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; - }; + 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 = Cast(this.rootDoc.presCollection, Doc, null); + const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement(); const dv = DocumentManager.Instance.getDocumentView(presCollection); this.childDocs .filter(doc => Cast(doc.presentationTargetDoc, Doc, null)) @@ -1036,15 +1274,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } } else if (doc.presPinView && presCollection === tagDoc && dv) { // Case B: Document is presPinView and is presCollection - const scale: number = 1 / NumCast(doc.presPinViewScale); + const scale: number = 1 / NumCast(doc.presViewScale); 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; + const xLoc: number = NumCast(doc.presPanX) - width / 2; + const yLoc: number = NumCast(doc.presPanY) - height / 2; docs.push(tagDoc); order.push( <> @@ -1069,18 +1307,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { */ @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) { + 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.presPinView && presCollection === tagDoc) { - const n1x = NumCast(doc.presPinViewX); - const n1y = NumCast(doc.presPinViewY); + } else if (doc.presPinView) { + const n1x = NumCast(doc.presPanX); + const n1y = NumCast(doc.presPanY); if ((index = 0)) pathPoints = n1x + ',' + n1y; else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; } @@ -1102,835 +1338,555 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { /> ); } + 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 presTransition - setTransitionTime = (number: String, change?: number) => { + 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 > 10000) timeInMS = 10000; - Array.from(this._selectedArray.keys()).forEach(doc => (doc.presTransition = timeInMS)); + if (timeInMS > 100000) timeInMS = 100000; + setter(timeInMS); + }; + + @undoBatch + updateTransitionTime = (number: String, change?: number) => { + PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presTransition = timeInMS)), change); }; // Converts seconds to ms and updates presTransition - setZoom = (number: String, change?: number) => { + @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.5) scale = 1.5; - Array.from(this._selectedArray.keys()).forEach(doc => (doc.presZoom = scale)); + if (scale > 1) scale = 1; + this.selectedArray.forEach(doc => (doc.presZoom = scale)); }; - // Converts seconds to ms and updates presDuration - setDurationTime = (number: String, change?: number) => { + /* + * Converts seconds to ms and updates presDuration + */ + @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; - Array.from(this._selectedArray.keys()).forEach(doc => (doc.presDuration = timeInMS)); + this.selectedArray.forEach(doc => (doc.presDuration = timeInMS)); }; - /** - * When the movement dropdown is changes - */ @undoBatch - updateMovement = action((movement: any, all?: boolean) => { - const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.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; - } - }); - }); + updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presMovement = movement))); @undoBatch @action updateHideBefore = (activeItem: Doc) => { activeItem.presHideBefore = !activeItem.presHideBefore; - Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); + this.selectedArray.forEach(doc => (doc.presHideBefore = activeItem.presHideBefore)); + }; + + @undoBatch + @action + updateHide = (activeItem: Doc) => { + activeItem.presHide = !activeItem.presHide; + this.selectedArray.forEach(doc => (doc.presHide = activeItem.presHide)); }; @undoBatch @action updateHideAfter = (activeItem: Doc) => { activeItem.presHideAfter = !activeItem.presHideAfter; - Array.from(this._selectedArray.keys()).forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); + this.selectedArray.forEach(doc => (doc.presHideAfter = activeItem.presHideAfter)); }; @undoBatch @action updateOpenDoc = (activeItem: Doc) => { - activeItem.openDocument = !activeItem.openDocument; - Array.from(this._selectedArray.keys()).forEach(doc => { - doc.openDocument = activeItem.openDocument; - }); + activeItem.presOpenInLightbox = !activeItem.presOpenInLightbox; + this.selectedArray.forEach(doc => (doc.presOpenInLightbox = activeItem.presOpenInLightbox)); }; @undoBatch @action - updateEffectDirection = (effect: any, all?: boolean) => { - const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.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; - } - }); + updateEaseFunc = (activeItem: Doc) => { + activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear'; + this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc)); }; @undoBatch @action - updateEffect = (effect: any, all?: boolean) => { - const array: any[] = all ? this.childDocs : Array.from(this._selectedArray.keys()); - array.forEach(doc => { - const tagDoc = doc; //Cast(doc.presentationTargetDoc, Doc, null); - switch (effect) { - 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; - case PresEffect.None: - default: - tagDoc.presEffect = PresEffect.None; - break; - } + updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presEffectDirection = effect)); + + @undoBatch + @action + updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presEffect = effect))); + + static _sliderBatch: any; + public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => { + return ( + <input + type="range" + step={step} + min={min} + max={max} + value={value} + style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)` }} + className={`toolbar-slider ${active ? '' : 'none'}`} + onPointerDown={e => { + PresBox._sliderBatch = UndoManager.StartBatch('pres slider'); + e.stopPropagation(); + }} + onPointerUp={() => PresBox._sliderBatch.end()} + onChange={e => { + e.stopPropagation(); + change(e.target.value); + }} + /> + ); + }; + + @undoBatch + @action + applyTo = (array: Doc[]) => { + this.updateMovement(this.activeItem.presMovement as PresMovement, true); + this.updateEffect(this.activeItem.presEffect as PresEffect, false, true); + this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true); + this.updateEffectDirection(this.activeItem.presEffectDirection as PresEffectDirection, true); + const { presTransition, presDuration, presHideBefore, presHideAfter } = this.activeItem; + array.forEach(curDoc => { + curDoc.presTransition = presTransition; + curDoc.presDuration = presDuration; + curDoc.presHideBefore = presHideBefore; + curDoc.presHideAfter = presHideAfter; }); }; - _batch: UndoManager.Batch | undefined = undefined; + @computed get visibiltyDurationDropdown() { + const activeItem = this.activeItem; + if (activeItem && this.targetDoc) { + const targetType = this.targetDoc.type; + let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 0; + if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); + return ( + <div className="presBox-ribbon"> + <div className="ribbon-doubleButton"> + <Tooltip title={<div className="dash-tooltip">{'Hide before presented'}</div>}> + <div className={`ribbon-toggle ${activeItem.presHideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}> + Hide before + </div> + </Tooltip> + <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}> + <div className={`ribbon-toggle ${activeItem.presHide ? 'active' : ''}`} onClick={() => this.updateHide(activeItem)}> + Hide + </div> + </Tooltip> + + <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}> + <div className={`ribbon-toggle ${activeItem.presHideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}> + Hide after + </div> + </Tooltip> + + <Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presOpenInLightbox ? Colors.LIGHT_BLUE : '' }} onClick={() => this.updateOpenDoc(activeItem)}> + Lightbox + </div> + </Tooltip> + <Tooltip title={<div className="dash-tooltip">{'Transition movement style'}</div>}> + <div className="ribbon-toggle" onClick={() => this.updateEaseFunc(activeItem)}> + {`${StrCast(activeItem.presEaseFunc, 'ease')}`} + </div> + </Tooltip> + </div> + {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as any as DocumentType) ? null : ( + <> + <div className="ribbon-doubleButton"> + <div className="presBox-subheading">Slide Duration</div> + <div className="ribbon-property"> + <input className="presBox-input" type="number" value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s + </div> + <div className="ribbon-propertyUpDown"> + <div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), 1000)}> + <FontAwesomeIcon icon={'caret-up'} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={() => this.updateDurationTime(String(duration), -1000)}> + <FontAwesomeIcon icon={'caret-down'} /> + </div> + </div> + </div> + {PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)} + <div className={'slider-headers'} style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}> + <div className="slider-text">Short</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Long</div> + </div> + </> + )} + </div> + ); + } + } + @computed get progressivizeDropdown() { + const activeItem = this.activeItem; + if (activeItem && this.targetDoc) { + const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None; + const bulletEffect = (effect: PresEffect) => ( + <div className={`presBox-dropdownOption ${activeItem.presEffect === effect || (effect === PresEffect.None && !activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect, true)}> + {effect} + </div> + ); + return ( + <div className="presBox-ribbon"> + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Progressivize Collection</div> + <input + className="presBox-checkbox" + style={{ margin: 10 }} + type="checkbox" + onChange={() => { + activeItem.presIndexed = activeItem.presIndexed === undefined ? 0 : undefined; + activeItem.presHideBefore = activeItem.presIndexed !== undefined; + const tagDoc = PresBox.targetRenderedDoc(this.activeItem); + const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type; + activeItem.presIndexedStart = 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.presentationTargetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc.annotationOn["${dataField}"]`); + else activeItem.data = ComputedField.MakeFunction(`self.presentationTargetDoc["${dataField}"]`); + }} + checked={Cast(activeItem.presIndexed, 'number', null) !== undefined ? true : false} + /> + </div> + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Progressivize First Bullet</div> + <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presIndexedStart = activeItem.presIndexedStart ? 0 : 1)} checked={!NumCast(activeItem.presIndexedStart)} /> + </div> + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Expand Current Bullet</div> + <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)} checked={BoolCast(activeItem.presBulletExpand)} /> + </div> + <div className="ribbon-box"> + Bullet Effect + <div + className="presBox-dropdown" + onClick={action(e => { + e.stopPropagation(); + this._openBulletEffectDropdown = !this._openBulletEffectDropdown; + })} + style={{ borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, border: this._openBulletEffectDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + {effect?.toString()} + <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> + <div className={'presBox-dropdownOptions'} style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}> + {bulletEffect(PresEffect.None)} + {bulletEffect(PresEffect.Fade)} + {bulletEffect(PresEffect.Flip)} + {bulletEffect(PresEffect.Rotate)} + {bulletEffect(PresEffect.Bounce)} + {bulletEffect(PresEffect.Roll)} + </div> + </div> + </div> + </div> + ); + } + return null; + } @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 activeItem = this.activeItem; + const presEffect = (effect: PresEffect) => ( + <div className={`presBox-dropdownOption ${activeItem.presEffect === effect || (effect === PresEffect.None && !activeItem.presEffect) ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateEffect(effect, false)}> + {effect} + </div> + ); + const presMovement = (movement: PresMovement) => ( + <div className={`presBox-dropdownOption ${activeItem.presMovement === movement ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateMovement(movement)}> + {movement} + </div> + ); + const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { + const color = activeItem.presEffectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presEffectDirection) ? Colors.LIGHT_BLUE : 'black'; + return ( + <Tooltip title={<div className="dash-tooltip">{direction}</div>}> + <div + style={{ ...opts, border: direction === PresEffectDirection.Center ? `solid 2px ${color}` : undefined, borderRadius: '100%', cursor: 'pointer', gridColumn, gridRow, justifySelf: 'center', color }} + onClick={() => this.updateEffectDirection(direction)}> + {icon ? <FontAwesomeIcon icon={icon as any} /> : null} + </div> + </Tooltip> + ); + }; + if (activeItem && this.targetDoc) { const transitionSpeed = activeItem.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'; + const zoom = NumCast(activeItem.presZoom, 1) * 100; + const effect = activeItem.presEffect ? activeItem.presEffect : PresMovement.None; return ( <div - className={`presBox-ribbon ${this.transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`} - onPointerDown={e => e.stopPropagation()} - onPointerUp={e => e.stopPropagation()} + className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presStatus === PresStatus.Edit ? 'active' : ''}`} + onPointerDown={StopEvent} + onPointerUp={StopEvent} onClick={action(e => { e.stopPropagation(); - this.openMovementDropdown = false; - this.openEffectDropdown = false; + this._openMovementDropdown = false; + this._openEffectDropdown = false; + this._openBulletEffectDropdown = false; })}> <div className="ribbon-box"> Movement - {isPresCollection || (isPresCollection && isPinWithView) ? ( - <div className="ribbon-property" style={{ marginLeft: 0, height: 25, textAlign: 'left', paddingLeft: 5, paddingRight: 5, fontSize: 10 }}> - {this.scrollable ? 'Scroll to pinned view' : !isPinWithView ? 'No movement' : 'Pan & Zoom to pinned view'} - </div> - ) : ( - <div - className="presBox-dropdown" - onClick={action(e => { - 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)} - <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this.openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> - <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? 'grid' : 'none' }}> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}> - None - </div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}> - Pan {'&'} Zoom - </div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}> - Pan - </div> - <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}> - Jump cut - </div> - </div> + <div + className="presBox-dropdown" + onClick={action(e => { + e.stopPropagation(); + this._openMovementDropdown = !this._openMovementDropdown; + })} + style={{ borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, border: this._openMovementDropdown ? `solid 2px ${Colors.MEDIUM_BLUE}` : 'solid 1px black' }}> + {this.movementName(activeItem)} + <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openMovementDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={StopEvent} style={{ display: this._openMovementDropdown ? 'grid' : 'none' }}> + {presMovement(PresMovement.None)} + {presMovement(PresMovement.Center)} + {presMovement(PresMovement.Zoom)} + {presMovement(PresMovement.Pan)} + {presMovement(PresMovement.Jump)} </div> - )} + </div> <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}> <div className="presBox-subheading">Zoom (% screen filled)</div> <div className="ribbon-property"> - <input className="presBox-input" type="number" value={zoom} onChange={action(e => this.setZoom(e.target.value))} />% + <input className="presBox-input" type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />% </div> <div className="ribbon-propertyUpDown"> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), 0.1))}> + <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}> <FontAwesomeIcon icon={'caret-up'} /> </div> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setZoom(String(zoom), -0.1))}> + <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), -0.1)}> <FontAwesomeIcon icon={'caret-down'} /> </div> </div> </div> - <input - type="range" - step="1" - min="0" - max="150" - value={zoom} - className={`toolbar-slider ${activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`} - id="toolbar-slider" - onPointerDown={() => (this._batch = UndoManager.StartBatch('presZoom'))} - onPointerUp={() => this._batch?.end()} - onChange={(e: React.ChangeEvent<HTMLInputElement>) => { - e.stopPropagation(); - this.setZoom(e.target.value); - }} - /> - <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? 'inline-flex' : 'none' }}> - <div className="presBox-subheading">Movement Speed</div> + {PresBox.inputter('0', '1', '100', zoom, activeItem.presMovement === PresMovement.Zoom, this.updateZoom)} + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Transition Time</div> <div className="ribbon-property"> - <input className="presBox-input" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.setTransitionTime(e.target.value))} /> s + <input className="presBox-input" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s </div> <div className="ribbon-propertyUpDown"> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), 1000))}> + <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}> <FontAwesomeIcon icon={'caret-up'} /> </div> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), -1000))}> + <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), -1000)}> <FontAwesomeIcon icon={'caret-down'} /> </div> </div> </div> - <input - type="range" - step="0.1" - min="0.1" - max="10" - value={transitionSpeed} - className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`} - id="toolbar-slider" - onPointerDown={() => (this._batch = UndoManager.StartBatch('presTransition'))} - onPointerUp={() => this._batch?.end()} - onChange={(e: React.ChangeEvent<HTMLInputElement>) => { - e.stopPropagation(); - this.setTransitionTime(e.target.value); - }} - /> - <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}> + {PresBox.inputter('0.1', '0.1', '100', transitionSpeed, true, this.updateTransitionTime)} + <div className={'slider-headers'}> <div className="slider-text">Fast</div> <div className="slider-text">Medium</div> <div className="slider-text">Slow</div> </div> </div> <div className="ribbon-box"> - Visibility {'&'} Duration - <div className="ribbon-doubleButton"> - {isPresCollection ? null : ( - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Hide before presented'}</div> - </> - }> - <div className={`ribbon-toggle ${activeItem.presHideBefore ? 'active' : ''}`} onClick={() => this.updateHideBefore(activeItem)}> - Hide before + Effects + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Play Audio Annotation</div> + <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} checked={BoolCast(activeItem.presPlayAudio)} /> + </div> + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Zoom Text Selections</div> + <input className="presBox-checkbox" style={{ margin: 10 }} type="checkbox" onChange={() => (activeItem.presZoomText = !BoolCast(activeItem.presZoomText))} checked={BoolCast(activeItem.presZoomText)} /> + </div> + <div + className="presBox-dropdown" + onClick={action(e => { + 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()} + <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this._openEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}> + {presEffect(PresEffect.None)} + {presEffect(PresEffect.Fade)} + {presEffect(PresEffect.Flip)} + {presEffect(PresEffect.Rotate)} + {presEffect(PresEffect.Bounce)} + {presEffect(PresEffect.Roll)} + </div> + </div> + <div className="ribbon-doubleButton" style={{ display: effect === PresEffectDirection.None ? 'none' : 'inline-flex' }}> + <div className="presBox-subheading">Effect direction</div> + <div className="ribbon-property">{StrCast(this.activeItem.presEffectDirection)}</div> + </div> + <div className="effectDirection" style={{ display: effect === PresEffectDirection.None ? 'none' : 'grid', width: 40 }}> + {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' })} + </div> + </div> + <div className="ribbon-final-box"> + <div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.childDocs)}> + Apply to all + </div> + </div> + </div> + ); + } + } + @computed get mediaOptionsDropdown() { + const activeItem = this.activeItem; + if (activeItem && this.targetDoc) { + const clipStart = NumCast(activeItem.clipStart); + const clipEnd = NumCast(activeItem.clipEnd, NumCast(activeItem[Doc.LayoutFieldKey(activeItem) + '-duration'])); + return ( + <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div> + <div className="ribbon-box"> + Start {'&'} End Time + <div className={'slider-headers'}> + <div className="slider-block"> + <div className="slider-text" style={{ fontWeight: 500 }}> + Start time (s) </div> - </Tooltip> - )} - {isPresCollection ? null : ( - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Hide after presented'}</div> - </> - }> - <div className={`ribbon-toggle ${activeItem.presHideAfter ? 'active' : ''}`} onClick={() => this.updateHideAfter(activeItem)}> - Hide after + <div id={'startTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}> + <input + className="presBox-input" + style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} + type="number" + value={NumCast(activeItem.presStartTime).toFixed(2)} + onKeyDown={e => e.stopPropagation()} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { + activeItem.presStartTime = Number(e.target.value); + })} + /> </div> - </Tooltip> - )} - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Open in lightbox view'}</div> - </> - }> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? Colors.LIGHT_BLUE : '' }} onClick={() => this.updateOpenDoc(activeItem)}> - Lightbox </div> - </Tooltip> - </div> - {type === DocumentType.AUDIO || type === DocumentType.VID ? null : ( - <> - <div className="ribbon-doubleButton"> - <div className="presBox-subheading">Slide Duration</div> - <div className="ribbon-property"> - <input className="presBox-input" type="number" value={duration} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.setDurationTime(e.target.value))} /> s + <div className="slider-block"> + <div className="slider-text" style={{ fontWeight: 500 }}> + Duration (s) + </div> + <div className="slider-number" style={{ backgroundColor: Colors.LIGHT_BLUE }}> + {Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10} + </div> + </div> + <div className="slider-block"> + <div className="slider-text" style={{ fontWeight: 500 }}> + End time (s) </div> - <div className="ribbon-propertyUpDown"> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}> - <FontAwesomeIcon icon={'caret-up'} /> - </div> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}> - <FontAwesomeIcon icon={'caret-down'} /> - </div> + <div id={'endTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}> + <input + className="presBox-input" + onKeyDown={e => e.stopPropagation()} + style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} + type="number" + value={NumCast(activeItem.presEndTime).toFixed(2)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { + activeItem.presEndTime = Number(e.target.value); + })} + /> </div> </div> + </div> + <div className="multiThumb-slider"> <input type="range" step="0.1" - min="0.1" - max="20" - value={duration} - style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'block' }} - className={'toolbar-slider'} - id="duration-slider" - onPointerDown={() => { - this._batch = UndoManager.StartBatch('presDuration'); + min={clipStart} + max={clipEnd} + value={NumCast(activeItem.presEndTime)} + style={{ gridColumn: 1, gridRow: 1 }} + className={`toolbar-slider ${'end'}`} + id="toolbar-slider" + onPointerDown={e => { + this._batch = UndoManager.StartBatch('presEndTime'); + const endBlock = document.getElementById('endTime'); + if (endBlock) { + endBlock.style.color = Colors.LIGHT_GRAY; + endBlock.style.backgroundColor = Colors.MEDIUM_BLUE; + } + e.stopPropagation(); }} onPointerUp={() => { - if (this._batch) this._batch.end(); + 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<HTMLInputElement>) => { e.stopPropagation(); - this.setDurationTime(e.target.value); + activeItem.presEndTime = Number(e.target.value); }} /> - <div className={'slider-headers'} style={{ display: targetDoc.type === DocumentType.AUDIO ? 'none' : 'grid' }}> - <div className="slider-text">Short</div> - <div className="slider-text">Medium</div> - <div className="slider-text">Long</div> - </div> - </> - )} - </div> - {isPresCollection ? null : ( - <div className="ribbon-box"> - Effects - <div - className="presBox-dropdown" - onClick={action(e => { - 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()} - <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this.openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> - <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? 'grid' : 'none' }} onPointerDown={e => e.stopPropagation()}> - <div - className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.None || !this.activeItem.presEffect ? 'active' : ''}`} - onPointerDown={e => e.stopPropagation()} - onClick={() => this.updateEffect(PresEffect.None)}> - None - </div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Fade ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}> - Fade In - </div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Flip ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}> - Flip - </div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Rotate ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}> - Rotate - </div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Bounce ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}> - Bounce - </div> - <div className={`presBox-dropdownOption ${this.activeItem.presEffect === PresEffect.Roll ? 'active' : ''}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}> - Roll - </div> - </div> - </div> - <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? 'none' : 'inline-flex' }}> - <div className="presBox-subheading">Effect direction</div> - <div className="ribbon-property">{this.effectDirection}</div> - </div> - <div className="effectDirection" style={{ display: effect === 'None' ? 'none' : 'grid', width: 40 }}> - <Tooltip title={<div className="dash-tooltip">{'Enter from left'}</div>}> - <div - style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Left ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }} - onClick={() => this.updateEffectDirection(PresEffect.Left)}> - <FontAwesomeIcon icon={'angle-right'} /> - </div> - </Tooltip> - <Tooltip title={<div className="dash-tooltip">{'Enter from right'}</div>}> - <div - style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Right ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }} - onClick={() => this.updateEffectDirection(PresEffect.Right)}> - <FontAwesomeIcon icon={'angle-left'} /> - </div> - </Tooltip> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Enter from top'}</div> - </> - }> - <div - style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Top ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }} - onClick={() => this.updateEffectDirection(PresEffect.Top)}> - <FontAwesomeIcon icon={'angle-down'} /> - </div> - </Tooltip> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Enter from bottom'}</div> - </> - }> - <div - style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: this.activeItem.presEffectDirection === PresEffect.Bottom ? Colors.LIGHT_BLUE : 'black', cursor: 'pointer' }} - onClick={() => this.updateEffectDirection(PresEffect.Bottom)}> - <FontAwesomeIcon icon={'angle-up'} /> - </div> - </Tooltip> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Enter from center'}</div> - </> - }> - <div - style={{ - gridColumn: 2, - gridRow: 2, - width: 10, - height: 10, - alignSelf: 'center', - justifySelf: 'center', - border: this.activeItem.presEffectDirection === PresEffect.Center || !this.activeItem.presEffectDirection ? `solid 2px ${Colors.LIGHT_BLUE}` : 'solid 2px black', - borderRadius: '100%', - cursor: 'pointer', - }} - onClick={() => this.updateEffectDirection(PresEffect.Center)}></div> - </Tooltip> - </div> - </div> - )} - <div className="ribbon-final-box"> - <div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.childDocs)}> - Apply to all - </div> - </div> - </div> - ); - } - } - - @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 = <img src="/assets/pinWithView.png" style={{ margin: 'auto', width: 16, filter: 'invert(1)' }} />; - return ( - <> - {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : null} - <div className="ribbon-doubleButton"> - <Tooltip - title={ - <> - <div className="dash-tooltip">{activeItem.presPinView ? 'Turn off pin with view' : 'Turn on pin with view'}</div> - </> - }> - <div - className="ribbon-toggle" - style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? Colors.LIGHT_BLUE : '' }} - onClick={() => { - 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} - </div> - </Tooltip> - {activeItem.presPinView ? ( - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Update the pinned view with the view of the selected document'}</div> - </> - }> - <div - className="ribbon-button" - onClick={() => { - 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 - </div> - </Tooltip> - ) : null} - </div> - </> - ); - } - - @computed get panOptionsDropdown() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - return ( - <> - {this.panable ? ( - <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Pan X</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input - className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" - value={NumCast(activeItem.presPinViewX)} - onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { - const val = e.target.value; - activeItem.presPinViewX = Number(val); - })} - /> - </div> - </div> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Pan Y</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input - className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" - value={NumCast(activeItem.presPinViewY)} - onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { - const val = e.target.value; - activeItem.presPinViewY = Number(val); - })} - /> - </div> - </div> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Scale</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> <input - className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" - value={NumCast(activeItem.presPinViewScale)} - onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { - const val = e.target.value; - activeItem.presPinViewScale = Number(val); - })} + type="range" + step="0.1" + min={clipStart} + max={clipEnd} + value={NumCast(activeItem.presStartTime)} + style={{ gridColumn: 1, gridRow: 1 }} + className={`toolbar-slider ${'start'}`} + id="toolbar-slider" + onPointerDown={e => { + this._batch = UndoManager.StartBatch('presStartTime'); + const startBlock = document.getElementById('startTime'); + if (startBlock) { + startBlock.style.color = Colors.LIGHT_GRAY; + startBlock.style.backgroundColor = Colors.MEDIUM_BLUE; + } + e.stopPropagation(); + }} + 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<HTMLInputElement>) => { + e.stopPropagation(); + activeItem.presStartTime = Number(e.target.value); + }} /> </div> - </div> - </div> - ) : null} - </> - ); - } - - @computed get scrollOptionsDropdown() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - return ( - <> - {this.scrollable ? ( - <div style={{ display: activeItem.presPinView ? 'block' : 'none' }}> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Scroll</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input - className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" - value={NumCast(activeItem.presPinViewScroll)} - onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { - const val = e.target.value; - activeItem.presPinViewScroll = Number(val); - })} - /> + <div className="slider-headers"> + <div className="slider-text">{clipStart.toFixed(2)} s</div> + <div className="slider-text"></div> + <div className="slider-text">{clipEnd.toFixed(2)} s</div> </div> </div> - </div> - ) : null} - </> - ); - } - - @computed get mediaStopSlides() { - const activeItem: Doc = this.activeItem; - const list = this.childDocs.map((doc, i) => { - if (i > this.itemIndex) { - return ( - <option> - {i + 1}. {StrCast(doc.title)} - </option> - ); - } - }); - 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 ( - <div> - <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> - <div> - <div className="ribbon-box"> - Start {'&'} End Time - <div className={'slider-headers'}> - <div className="slider-block"> - <div className="slider-text" style={{ fontWeight: 500 }}> - Start time (s) - </div> - <div id={'startTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}> - <input - className="presBox-input" - style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }} - type="number" - value={NumCast(activeItem.presStartTime)} - onKeyDown={e => e.stopPropagation()} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { - activeItem.presStartTime = Number(e.target.value); - })} - /> - </div> - </div> - <div className="slider-block"> - <div className="slider-text" style={{ fontWeight: 500 }}> - Duration (s) - </div> - <div className="slider-number" style={{ backgroundColor: Colors.LIGHT_BLUE }}> - {Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10} - </div> - </div> - <div className="slider-block"> - <div className="slider-text" style={{ fontWeight: 500 }}> - End time (s) - </div> - <div id={'endTime'} className="slider-number" style={{ backgroundColor: Colors.LIGHT_GRAY }}> - <input - className="presBox-input" - onKeyDown={e => e.stopPropagation()} - style={{ textAlign: 'center', width: 30, height: 15, fontSize: 10 }} - type="number" - value={NumCast(activeItem.presEndTime)} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { - activeItem.presEndTime = Number(e.target.value); - })} - /> - </div> - </div> - </div> - <div className="multiThumb-slider"> - <input - type="range" - step="0.1" - min={clipStart} - max={clipEnd} - value={NumCast(activeItem.presEndTime)} - style={{ gridColumn: 1, gridRow: 1 }} - className={`toolbar-slider ${'end'}`} - id="toolbar-slider" - onPointerDown={() => { - 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<HTMLInputElement>) => { - e.stopPropagation(); - activeItem.presEndTime = Number(e.target.value); - }} - /> - <input - type="range" - step="0.1" - min={clipStart} - max={clipEnd} - value={NumCast(activeItem.presStartTime)} - style={{ gridColumn: 1, gridRow: 1 }} - className={`toolbar-slider ${'start'}`} - id="toolbar-slider" - onPointerDown={() => { - 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<HTMLInputElement>) => { - e.stopPropagation(); - activeItem.presStartTime = Number(e.target.value); - }} - /> + <div className="ribbon-final-box"> + Playback + <div className="presBox-subheading">Start playing:</div> + <div className="presBox-radioButtons"> + <div className="checkbox-container"> + <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} /> + <div>On click</div> </div> - <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? '' : 'none'}`}> - <div className="slider-text">{clipStart} s</div> - <div className="slider-text"></div> - <div className="slider-text">{clipEnd} s</div> + <div className="checkbox-container"> + <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} /> + <div>Automatically</div> </div> </div> - <div className="ribbon-final-box"> - Playback - <div className="presBox-subheading">Start playing:</div> - <div className="presBox-radioButtons"> - <div className="checkbox-container"> - <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'manual')} checked={activeItem.mediaStart === 'manual'} /> - <div>On click</div> - </div> - <div className="checkbox-container"> - <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStart = 'auto')} checked={activeItem.mediaStart === 'auto'} /> - <div>Automatically</div> - </div> + <div className="presBox-subheading">Stop playing:</div> + <div className="presBox-radioButtons"> + <div className="checkbox-container"> + <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} /> + <div>At audio end time</div> </div> - <div className="presBox-subheading">Stop playing:</div> - <div className="presBox-radioButtons"> - <div className="checkbox-container"> - <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'manual')} checked={activeItem.mediaStop === 'manual'} /> - <div>At audio end time</div> - </div> - <div className="checkbox-container"> - <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} /> - <div>On slide change</div> - </div> - {/* <div className="checkbox-container"> + <div className="checkbox-container"> + <input className="presBox-checkbox" type="checkbox" onChange={() => (activeItem.mediaStop = 'auto')} checked={activeItem.mediaStop === 'auto'} /> + <div>On slide change</div> + </div> + {/* <div className="checkbox-container"> <input className="presBox-checkbox" type="checkbox" onChange={() => activeItem.mediaStop = "afterSlide"} @@ -1947,7 +1903,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </select> </div> </div> */} - </div> </div> </div> </div> @@ -1955,58 +1910,55 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { ); } } - @computed get newDocumentToolbarDropdown() { return ( - <div> - <div - className={'presBox-toolbar-dropdown'} - style={{ display: this.newDocumentTools && this.layoutDoc.presStatus === 'edit' ? 'inline-flex' : 'none' }} - onClick={e => e.stopPropagation()} - onPointerUp={e => e.stopPropagation()} - onPointerDown={e => e.stopPropagation()}> - <div className="layout-container" style={{ height: 'max-content' }}> - <div - className="layout" - style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} - onClick={action(() => { - this.layout = 'blank'; - this.createNewSlide(this.layout); - })} - /> - <div - className="layout" - style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} - onClick={action(() => { - this.layout = 'title'; - this.createNewSlide(this.layout); - })}> - <div className="title">Title</div> - <div className="subtitle">Subtitle</div> - </div> - <div - className="layout" - style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} - onClick={action(() => { - this.layout = 'header'; - this.createNewSlide(this.layout); - })}> - <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}> - Section header - </div> + <div + className="presBox-toolbar-dropdown" + style={{ display: this._newDocumentTools && this.layoutDoc.presStatus === 'edit' ? 'inline-flex' : 'none' }} + onClick={e => e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} + onPointerDown={e => e.stopPropagation()}> + <div className="layout-container" style={{ height: 'max-content' }}> + <div + className="layout" + style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'blank'; + this.createNewSlide(this.layout); + })} + /> + <div + className="layout" + style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'title'; + this.createNewSlide(this.layout); + })}> + <div className="title">Title</div> + <div className="subtitle">Subtitle</div> + </div> + <div + className="layout" + style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'header'; + this.createNewSlide(this.layout); + })}> + <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}> + Section header </div> - <div - className="layout" - style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} - onClick={action(() => { - this.layout = 'content'; - this.createNewSlide(this.layout); - })}> - <div className="title" style={{ alignSelf: 'center' }}> - Title - </div> - <div className="content">Text goes here</div> + </div> + <div + className="layout" + style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'content'; + this.createNewSlide(this.layout); + })}> + <div className="title" style={{ alignSelf: 'center' }}> + Title </div> + <div className="content">Text goes here</div> </div> </div> </div> @@ -2020,73 +1972,71 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get newDocumentDropdown() { return ( - <div> - <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> - <div className="ribbon-box"> - Slide Title: <br></br> - <input - className="ribbon-textInput" - placeholder="..." - type="text" - name="fname" - onChange={e => { - e.stopPropagation(); - e.preventDefault(); - runInAction(() => (this.title = e.target.value)); - }}></input> + <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="ribbon-box"> + Slide Title: <br></br> + <input + className="ribbon-textInput" + placeholder="..." + type="text" + name="fname" + onChange={e => { + e.stopPropagation(); + e.preventDefault(); + runInAction(() => (this.title = e.target.value)); + }}></input> + </div> + <div className="ribbon-box"> + Choose type: + <div className="ribbon-doubleButton"> + <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}> + Text + </div> + <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}> + Freeform + </div> </div> - <div className="ribbon-box"> - Choose type: - <div className="ribbon-doubleButton"> - <div title="Text" className={'ribbon-toggle'} style={{ background: this.addFreeform ? '' : Colors.LIGHT_BLUE }} onClick={action(() => (this.addFreeform = !this.addFreeform))}> - Text - </div> - <div title="Freeform" className={'ribbon-toggle'} style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} onClick={action(() => (this.addFreeform = !this.addFreeform))}> - Freeform + </div> + <div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}> + Preset layouts: + <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}> + <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} /> + <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}> + <div className="title">Title</div> + <div className="subtitle">Subtitle</div> + </div> + <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}> + <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}> + Section header </div> </div> - </div> - <div className="ribbon-box" style={{ display: this.addFreeform ? 'grid' : 'none' }}> - Preset layouts: - <div className="layout-container" style={{ height: this.openLayouts ? 'max-content' : '75px' }}> - <div className="layout" style={{ border: this.layout === 'blank' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'blank'))} /> - <div className="layout" style={{ border: this.layout === 'title' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'title'))}> - <div className="title">Title</div> - <div className="subtitle">Subtitle</div> + <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}> + <div className="title" style={{ alignSelf: 'center' }}> + Title </div> - <div className="layout" style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'header'))}> - <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}> - Section header - </div> + <div className="content">Text goes here</div> + </div> + <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}> + <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}> + Title </div> - <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}> - <div className="title" style={{ alignSelf: 'center' }}> - Title - </div> - <div className="content">Text goes here</div> + <div className="content" style={{ gridColumn: 1, gridRow: 2 }}> + Column one text </div> - <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}> - <div className="title" style={{ alignSelf: 'center', gridColumn: '1/3' }}> - Title - </div> - <div className="content" style={{ gridColumn: 1, gridRow: 2 }}> - Column one text - </div> - <div className="content" style={{ gridColumn: 2, gridRow: 2 }}> - Column two text - </div> + <div className="content" style={{ gridColumn: 2, gridRow: 2 }}> + Column two text </div> </div> - <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}> - <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} /> - </div> </div> - <div className="ribbon-final-box"> - <div - className={this.title !== '' && ((this.addFreeform && this.layout !== '') || !this.addFreeform) ? 'ribbon-final-button-hidden' : 'ribbon-final-button'} - onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}> - Create New Slide - </div> + <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}> + <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} /> + </div> + </div> + <div className="ribbon-final-box"> + <div + className={this.title !== '' && ((this.addFreeform && this.layout !== '') || !this.addFreeform) ? 'ribbon-final-button-hidden' : 'ribbon-final-button'} + onClick={() => this.createNewSlide(this.layout, this.title, this.addFreeform)}> + Create New Slide </div> </div> </div> @@ -2099,67 +2049,50 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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 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().presentationTargetDoc ?? tab; const data = Cast(presCollection?.data, listSpec(Doc)); const presData = Cast(this.rootDoc.data, listSpec(Doc)); if (data && presData) { data.push(doc); - TabDocView.PinDoc(doc); + TabDocView.PinDoc(doc, {}); this.gotoDocument(this.childDocs.length, this.activeItem); } else { - this.props.addDocTab(doc, 'add:right'); + this.props.addDocTab(doc, OpenWhere.addRight); } } }; 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' }); + 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, _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' }); + // prettier-ignore 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; + 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, _fitContentsToBox: true, x, y }); + case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _fitContentsToBox: true, x, y }); + case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _fitContentsToBox: true, x, y }); + case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _fitContentsToBox: true, x, y }) } - return doc; }; // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) @computed get presentDropdown() { return ( - <div className={`dropdown-play ${this.presentTools ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className={`dropdown-play ${this._presentTools ? 'active' : ''}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="dropdown-play-button" onClick={undoBatch( action(() => { - this.updateMinimize(); + this.enterMinimize(); this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }) @@ -2171,6 +2104,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { onClick={undoBatch( action(() => { this.layoutDoc.presStatus = 'manual'; + this.initializePresState(this.itemIndex); this.turnOffEdit(true); this.gotoDocument(this.itemIndex, this.activeItem); }) @@ -2181,591 +2115,48 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { ); } - // 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 ( - <div> - <div - className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === 'edit' ? 'active' : ''}`} - onClick={e => e.stopPropagation()} - onPointerUp={e => e.stopPropagation()} - onPointerDown={e => e.stopPropagation()}> - <div className="ribbon-box"> - {this.stringType} selected - <div - className="ribbon-doubleButton" - style={{ - borderTop: 'solid 1px darkgrey', - display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none', - }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeChild}> - Contents - </div> - <div className="ribbon-toggle" style={{ opacity: activeItem.presProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editProgressivize}> - Edit - </div> - </div> - <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}> - <div className="presBox-subheading">Active text color</div> - <div - className="ribbon-colorBox" - style={{ backgroundColor: activeFontColor, height: 15, width: 15 }} - onClick={action(() => { - this.openActiveColorPicker = !this.openActiveColorPicker; - })}></div> - </div> - {this.activeColorPicker} - <div className="ribbon-doubleButton" style={{ display: activeItem.presProgressivize ? 'inline-flex' : 'none' }}> - <div className="presBox-subheading">Viewed font color</div> - <div className="ribbon-colorBox" style={{ backgroundColor: viewedFontColor, height: 15, width: 15 }} onClick={action(() => (this.openViewedColorPicker = !this.openViewedColorPicker))}></div> - </div> - {this.viewedColorPicker} - <div - className="ribbon-doubleButton" - style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG ? 'inline-flex' : 'none' }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.zoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeZoom}> - Zoom - </div> - <div className="ribbon-toggle" style={{ opacity: activeItem.zoomProgressivize ? 1 : 0.4, backgroundColor: activeItem.editZoomProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editZoomProgressivize}> - Edit - </div> - </div> - <div - className="ribbon-doubleButton" - style={{ - borderTop: 'solid 1px darkgrey', - display: targetDoc._viewType === 'stacking' || targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF ? 'inline-flex' : 'none', - }}> - <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.progressivizeScroll}> - Scroll - </div> - <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? Colors.LIGHT_BLUE : '' }} onClick={this.editScrollProgressivize}> - Edit - </div> - </div> - </div> - <div className="ribbon-final-box"> - Frames - <div className="ribbon-doubleButton"> - <div className="ribbon-frameSelector"> - <div - key="back" - title="back frame" - className="backKeyframe" - onClick={e => { - e.stopPropagation(); - this.prevKeyframe(targetDoc, activeItem); - }}> - <FontAwesomeIcon icon={'caret-left'} size={'lg'} /> - </div> - <div - key="num" - title="toggle view all" - className="numKeyframe" - style={{ color: targetDoc.keyFrameEditing ? 'white' : 'black', backgroundColor: targetDoc.keyFrameEditing ? Colors.MEDIUM_BLUE : Colors.LIGHT_BLUE }} - onClick={action(() => (targetDoc.keyFrameEditing = !targetDoc.keyFrameEditing))}> - {NumCast(targetDoc._currentFrame)} - </div> - <div - key="fwd" - title="forward frame" - className="fwdKeyframe" - onClick={e => { - e.stopPropagation(); - this.nextKeyframe(targetDoc, activeItem); - }}> - <FontAwesomeIcon icon={'caret-right'} size={'lg'} /> - </div> - </div> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Last frame'}</div> - </> - }> - <div className="ribbon-property">{NumCast(targetDoc.lastFrame)}</div> - </Tooltip> - </div> - <div className="ribbon-frameList"> - {this.frameListHeader} - {this.frameList} - </div> - <div className="ribbon-toggle" style={{ height: 20, backgroundColor: Colors.LIGHT_BLUE }} onClick={() => console.log(' TODO: play frames')}> - Play - </div> - </div> - </div> - </div> - ); - } - } - - @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 : ( - <SketchPicker - onChange={this.switchActive} - presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} - color={StrCast(targetDoc['pres-text-color'])} - /> - ); - } - - @computed get viewedColorPicker() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - return !this.openViewedColorPicker ? null : ( - <SketchPicker - onChange={this.switchPresented} - presetColors={['#D0021B', '#F5A623', '#F8E71C', '#8B572A', '#7ED321', '#417505', '#9013FE', '#4A90E2', '#50E3C2', '#B8E986', '#000000', '#4A4A4A', '#9B9B9B', '#FFFFFF', '#f1efeb', 'transparent']} - color={StrCast(targetDoc['pres-text-viewed-color'])} - /> - ); - } - - @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<number> = xlist; - const y: List<number> = 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( - <div className="progressivizeMove-frame" style={{ position: 'absolute', top: y[i], left: x[i] }}> - {i} - </div> - ); - } - } - tags.push( - <svg style={{ overflow: 'visible', position: 'absolute' }}> - <polyline - points={pathPoints} - style={{ - position: 'absolute', - opacity: 1, - stroke: '#000000', - strokeWidth: 2, - strokeDasharray: '10 5', - }} - fill="none" - /> - </svg> - ); - 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<number> = 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(<div style={{ position: 'absolute', display: doc.displayMovement ? 'block' : 'none' }}>{this.checkMovementLists(doc, doc['x-indexed'], doc['y-indexed'])}</div>); - } - tags.push( - <div - className="progressivizeButton" - key={index} - onPointerLeave={() => { - 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) }}> - <div className="progressivizeButton-prev"> - <FontAwesomeIcon - icon={'caret-left'} - size={'lg'} - onClick={e => { - e.stopPropagation(); - this.prevAppearFrame(doc, index); - }} - /> - </div> - <div className="progressivizeButton-frame">{NumCast(doc.appearFrame)}</div> - <div className="progressivizeButton-next"> - <FontAwesomeIcon - icon={'caret-right'} - size={'lg'} - onClick={e => { - e.stopPropagation(); - this.nextAppearFrame(doc, index); - }} - /> - </div> - </div> - ); - }); - 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<number> = 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 <div></div>; - } + turnOffEdit = (paths?: boolean) => paths && this.togglePath(true); // Turn off paths @computed get toolbarWidth(): number { - const width = this.props.PanelWidth(); - return width; + return this.props.PanelWidth(); } @action - toggleProperties = () => { - if (SettingsManager.propertiesWidth > 0) { - SettingsManager.propertiesWidth = 0; - } else { - SettingsManager.propertiesWidth = 250; - } - }; + 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._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; - const presKeyEvents: boolean = this.isPres && this._presKeyEventsActive && this.rootDoc === Doc.ActivePresentation; + const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); const activeColor = Colors.LIGHT_BLUE; const inactiveColor = Colors.WHITE; - return mode === CollectionViewType.Carousel3D ? null : ( + return mode === CollectionViewType.Carousel3D || inOverlay ? null : ( <div id="toolbarContainer" className={'presBox-toolbar'}> {/* <Tooltip title={<><div className="dash-tooltip">{"Add new slide"}</div></>}><div className={`toolbar-button ${this.newDocumentTools ? "active" : ""}`} onClick={action(() => this.newDocumentTools = !this.newDocumentTools)}> <FontAwesomeIcon icon={"plus"} /> <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} /> </div></Tooltip> */} - <Tooltip - title={ - <> - <div className="dash-tooltip">{'View paths'}</div> - </> - }> + <Tooltip title={<div className="dash-tooltip">View paths</div>}> <div - style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }} + style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }} className={'toolbar-button'} - onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}> + onClick={this.childDocs.length > 1 ? () => this.togglePath() : undefined}> <FontAwesomeIcon icon={'exchange-alt'} /> </div> </Tooltip> {isMini ? null : ( <> <div className="toolbar-divider" /> - {/* <Tooltip title={<><div className="dash-tooltip">{this._expandBoolean ? "Minimize all" : "Expand all"}</div></>}> - <div className={"toolbar-button"} - style={{ color: this._expandBoolean ? Colors.MEDIUM_BLUE : 'white' }} - onClick={this.toggleExpandMode}> - <FontAwesomeIcon icon={"eye"} /> + <Tooltip title={<div className="dash-tooltip">{this._presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}> + <div className="toolbar-button" style={{ cursor: this._presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}> + <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} /> </div> </Tooltip> - <div className="toolbar-divider" /> */} - <Tooltip - title={ - <> - <div className="dash-tooltip">{presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div> - </> - }> - <div className="toolbar-button" style={{ cursor: presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}> - <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: presKeyEvents ? activeColor : inactiveColor }} /> - </div> - </Tooltip> - <Tooltip - title={ - <> - <div className="dash-tooltip">{propTitle}</div> - </> - }> + <Tooltip title={<div className="dash-tooltip">{propTitle}</div>}> <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}> <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.propertiesWidth > 0 ? activeColor : inactiveColor }} /> </div> @@ -2784,30 +2175,32 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get topPanel() { const mode = StrCast(this.rootDoc._viewType) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; + const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); return ( - <div className="presBox-buttons" style={{ display: !this.rootDoc._chromeHidden ? 'none' : undefined }}> + <div className={`presBox-buttons${inOverlay ? ' inOverlay' : ''}`} style={{ background: Doc.ActivePresentation === this.rootDoc ? Colors.LIGHT_BLUE : undefined, display: !this.rootDoc._chromeHidden ? 'none' : undefined }}> {isMini ? null : ( <select className="presBox-viewPicker" style={{ display: this.layoutDoc.presStatus === 'edit' ? 'block' : 'none' }} onPointerDown={e => e.stopPropagation()} onChange={this.viewChanged} value={mode}> - <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Stacking}> + <option onPointerDown={StopEvent} value={CollectionViewType.Stacking}> List </option> - <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Tree}> + <option onPointerDown={StopEvent} value={CollectionViewType.Tree}> Tree </option> {Doc.noviceMode ? null : ( - <option onPointerDown={e => e.stopPropagation()} value={CollectionViewType.Carousel3D}> + <option onPointerDown={StopEvent} value={CollectionViewType.Carousel3D}> 3D Carousel </option> )} </select> )} <div className="presBox-presentPanel" style={{ opacity: this.childDocs.length ? 1 : 0.3 }}> - <span className={`presBox-button ${this.layoutDoc.presStatus === 'edit' ? 'present' : ''}`}> + <span className={`presBox-button ${this.layoutDoc.presStatus === PresStatus.Edit ? 'present' : ''}`}> <div className="presBox-button-left" onClick={undoBatch(() => { if (this.childDocs.length) { this.layoutDoc.presStatus = 'manual'; + this.initializePresState(this.itemIndex); this.gotoDocument(this.itemIndex, this.activeItem); } })}> @@ -2816,11 +2209,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> {mode === CollectionViewType.Carousel3D || isMini ? null : ( <div - className={`presBox-button-right ${this.presentTools ? 'active' : ''}`} + className={`presBox-button-right ${this._presentTools ? 'active' : ''}`} onClick={action(() => { - if (this.childDocs.length) this.presentTools = !this.presentTools; + if (this.childDocs.length) this._presentTools = !this._presentTools; })}> - <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this.presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} /> + <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} /> {this.presentDropdown} </div> )} @@ -2831,168 +2224,97 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { ); } - @action - getList = (list: any): List<number> => { - const x: List<number> = list; - return x; - }; - - @action - updateList = (list: any): List<number> => { - const targetDoc: Doc = this.targetDoc; - const x: List<number> = 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<number>(); - switch (type) { - case DocumentType.PDF || DocumentType.RTF || DocumentType.WEB: - this.updateList(activeItem.frameList); - break; - case DocumentType.COL: - break; - default: - break; - } - }; - - @computed get frameListHeader() { - return ( - <div className="frameList-header"> - Frames {this.panable ? <i>Panable</i> : this.scrollable ? <i>Scrollable</i> : null} - <div className={'frameList-headerButtons'}> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Add frame by example'}</div> - </> - }> - <div - className={'headerButton'} - onClick={e => { - e.stopPropagation(); - this.newFrame(); - }}> - <FontAwesomeIcon icon={'plus'} onPointerDown={e => e.stopPropagation()} /> - </div> - </Tooltip> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Edit in collection'}</div> - </> - }> - <div - className={'headerButton'} - onClick={e => { - e.stopPropagation(); - console.log('New frame'); - }}> - <FontAwesomeIcon icon={'edit'} onPointerDown={e => e.stopPropagation()} /> - </div> - </Tooltip> - </div> - </div> - ); - } - - @computed get frameList() { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; - const frameList: List<number> = this.getList(activeItem.frameList); - if (frameList) { - const frameItems = frameList.map(value => <div className="framList-item"></div>); - return <div className="frameList-container">{frameItems}</div>; - } else return null; - } - - @computed get playButtonFrames() { - const targetDoc: Doc = this.targetDoc; - return ( - <> - {this.targetDoc ? ( - <div className="presPanel-button-frame" style={{ display: targetDoc.lastFrame !== undefined && targetDoc.lastFrame >= 0 ? 'inline-flex' : 'none' }}> - <div>{NumCast(targetDoc._currentFrame)}</div> - <div className="presPanel-divider" style={{ border: 'solid 0.5px white', height: '60%' }}></div> - <div>{NumCast(targetDoc.lastFrame)}</div> - </div> - ) : null} - </> - ); - } - @computed get playButtons() { - const presEnd: boolean = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1; + const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0)); const presStart: boolean = !this.layoutDoc.presLoop && this.itemIndex === 0; + const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); // Case 1: There are still other frames and should go through all frames before going to next slide return ( <div className="presPanelOverlay" style={{ display: this.layoutDoc.presStatus !== 'edit' ? 'inline-flex' : 'none' }}> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Loop'}</div> - </> - }> - <div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} onClick={() => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop)}> + <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}> + <div + className="presPanel-button" + style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : 'white' }} + onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}> <FontAwesomeIcon icon={'redo-alt'} /> </div> </Tooltip> - <div className="presPanel-divider"></div> + <div className="presPanel-divider" /> <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} - onClick={() => { - this.back(); - if (this._presTimer) { - clearTimeout(this._presTimer); - this.layoutDoc.presStatus = PresStatus.Manual; - } - }}> + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + () => { + this.back(); + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } + e.stopPropagation(); + }, + false, + false + ) + }> <FontAwesomeIcon icon={'arrow-left'} /> </div> - <Tooltip - title={ - <> - <div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div> - </> - }> - <div className="presPanel-button" onClick={this.startOrPause}> - <FontAwesomeIcon icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? 'pause' : 'play'} /> + <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}> + <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.startOrPause(true), false, false)}> + <FontAwesomeIcon color={this.layoutDoc.presStatus === PresStatus.Autoplay ? 'red' : undefined} icon={this.layoutDoc.presStatus === PresStatus.Autoplay ? 'pause' : 'play'} /> </div> </Tooltip> <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} - onClick={() => { - this.next(); - if (this._presTimer) { - clearTimeout(this._presTimer); - this.layoutDoc.presStatus = PresStatus.Manual; - } - }}> + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + () => { + this.next(); + if (this._presTimer) { + clearTimeout(this._presTimer); + this.layoutDoc.presStatus = PresStatus.Manual; + } + e.stopPropagation(); + }, + false, + false + ) + }> <FontAwesomeIcon icon={'arrow-right'} /> </div> <div className="presPanel-divider"></div> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Click to return to 1st slide'}</div> - </> - }> - <div className="presPanel-button" style={{ border: 'solid 1px white' }} onClick={() => this.gotoDocument(0, this.activeItem)}> + <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}> + <div + className="presPanel-button" + style={{ border: 'solid 1px white' }} + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + emptyFunction, + () => { + this.nextSlide(0); + }, + false, + false + ) + }> <b>1</b> </div> </Tooltip> - <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}> - Slide {this.itemIndex + 1} / {this.childDocs.length} - {this.playButtonFrames} + <div className="presPanel-button-text" onClick={() => this.gotoDocument(0, this.activeItem)} style={{ display: inOverlay || this.props.PanelWidth() > 250 ? 'inline-flex' : 'none' }}> + {inOverlay ? '' : 'Slide'} {this.itemIndex + 1} + {this.activeItem?.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length} </div> <div className="presPanel-divider"></div> {this.props.PanelWidth() > 250 ? ( @@ -3000,14 +2322,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { className="presPanel-button-text" onClick={undoBatch( action(() => { - this.layoutDoc.presStatus = 'edit'; + this.layoutDoc.presStatus = PresStatus.Edit; clearTimeout(this._presTimer); }) )}> EXIT </div> ) : ( - <div className="presPanel-button" onClick={undoBatch(action(() => (this.layoutDoc.presStatus = 'edit')))}> + <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.exitClicked, false, false)}> <FontAwesomeIcon icon={'times'} /> </div> )} @@ -3016,8 +2338,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } @action - startOrPause = () => { - if (this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startAutoPres(this.itemIndex); + startOrPause = (makeActive = true) => { + makeActive && this.updateCurrentPresentation(); + if (!this.layoutDoc.presStatus || this.layoutDoc.presStatus === PresStatus.Manual || this.layoutDoc.presStatus === PresStatus.Edit) this.startPresentation(this.itemIndex); else this.pauseAutoPres(); }; @@ -3038,20 +2361,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.layoutDoc.presStatus = PresStatus.Manual; } }; + @undoBatch @action - exitClicked = (e: PointerEvent) => { - this.updateMinimize(); - this.layoutDoc.presStatus = PresStatus.Edit; + exitClicked = () => { + this.layoutDoc.presStatus = this._exitTrail?.() ?? this.exitMinimize(); clearTimeout(this._presTimer); }; - @action - startMarqueeCreateSlide = () => { - PresBox.startMarquee = true; - }; - - AddToMap = (treeViewDoc: Doc, index: number[]): Doc[] => { + AddToMap = (treeViewDoc: Doc, index: number[]) => { + if (!treeViewDoc.presentationTargetDoc) 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; @@ -3064,38 +2383,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs } } - return this.childDocs; }; - RemFromMap = (treeViewDoc: Doc, index: number[]): Doc[] => { + RemFromMap = (treeViewDoc: Doc, index: number[]) => { + if (!treeViewDoc.presentationTargetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element. if (!this._unmounting && this.isTree) { this._treeViewMap.delete(treeViewDoc); this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap)); } - 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<Doc, number>) => [...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) ? ( - <div className="miniPres" onClick={e => e.stopPropagation()}> - <div className="presPanelOverlay" style={{ display: 'inline-flex', height: 30, background: '#323232', top: 0, zIndex: 3000000, boxShadow: presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Loop'}</div> - </> - }> + const presEnd = !this.layoutDoc.presLoop && this.itemIndex === this.childDocs.length - 1 && (this.activeItem.presIndexed === undefined || NumCast(this.activeItem.presIndexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0)); + const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0; + const inOverlay = DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc); + return this.props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player + <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}> + <div + className="presPanelOverlay" + style={{ display: 'inline-flex', height: 30, background: Doc.ActivePresentation === this.rootDoc ? 'green' : '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}> + <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}> <div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }} @@ -3107,13 +2419,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="presPanel-button" style={{ opacity: presStart ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.prevClicked, false, false)}> <FontAwesomeIcon icon={'arrow-left'} /> </div> - <Tooltip - title={ - <> - <div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div> - </> - }> - <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.startOrPause, false, false)}> + <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presStatus === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}> + <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.startOrPause(true), false, false)}> <FontAwesomeIcon icon={this.layoutDoc.presStatus === 'auto' ? 'pause' : 'play'} /> </div> </Tooltip> @@ -3121,19 +2428,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <FontAwesomeIcon icon={'arrow-right'} /> </div> <div className="presPanel-divider"></div> - <Tooltip - title={ - <> - <div className="dash-tooltip">{'Click to return to 1st slide'}</div> - </> - }> + <Tooltip title={<div className="dash-tooltip">{'Click to return to 1st slide'}</div>}> <div className="presPanel-button" style={{ border: 'solid 1px white' }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.gotoDocument(0, this.activeItem), false, false)}> <b>1</b> </div> </Tooltip> <div className="presPanel-button-text"> - Slide {this.itemIndex + 1} / {this.childDocs.length} - {this.playButtonFrames} + Slide {this.itemIndex + 1} + {this.activeItem?.presIndexed !== undefined ? `(${this.activeItem.presIndexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length} </div> <div className="presPanel-divider" /> <div className="presPanel-button-text" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.exitClicked, false, false)}> @@ -3142,12 +2444,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> </div> ) : ( - <div className="presBox-cont" style={{ minWidth: DocListCast(Doc.MyOverlayDocs?.data).includes(this.layoutDoc) ? 240 : undefined }}> + <div className="presBox-cont" style={{ minWidth: inOverlay ? PresBox.minimizedWidth : undefined }}> {this.topPanel} {this.toolbar} {this.newDocumentToolbarDropdown} <div className="presBox-listCont"> - <div className="Slide" style={{ height: `calc(100% - 30px)` }}> + <div className="Slide"> {mode !== CollectionViewType.Invalid ? ( <CollectionView {...this.props} @@ -3156,30 +2458,34 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { PanelHeight={this.panelHeight} childIgnoreNativeSize={true} moveDocument={returnFalse} - childFitWidth={returnTrue} + ignoreUnrendered={true} + //childFitWidth={returnTrue} childOpacity={returnOne} + //childLayoutString={PresElementBox.LayoutString('data')} + childClickScript={PresBox.navigateToDocScript} childLayoutTemplate={this.childLayoutTemplate} + childXPadding={Doc.IsComicStyle(this.rootDoc) ? 20 : undefined} filterAddDocument={this.addDocumentFilter} removeDocument={returnFalse} dontRegisterView={true} - focus={this.selectElement} + focus={this.focusElement} scriptContext={this} ScreenToLocalTransform={this.getTransform} AddToMap={this.AddToMap} RemFromMap={this.RemFromMap} - hierarchyIndex={[]} + hierarchyIndex={emptyPath} /> ) : null} </div> - { + {/* { // if the document type is a presentation, then the collection stacking view has a "+ new slide" button at the bottom of the stack <Tooltip title={<div className="dash-tooltip">{'Click on document to pin to presentaiton or make a marquee selection to pin your desired view'}</div>}> <button className="add-slide-button" onPointerDown={this.startMarqueeCreateSlide}> + NEW SLIDE </button> </Tooltip> - } + } */} </div> </div> ); @@ -3187,10 +2493,5 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } 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)); + PresBox.NavigateToTarget(bestTarget, activeItem); }); |
