diff options
Diffstat (limited to 'src/client/views/nodes/trails/PresBox.tsx')
| -rw-r--r-- | src/client/views/nodes/trails/PresBox.tsx | 1893 |
1 files changed, 1182 insertions, 711 deletions
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 91fdb90fc..101f28ae7 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1,82 +1,74 @@ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; +import Slider from '@mui/material/Slider'; +import { Button, Dropdown, DropdownType, IconButton, Toggle, ToggleType, Type } from 'browndash-components'; import { action, computed, IReactionDisposer, makeObservable, observable, ObservableSet, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, Field, FieldResult, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; +import { AiOutlineSend } from 'react-icons/ai'; +import { BiMicrophone } from 'react-icons/bi'; +import { FaArrowDown, FaArrowLeft, FaArrowRight, FaArrowUp } from 'react-icons/fa'; +import ReactLoading from 'react-loading'; +import ReactTextareaAutosize from 'react-textarea-autosize'; +import { lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from '../../../../ClientUtils'; +import { Doc, DocListCast, Field, FieldResult, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols'; -import { Copy, Id } from '../../../../fields/FieldSymbols'; +import { Copy } from '../../../../fields/FieldSymbols'; import { InkField } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { ObjectField } from '../../../../fields/ObjectField'; import { listSpec } from '../../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../../fields/ScriptField'; -import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { AudioField } from '../../../../fields/URLField'; -import { emptyFunction, emptyPath, lightOrDark, returnFalse, returnOne, setupMoveUpEvents, StopEvent, stringHash } from '../../../../Utils'; +import { BoolCast, Cast, DocCast, NumCast, StrCast, toList } from '../../../../fields/Types'; +import { emptyFunction, emptyPath, stringHash } from '../../../../Utils'; +import { getSlideTransitionSuggestions, gptSlideProperties, gptTrailSlideCustomization } from '../../../apis/gpt/customization'; import { DocServer } from '../../../DocServer'; import { Docs } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; -import { DocumentManager } from '../../../util/DocumentManager'; -import { dropActionType } from '../../../util/DragManager'; +import { DictationManager } from '../../../util/DictationManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; -import { SelectionManager } from '../../../util/SelectionManager'; import { SerializationHelper } from '../../../util/SerializationHelper'; -import { SettingsManager } from '../../../util/SettingsManager'; +import { SnappingManager } from '../../../util/SnappingManager'; import { undoBatch, UndoManager } from '../../../util/UndoManager'; -import { CollectionDockingView } from '../../collections/CollectionDockingView'; -import { CollectionFreeFormView, MarqueeViewBounds } from '../../collections/collectionFreeForm'; -import { CollectionStackedTimeline } from '../../collections/CollectionStackedTimeline'; +import { CollectionFreeFormView } from '../../collections/collectionFreeForm'; +import { CollectionFreeFormPannableContents } from '../../collections/collectionFreeForm/CollectionFreeFormPannableContents'; import { CollectionView } from '../../collections/CollectionView'; import { TreeView } from '../../collections/TreeView'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { Colors } from '../../global/globalEnums'; -import { LightboxView } from '../../LightboxView'; -import { DocumentView, OpenWhere, OpenWhereMod } from '../DocumentView'; -import { FieldView, FieldViewProps, FocusViewOptions } from '../FieldView'; +import { pinDataTypes as dataTypes } from '../../PinFuncs'; +import { DocumentView } from '../DocumentView'; +import { FieldView, FieldViewProps } from '../FieldView'; +import { FocusViewOptions } from '../FocusViewOptions'; +import { OpenWhere, OpenWhereMod } from '../OpenWhere'; import { ScriptingBox } from '../ScriptingBox'; +import CubicBezierEditor, { EaseFuncToPoints, TIMING_DEFAULT_MAPPINGS } from './CubicBezierEditor'; import './PresBox.scss'; import { PresEffect, PresEffectDirection, PresMovement, PresStatus } from './PresEnums'; -export interface pinDataTypes { - scrollable?: boolean; - dataviz?: number[]; - pannable?: boolean; - type_collection?: boolean; - inkable?: boolean; - filters?: boolean; - pivot?: boolean; - temporal?: boolean; - clippable?: boolean; - datarange?: boolean; - dataview?: boolean; - poslayoutview?: boolean; - dataannos?: boolean; - map?: boolean; -} -export interface PinProps { - audioRange?: boolean; - activeFrame?: number; - currentFrame?: number; - hidePresBox?: boolean; - pinViewport?: MarqueeViewBounds; // pin a specific viewport on a freeform view (use MarqueeView.CurViewBounds to compute if no region has been selected) - pinDocLayout?: boolean; // pin layout info (width/height/x/y) - pinAudioPlay?: boolean; // pin audio annotation - pinData?: pinDataTypes; -} +import SlideEffect from './SlideEffect'; +import { AnimationSettings, easeItems, effectItems, effectTimings, movementItems, presEffectDefaultTimings, springMappings, springPreviewColors, SpringSettings, SpringType } from './SpringUtils'; @observer export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresBox, fieldKey); } + private static _getTabDocs: () => Doc[]; + public static Init(tabDocs: () => Doc[]) { + PresBox._getTabDocs = tabDocs; + } static navigateToDocScript: ScriptField; constructor(props: FieldViewProps) { super(props); makeObservable(this); if (!PresBox.navigateToDocScript) { - PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, self)')!; + PresBox.navigateToDocScript = ScriptField.MakeFunction('navigateToDoc(this.presentation_targetDoc, this)')!; } + CollectionFreeFormPannableContents.SetOverlayPlugin((fform: Doc) => PresBox.Instance.pathLines(fform)); } private _disposers: { [name: string]: IReactionDisposer } = {}; @@ -86,6 +78,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things _presTimer: NodeJS.Timeout | undefined; + // eslint-disable-next-line no-use-before-define @observable public static Instance: PresBox; @observable _isChildActive = false; @@ -104,7 +97,100 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @observable _treeViewMap: Map<Doc, number> = new Map(); @observable _presKeyEvents: boolean = false; @observable _forceKeyEvents: boolean = false; - @computed get isTreeOrStack() { + + // GPT + private _inputref: HTMLTextAreaElement | null = null; + private _inputref2: HTMLTextAreaElement | null = null; + @observable chatActive: boolean = false; + @observable chatInput: string = ''; + public slideToModify: Doc | null = null; + @observable isRecording: boolean = false; + @observable isLoading: boolean = false; + + @observable generatedAnimations: AnimationSettings[] = [ + // default presets + { + effect: PresEffect.Bounce, + direction: PresEffectDirection.Left, + stiffness: 400, + damping: 15, + mass: 1, + }, + { + effect: PresEffect.Fade, + direction: PresEffectDirection.Left, + stiffness: 100, + damping: 15, + mass: 1, + }, + { + effect: PresEffect.Flip, + direction: PresEffectDirection.Left, + stiffness: 100, + damping: 15, + mass: 1, + }, + { + effect: PresEffect.Rotate, + direction: PresEffectDirection.Left, + stiffness: 100, + damping: 15, + mass: 1, + }, + ]; + + @action + setGeneratedAnimations = (settings: AnimationSettings[]) => { + this.generatedAnimations = settings; + }; + + @observable animationChat: string = ''; + + @action + setChatInput = (input: string) => { + this.chatInput = input; + }; + + @action + setAnimationChat = (input: string) => { + this.animationChat = input; + }; + + @action + setIsLoading = (isLoading: boolean) => { + this.isLoading = isLoading; + }; + + @action + public setIsRecording = (isRecording: boolean) => { + this.isRecording = isRecording; + }; + + @observable showBezierEditor = false; + @action setBezierEditorVisibility = (visible: boolean) => { + this.showBezierEditor = visible; + }; + @observable showSpringEditor = true; + @action setSpringEditorVisibility = (visible: boolean) => { + this.showSpringEditor = visible; + }; + + // Easing function variables + + @observable easeDropdownVal = 'ease'; + + @action setBezierControlPoints = (newPoints: { p1: number[]; p2: number[] }) => { + this.setEaseFunc(this.activeItem, `cubic-bezier(${newPoints.p1[0]}, ${newPoints.p1[1]}, ${newPoints.p2[0]}, ${newPoints.p2[1]})`); + }; + + @computed + get currCPoints() { + const strPoints = this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease'; + return EaseFuncToPoints(strPoints); + } + + @computed + get isTreeOrStack() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(StrCast(this.layoutDoc._type_collection) as any); } @computed get isTree() { @@ -126,7 +212,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return DocCast(this.childDocs[NumCast(this.Document._itemIndex)]); } @computed get targetDoc() { - return Cast(this.activeItem?.presentation_targetDoc, Doc, null); + return DocCast(this.activeItem?.presentation_targetDoc); } public static targetRenderedDoc = (doc: Doc) => { const targetDoc = Cast(doc?.presentation_targetDoc, Doc, null); @@ -141,8 +227,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return false; } @computed get selectedDocumentView() { - if (SelectionManager.Views.length) return SelectionManager.Views[0]; - if (this.selectedArray.size) return DocumentManager.Instance.getDocumentView(this.Document); + if (DocumentView.Selected().length) return DocumentView.Selected()[0]; + if (this.selectedArray.size) return DocumentView.getDocumentView(this.Document); + return undefined; } @computed get isPres() { return this.selectedDoc === this.Document; @@ -165,6 +252,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } componentDidMount() { + this._disposers.pause = reaction( + () => SnappingManager.UserPanned, + () => this.pauseAutoPres() + ); this._disposers.keyboard = reaction( () => this.selectedDoc, selected => { @@ -189,7 +280,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._unmounting = false; this.turnOffEdit(true); this._disposers.selection = reaction( - () => SelectionManager.Views.slice(), + () => DocumentView.Selected().slice(), views => (!PresBox.Instance || views.some(view => view.Document === this.Document)) && this.updateCurrentPresentation(), { fireImmediately: true } ); @@ -197,7 +288,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { () => this.layoutDoc.presentation_status === PresStatus.Edit, editing => editing && this.childDocs.filter(doc => doc.presentation_indexed !== undefined).forEach(doc => { - this.progressivizedItems(doc)?.forEach(indexedDoc => (indexedDoc.opacity = undefined)); + this.progressivizedItems(doc)?.forEach(indexedDoc => { indexedDoc.opacity = undefined; }); doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, 1); }) // prettier-ignore ); @@ -214,7 +305,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { startTempMedia = (targetDoc: Doc, activeItem: Doc) => { const duration: number = NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart); if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) { - const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); + const targMedia = DocumentView.getDocumentView(targetDoc); targMedia?.ComponentView?.playFrom?.(NumCast(activeItem.config_clipStart), NumCast(activeItem.config_clipStart) + duration); } }; @@ -222,18 +313,97 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { stopTempMedia = (targetDocField: FieldResult) => { const targetDoc = DocCast(DocCast(targetDocField).annotationOn) ?? DocCast(targetDocField); if ([DocumentType.VID, DocumentType.AUDIO].includes(targetDoc.type as any)) { - const targMedia = DocumentManager.Instance.getDocumentView(targetDoc); + const targMedia = DocumentView.getDocumentView(targetDoc); targMedia?.ComponentView?.Pause?.(); } }; - //TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time + // Recording for GPT customization + + recordDictation = () => { + this.setIsRecording(true); + this.setChatInput(''); + DictationManager.Controls.listen({ + interimHandler: this.setDictationContent, + continuous: { indefinite: false }, + }).then(results => { + if (results && [DictationManager.Controls.Infringed].includes(results)) { + DictationManager.Controls.stop(); + } + }); + }; + stopDictation = () => { + this.setIsRecording(false); + DictationManager.Controls.stop(); + }; + + setDictationContent = (value: string) => { + console.log('Dictation value', value); + this.setChatInput(value); + }; + + @action + customizeAnimations = async () => { + this.setIsLoading(true); + try { + const res = await getSlideTransitionSuggestions(this.animationChat); + if (typeof res === 'string') { + const resObj = JSON.parse(res); + console.log('Parsed GPT Result ', resObj); + this.setGeneratedAnimations(resObj as AnimationSettings[]); + } + } catch (err) { + console.error(err); + } + this.setIsLoading(false); + }; + + @action + customizeWithGPT = async (input: string) => { + // const testInput = 'change title to Customized Slide, transition for 2.3s with fade in effect'; + this.setIsRecording(false); + this.setIsLoading(true); + + const currSlideProperties: { [key: string]: any } = {}; + gptSlideProperties.forEach(key => { + if (this.activeItem[key]) { + currSlideProperties[key] = this.activeItem[key]; + } + // default values + else if (key === 'presentation_transition') { + currSlideProperties[key] = 500; + } else if (key === 'config_zoom') { + currSlideProperties[key] = 1.0; + } + }); + console.log('current slide props ', currSlideProperties); + + try { + const res = await gptTrailSlideCustomization(input, currSlideProperties); + if (typeof res === 'string') { + const resObj = JSON.parse(res); + console.log('Parsed GPT Result ', resObj); + // eslint-disable-next-line no-restricted-syntax + for (const key in resObj) { + if (resObj[key]) { + console.log('typeof property', typeof resObj[key]); + this.activeItem[key] = resObj[key]; + } + } + } + } catch (err) { + console.error(err); + } + this.setIsLoading(false); + }; + + // TODO: al: it seems currently that tempMedia doesn't stop onslidechange after clicking the button; the time the tempmedia stop depends on the start & end time // TODO: to handle child slides (entering into subtrail and exiting), also the next() and back() functions // No more frames in current doc and next slide is defined, therefore move to next slide nextSlide = (slideNum?: number) => { const nextSlideInd = slideNum ?? this.itemIndex + 1; let curSlideInd = nextSlideInd; - //CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.()); + // CollectionStackedTimeline.CurrentlyPlaying?.map(clipView => clipView?.ComponentView?.Pause?.()); this.clearSelectedArray(); const doGroupWithUp = (nextSelected: number, force = false) => @@ -245,7 +415,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (serial) { this.gotoDocument(nextSelected, this.activeItem, true, async () => { const waitTime = NumCast(this.activeItem.presentation_duration); - await new Promise<void>(res => setTimeout(() => res(), Math.max(0, waitTime))); + await new Promise<void>(res => { + setTimeout(res, Math.max(0, waitTime)); + }); doGroupWithUp(nextSelected + 1)(); }); } else { @@ -264,14 +436,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targetList = PresBox.targetRenderedDoc(doc); if (doc.presentation_indexed !== undefined && targetList) { const listItems = (Cast(targetList[Doc.LayoutFieldKey(targetList)], listSpec(Doc), null)?.filter(d => d instanceof Doc) as Doc[]) ?? DocListCast(targetList[Doc.LayoutFieldKey(targetList) + '_annotations']); - return listItems.filter(doc => !doc.layout_unrendered); + return listItems.filter(ldoc => !ldoc.layout_unrendered); } + return undefined; }; // go to documents chain runSubroutines = (childrenToRun: Opt<Doc[]>, normallyNextSlide: Doc) => { if (childrenToRun && childrenToRun[0] !== normallyNextSlide) { - childrenToRun.forEach(child => DocumentManager.Instance.showDocument(child, {})); + childrenToRun.forEach(child => DocumentView.showDocument(child, {})); } }; @@ -284,12 +457,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem); targetRenderedDoc._dataTransition = 'all 1s'; targetRenderedDoc.opacity = 1; - setTimeout(() => (targetRenderedDoc._dataTransition = 'inherit'), 1000); + 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); + const targetView = listItems && DocumentView.getFirstDocumentView(listItemDoc); Doc.linkFollowUnhighlight(); Doc.HighlightDoc(listItemDoc); listItemDoc.presentation_effect = this.activeItem.presBulletEffect; @@ -305,6 +480,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return true; } } + return undefined; }; if (progressiveReveal(false)) return true; if (this.childDocs[this.itemIndex + 1] !== undefined) { @@ -314,7 +490,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // before moving onto next slide, run the subroutines :) const currentDoc = this.childDocs[this.itemIndex]; - //could i do this.childDocs[this.itemIndex] for first arg? + // could i do this.childDocs[this.itemIndex] for first arg? this.runSubroutines(TreeView.GetRunningChildren.get(currentDoc)?.(), this.childDocs[this.itemIndex + 1]); this.nextSlide(curLast + 1 === this.childDocs.length ? (this.layoutDoc.presLoop ? 0 : curLast) : curLast + 1); @@ -334,7 +510,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // Called when the user activates 'back' - to move to the previous part of the pres. trail @action back = () => { - const activeItem: Doc = this.activeItem; + const { activeItem } = this; let prevSelected = this.itemIndex; // Functionality for group with up let didZoom = activeItem.presentation_movement; @@ -353,8 +529,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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. + // The function that is called when a document is clicked or reached through next or back. + // it'll also execute the necessary actions if presentation is playing. @undoBatch public gotoDocument = action((index: number, from?: Doc, group?: boolean, finished?: () => void) => { Doc.UnBrushAllDocs(); @@ -371,13 +547,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.startTempMedia(this.targetDoc, this.activeItem); } if (!group) this.clearSelectedArray(); - this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); //Update selected array + this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); // Update selected array this.turnOffEdit(); - this.navigateToActiveItem(finished); //Handles movement to element only when presentationTrail is list - this.doHideBeforeAfter(); //Handles hide after/before + this.navigateToActiveItem(finished); // Handles movement to element only when presentationTrail is list + this.doHideBeforeAfter(); // Handles hide after/before } }); - static pinDataTypes(target?: Doc): pinDataTypes { + static pinDataTypes(target?: Doc): dataTypes { const targetType = target?.type as any; const inkable = [DocumentType.INK].includes(targetType); const scrollable = [DocumentType.PDF, DocumentType.RTF, DocumentType.WEB].includes(targetType) || target?._type_collection === CollectionViewType.Stacking; @@ -388,19 +564,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const datarange = [DocumentType.FUNCPLOT].includes(targetType); const dataview = [DocumentType.INK, DocumentType.COL, DocumentType.IMG, DocumentType.RTF].includes(targetType) && target?.activeFrame === undefined; const poslayoutview = [DocumentType.COL].includes(targetType) && target?.activeFrame === undefined; - const type_collection = targetType === DocumentType.COL; + const typeCollection = targetType === DocumentType.COL; const filters = true; const pivot = true; const dataannos = false; - return { scrollable, pannable, inkable, type_collection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos }; + return { scrollable, pannable, inkable, type_collection: typeCollection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos }; } @action - playAnnotation = (anno: AudioField) => {}; + playAnnotation = (/* anno: AudioField */) => { + /* empty */ + }; @action - static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: pinDataTypes, targetDoc?: Doc) { + // eslint-disable-next-line default-param-last + static restoreTargetDocView(bestTargetView: Opt<DocumentView>, activeItem: Doc, transTime: number, pinDocLayout: boolean = BoolCast(activeItem.config_pinLayout), pinDataTypes?: dataTypes, targetDoc?: Doc) { const bestTarget = bestTargetView?.Document ?? (targetDoc?.layout_unrendered ? DocCast(targetDoc?.annotationOn) : targetDoc); - if (!bestTarget) return; + if (!bestTarget) return undefined; let changed = false; if (pinDocLayout) { if ( @@ -417,20 +596,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { bestTarget.width = NumCast(activeItem.config_width, NumCast(bestTarget.width)); bestTarget.height = NumCast(activeItem.config_height, NumCast(bestTarget.height)); bestTarget[TransitionTimer] && clearTimeout(bestTarget[TransitionTimer]); - bestTarget[TransitionTimer] = setTimeout(() => (bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined), transTime + 10); + bestTarget[TransitionTimer] = setTimeout(() => { + bestTarget[TransitionTimer] = bestTarget._dataTransition = undefined; + }, transTime + 10); changed = true; } } const activeFrame = activeItem.config_activeFrame ?? activeItem.config_currentFrame; if (activeFrame !== undefined) { - const transTime = NumCast(activeItem.presentation_transition, 500); + const frameTime = NumCast(activeItem.presentation_transition, 500); const acontext = activeItem.config_activeFrame !== undefined ? DocCast(DocCast(activeItem.presentation_targetDoc).embedContainer) : DocCast(activeItem.presentation_targetDoc); const context = DocCast(acontext)?.annotationOn ? DocCast(DocCast(acontext).annotationOn) : acontext; if (context) { - const ffview = DocumentManager.Instance.getFirstDocumentView(context)?.CollectionFreeFormView; + const ffview = CollectionFreeFormView.from(DocumentView.getFirstDocumentView(context)); if (ffview?.childDocs) { - PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, transTime); + PresBox.Instance._keyTimer = CollectionFreeFormView.gotoKeyframe(PresBox.Instance._keyTimer, ffview.childDocs, frameTime); ffview.layoutDoc._currentFrame = NumCast(activeFrame); } } @@ -443,12 +624,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { else { const bestTargetData = bestTarget[DocData]; const current = bestTargetData[fkey]; - const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as Field)) : undefined; + const hash = bestTargetData[fkey] ? stringHash(Field.toString(bestTargetData[fkey] as FieldType)) : undefined; if (hash) bestTargetData[fkey + '_' + hash] = current instanceof ObjectField ? current[Copy]() : current; bestTargetData[fkey] = activeItem.config_data instanceof ObjectField ? activeItem.config_data[Copy]() : activeItem.config_data; } bestTarget[fkey + '_usePath'] = activeItem.config_usePath; - setTimeout(() => (bestTarget._dataTransition = undefined), transTime + 10); + setTimeout(() => { + bestTarget._dataTransition = undefined; + }, transTime + 10); } if (pinDataTypes?.datarange || (!pinDataTypes && activeItem.config_xRange !== undefined)) { if (bestTarget.xRange !== activeItem.config_xRange) { @@ -590,7 +773,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { Doc.AddDocToList(bestTarget[DocData], layoutField, doc); } }); - setTimeout(() => Array.from(transitioned).forEach(action(doc => (doc._dataTransition = undefined))), transTime + 10); + setTimeout( + () => + Array.from(transitioned).forEach( + action(doc => { + doc._dataTransition = undefined; + }) + ), + transTime + 10 + ); } if ((pinDataTypes?.pannable || (!pinDataTypes && (activeItem.config_viewBounds !== undefined || activeItem.config_panX !== undefined || activeItem.config_viewScale !== undefined))) && !bestTarget.isGroup) { const contentBounds = Cast(activeItem.config_viewBounds, listSpec('number')); @@ -598,132 +789,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const viewport = { panX: (contentBounds[0] + contentBounds[2]) / 2, panY: (contentBounds[1] + contentBounds[3]) / 2, width: contentBounds[2] - contentBounds[0], height: contentBounds[3] - contentBounds[1] }; bestTarget._freeform_panX = viewport.panX; bestTarget._freeform_panY = viewport.panY; - const dv = DocumentManager.Instance.getDocumentView(bestTarget); + const dv = DocumentView.getDocumentView(bestTarget); if (dv) { changed = true; const computedScale = NumCast(activeItem.config_zoom, 1) * Math.min(dv._props.PanelWidth() / viewport.width, dv._props.PanelHeight() / viewport.height); activeItem.presentation_movement === PresMovement.Zoom && (bestTarget._freeform_scale = computedScale); dv.ComponentView?.brushView?.(viewport, transTime, 2500); } - } else { - if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) { - bestTarget._freeform_panX = activeItem.config_panX ?? bestTarget._freeform_panX; - bestTarget._freeform_panY = activeItem.config_panY ?? bestTarget._freeform_panY; - bestTarget._freeform_scale = activeItem.config_viewScale ?? bestTarget._freeform_scale; - changed = true; - } + } else if (bestTarget._freeform_panX !== activeItem.config_panX || bestTarget._freeform_panY !== activeItem.config_panY || bestTarget._freeform_scale !== activeItem.config_viewScale) { + bestTarget._freeform_panX = activeItem.config_panX ?? bestTarget._freeform_panX; + bestTarget._freeform_panY = activeItem.config_panY ?? bestTarget._freeform_panY; + bestTarget._freeform_scale = activeItem.config_viewScale ?? bestTarget._freeform_scale; + changed = true; } } if (changed) { return bestTargetView?.setViewTransition('all', transTime); } - } - - /// copies values from the targetDoc (which is the prototype of the pinDoc) to - /// reserved fields on the pinDoc so that those values can be restored to the - /// target doc when navigating to it. - @action - static pinDocView(pinDoc: Doc, pinProps: PinProps, targetDoc: Doc) { - pinDoc.presentation = true; - pinDoc.config = ''; - if (pinProps.pinDocLayout) { - pinDoc.config_pinLayout = true; - pinDoc.config_x = NumCast(targetDoc.x); - pinDoc.config_y = NumCast(targetDoc.y); - pinDoc.config_rotation = NumCast(targetDoc.rotation); - pinDoc.config_width = NumCast(targetDoc.width); - pinDoc.config_height = NumCast(targetDoc.height); - } - if (pinProps.pinAudioPlay) pinDoc.presPlayAudio = true; - if (pinProps.pinData) { - pinDoc.config_pinData = - pinProps.pinData.scrollable || - pinProps.pinData.temporal || - pinProps.pinData.pannable || - pinProps.pinData.type_collection || - pinProps.pinData.clippable || - pinProps.pinData.datarange || - pinProps.pinData.dataview || - pinProps.pinData.poslayoutview || - pinProps?.activeFrame !== undefined; - const fkey = Doc.LayoutFieldKey(targetDoc); - if (pinProps.pinData.dataview) { - pinDoc.config_usePath = targetDoc[fkey + '_usePath']; - pinDoc.config_data = targetDoc[fkey] instanceof ObjectField ? (targetDoc[fkey] as ObjectField)[Copy]() : targetDoc.data; - } - if (pinProps.pinData.dataannos) { - const fkey = Doc.LayoutFieldKey(targetDoc); - pinDoc.config_annotations = new List<Doc>(DocListCast(targetDoc[DocData][fkey + '_annotations']).filter(doc => !doc.layout_unrendered)); - } - if (pinProps.pinData.inkable) { - pinDoc.config_fillColor = targetDoc.fillColor; - pinDoc.config_color = targetDoc.color; - pinDoc.config_width = targetDoc._width; - pinDoc.config_height = targetDoc._height; - } - if (pinProps.pinData.scrollable) pinDoc.config_scrollTop = targetDoc._layout_scrollTop; - if (pinProps.pinData.clippable) { - const fkey = Doc.LayoutFieldKey(targetDoc); - pinDoc.config_clipWidth = targetDoc[fkey + '_clipWidth']; - } - if (pinProps.pinData.datarange) { - pinDoc.config_xRange = undefined; //targetDoc?.xrange; - pinDoc.config_yRange = undefined; //targetDoc?.yrange; - } - if (pinProps.pinData.map) { - // pinDoc.config_latitude = targetDoc?.latitude; - // pinDoc.config_longitude = targetDoc?.longitude; - pinDoc.config_map_zoom = targetDoc?.map_zoom; - pinDoc.config_map_type = targetDoc?.map_type; - //... - } - if (pinProps.pinData.poslayoutview) - pinDoc.config_pinLayoutData = new List<string>( - DocListCast(targetDoc[fkey] as ObjectField).map(d => - JSON.stringify({ - id: d[Id], - x: NumCast(d.x), - y: NumCast(d.y), - w: NumCast(d._width), - h: NumCast(d._height), - fill: StrCast(d._fillColor), - back: StrCast(d._backgroundColor), - data: SerializationHelper.Serialize(d.data instanceof ObjectField ? d.data[Copy]() : ''), - text: SerializationHelper.Serialize(d.text instanceof ObjectField ? d.text[Copy]() : ''), - }) - ) - ); - if (pinProps.pinData.type_collection) pinDoc.config_viewType = targetDoc._type_collection; - if (pinProps.pinData.filters) pinDoc.config_docFilters = ObjectField.MakeCopy(targetDoc.childFilters as ObjectField); - if (pinProps.pinData.pivot) pinDoc.config_pivotField = targetDoc._pivotField; - if (pinProps.pinData.pannable) { - pinDoc.config_panX = NumCast(targetDoc._freeform_panX); - pinDoc.config_panY = NumCast(targetDoc._freeform_panY); - pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1); - } - if (pinProps.pinData.temporal) { - pinDoc.config_clipStart = targetDoc._layout_currentTimecode; - const duration = NumCast(pinDoc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], NumCast(targetDoc.config_clipStart) + 0.1); - pinDoc.config_clipEnd = NumCast(pinDoc.config_clipStart) + NumCast(targetDoc.clipEnd, duration); - } - } - if (pinProps?.pinViewport) { - // If pinWithView option set then update scale and x / y props of slide - const bounds = pinProps.pinViewport; - pinDoc.config_pinView = true; - pinDoc.config_viewScale = NumCast(targetDoc._freeform_scale, 1); - pinDoc.config_panX = bounds.left + bounds.width / 2; - pinDoc.config_panY = bounds.top + bounds.height / 2; - pinDoc.config_viewBounds = new List<number>([bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + bounds.height]); - } - } - - @action - static reversePin(pinDoc: Doc, targetDoc: Doc) { - // const fkey = Doc.LayoutFieldKey(targetDoc); - pinDoc.config_data = targetDoc.data; - - console.log(pinDoc.presData); + return undefined; } /** @@ -735,8 +818,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { * on the right. */ navigateToActiveItem = (afterNav?: () => void) => { - const activeItem: Doc = this.activeItem; - const targetDoc: Doc = this.targetDoc; + const { activeItem, targetDoc } = this; const finished = () => { afterNav?.(); targetDoc[Animation] = undefined; @@ -746,8 +828,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const eleViewCache = Array.from(this._eleArray); const resetSelection = action(() => { if (!this._props.isSelected()) { - const presDocView = DocumentManager.Instance.getDocumentView(this.Document); - if (presDocView) SelectionManager.SelectView(presDocView, false); + const presDocView = DocumentView.getDocumentView(this.Document); + if (presDocView) DocumentView.SelectView(presDocView, false); this.clearSelectedArray(); selViewCache.forEach(doc => this.addToSelectedArray(doc)); this._dragArray.splice(0, this._dragArray.length, ...dragViewCache); @@ -762,10 +844,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) { if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { - (DocumentManager.Instance.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); + (DocumentView.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); return; } const effect = activeItem.presentation_effect && activeItem.presentation_effect !== PresEffect.None ? activeItem.presentation_effect : undefined; + // default with effect: 750ms else 500ms const presTime = NumCast(activeItem.presentation_transition, effect ? 750 : 500); const options: FocusViewOptions = { willPan: activeItem.presentation_movement !== PresMovement.None, @@ -775,27 +858,26 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { effect: activeItem, noSelect: true, openLocation: targetDoc.type === DocumentType.PRES ? ((OpenWhere.replace + ':' + PresBox.PanelName) as OpenWhere) : OpenWhere.addLeft, - anchorDoc: activeItem, - easeFunc: StrCast(activeItem.presEaseFunc, 'ease') as any, + easeFunc: StrCast(activeItem.presentation_easeFunc, 'ease') as any, zoomTextSelections: BoolCast(activeItem.presentation_zoomText), - playAudio: BoolCast(activeItem.presPlayAudio), + playAudio: BoolCast(activeItem.presentation_playAudio), playMedia: activeItem.presentation_mediaStart === 'auto', }; if (activeItem.presentation_openInLightbox) { const context = DocCast(targetDoc.annotationOn) ?? targetDoc; - if (!DocumentManager.Instance.getLightboxDocumentView(context)) { - LightboxView.Instance.SetLightboxDoc(context); + if (!DocumentView.getLightboxDocumentView(context)) { + DocumentView.SetLightboxDoc(context); } } if (targetDoc) { if (activeItem.presentation_targetDoc instanceof Doc) activeItem.presentation_targetDoc[Animation] = undefined; - DocumentManager.Instance.AddViewRenderedCb(LightboxView.LightboxDoc, dv => { + DocumentView.addViewRenderedCb(DocumentView.LightboxDoc(), () => { // if target or the doc it annotates is not in the lightbox, then close the lightbox - if (!DocumentManager.Instance.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) { - LightboxView.Instance.SetLightboxDoc(undefined); + if (!DocumentView.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) { + DocumentView.SetLightboxDoc(undefined); } - DocumentManager.Instance.showDocument(targetDoc, options, finished); + DocumentView.showDocument(targetDoc, options, finished); }); } else finished?.(); } @@ -822,7 +904,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { opacity = 0; } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) { opacity = 1; - setTimeout(() => (tagDoc._dataTransition = undefined), 1000); + setTimeout(() => { + tagDoc._dataTransition = undefined; + }, 1000); } } const hidingIndAft = @@ -848,16 +932,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }; _exitTrail: Opt<() => void>; - PlayTrail = (docs: Doc[]) => { + playTrail = (docs: Doc[]) => { const savedStates = docs.map(doc => { switch (doc.type) { case DocumentType.COL: - if (doc._type_collection === CollectionViewType.Freeform) return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) }; + if (doc._type_collection === CollectionViewType.Freeform) { + return { type: CollectionViewType.Freeform, doc, x: NumCast(doc.freeform_panX), y: NumCast(doc.freeform_panY), s: NumCast(doc.freeform_scale) }; + } break; case DocumentType.INK: if (doc.data instanceof InkField) { return { type: doc.type, doc, data: doc.data?.[Copy](), fillColor: doc.fillColor, color: doc.color, x: NumCast(doc.x), y: NumCast(doc.y) }; } + break; + default: } return undefined; }); @@ -865,7 +953,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._exitTrail = () => { savedStates .filter(savedState => savedState) - .map(savedState => { + .forEach(savedState => { switch (savedState?.type) { case CollectionViewType.Freeform: { @@ -885,9 +973,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { doc.color = color; } break; + default: } }); - LightboxView.Instance.SetLightboxDoc(undefined); + DocumentView.SetLightboxDoc(undefined); Doc.RemFromMyOverlay(this.Document); return PresStatus.Edit; }; @@ -903,8 +992,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. + // The function that resets the presentation by removing every action done by it. It also + // stops the presentaton. resetPresentation = () => { this.childDocs .map(doc => PresBox.targetRenderedDoc(doc)) @@ -921,12 +1010,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // The function allows for viewing the pres path on toggle @action togglePath = (off?: boolean) => { this._pathBoolean = off ? false : !this._pathBoolean; - CollectionFreeFormView.ShowPresPaths = this._pathBoolean; + SnappingManager.SetShowPresPaths(this._pathBoolean); }; // The function allows for expanding the view of pres on toggle @action toggleExpandMode = () => { - runInAction(() => (this._expandBoolean = !this._expandBoolean)); + runInAction(() => { + this._expandBoolean = !this._expandBoolean; + }); this.Document.expandBoolean = this._expandBoolean; this.childDocs.forEach(doc => { doc.presentation_expandInlineButton = this._expandBoolean; @@ -942,7 +1033,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const startInd = NumCast(doc.presentation_indexedStart); this.progressivizedItems(doc) ?.slice(startInd) - .forEach(indexedDoc => (indexedDoc.opacity = 0)); + .forEach(indexedDoc => { + indexedDoc.opacity = 0; + }); doc.presentation_indexed = Math.min(this.progressivizedItems(doc)?.length ?? 0, startInd); } // if (doc.presentation_hide && this.childDocs.indexOf(doc) === startIndex) tagDoc.opacity = 0; @@ -986,20 +1079,20 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { exitMinimize = () => { if (Doc.IsInMyOverlay(this.layoutDoc)) { Doc.RemFromMyOverlay(this.Document); - CollectionDockingView.AddSplit(this.Document, OpenWhereMod.right); + DocumentView.addSplit(this.Document, OpenWhereMod.right); } return PresStatus.Edit; }; public static minimizedWidth = 198; public static OpenPresMinimized(doc: Doc, pt: number[]) { - doc.overlayX = pt[0]; - doc.overlayY = pt[1]; + [doc.overlayX, doc.overlayY] = pt; doc._height = 30; doc._width = PresBox.minimizedWidth; Doc.AddToMyOverlay(doc); PresBox.Instance?.initializePresState(PresBox.Instance.itemIndex); - return (doc.presentation_status = PresStatus.Manual); + doc.presentation_status = PresStatus.Manual; + return doc.presentation_status; } /** @@ -1008,12 +1101,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { */ @undoBatch viewChanged = action((e: React.ChangeEvent) => { - //@ts-ignore - const type_collection = e.target.selectedOptions[0].value as CollectionViewType; - this.layoutDoc.presFieldKey = this.fieldKey + (type_collection === CollectionViewType.Tree ? '-linearized' : ''); + const typeCollection = (e.target as any).selectedOptions[0].value as CollectionViewType; + this.layoutDoc.presFieldKey = this.fieldKey + (typeCollection === CollectionViewType.Tree ? '-linearized' : ''); // pivot field may be set by the user in timeline view (or some other way) -- need to reset it here - [CollectionViewType.Tree || CollectionViewType.Stacking].includes(type_collection) && (this.Document._pivotField = undefined); - this.Document._type_collection = type_collection; + [CollectionViewType.Tree || CollectionViewType.Stacking].includes(typeCollection) && (this.Document._pivotField = undefined); + this.Document._type_collection = typeCollection; if (this.isTreeOrStack) { this.layoutDoc._gridGap = 0; } @@ -1025,10 +1117,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { */ // @undoBatch mediaStopChanged = action((e: React.ChangeEvent) => { - const activeItem: Doc = this.activeItem; - //@ts-ignore - const stopDoc = e.target.selectedOptions[0].value as string; - const stopDocIndex: number = Number(stopDoc[0]); + const { activeItem } = this; + const stopDoc = (e.target as any).selectedOptions[0].value as string; + const stopDocIndex = Number(stopDoc[0]); activeItem.mediaStopDoc = stopDocIndex; if (this.childDocs[stopDocIndex - 1].mediaStopTriggerList) { const list = DocListCast(this.childDocs[stopDocIndex - 1].mediaStopTriggerList); @@ -1049,10 +1140,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return StrCast(activeItem.presentation_movement); }); - whenChildContentsActiveChanged = action((isActive: boolean) => this._props.whenChildContentsActiveChanged((this._isChildActive = isActive))); + whenChildContentsActiveChanged = action((isActive: boolean) => { + this._props.whenChildContentsActiveChanged((this._isChildActive = isActive)); + }); // For dragging documents into the presentation trail addDocumentFilter = (docs: Doc[]) => { - docs.forEach((doc, i) => { + const results = docs.map(doc => { if (doc.presentation_targetDoc) return true; if (doc.type === DocumentType.LABEL) { const audio = Cast(doc.annotationOn, Doc, null); @@ -1065,17 +1158,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return false; } } else if (doc.type !== DocumentType.PRES) { + // eslint-disable-next-line operator-assignment if (!doc.presentation_targetDoc) doc.title = doc.title + ' - Slide'; doc.presentation_targetDoc = doc.createdFrom ?? doc; // dropped document will be a new embedding of an embedded document somewhere else. doc.presentation_movement = PresMovement.Zoom; if (this._expandBoolean) doc.presentation_expandInlineButton = true; } + return false; }); - return true; + return !results.some(r => !r); }; childLayoutTemplate = () => Docs.Create.PresElementBoxDocument(); - removeDocument = (doc: Doc | Doc[]) => !(doc instanceof Doc ? [doc] : doc).map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d)).some(p => !p); + removeDocument = (doc: Doc | Doc[]) => + !toList(doc) + .map(d => Doc.RemoveDocFromList(this.Document, this.fieldKey, d)) + .some(p => !p); getTransform = () => this.ScreenToLocalBoxXf().translate(-5, -65); // listBox padding-left and pres-box-cont minHeight panelHeight = () => this._props.PanelHeight() - 40; /** @@ -1092,42 +1190,46 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const tagDoc = Cast(curDoc.presentation_targetDoc, Doc, null); if (curDoc && curDoc === this.activeItem) return ( + // eslint-disable-next-line react/no-array-index-key <div key={index} className="selectedList-items"> <b> {index + 1}. {curDoc.title} </b> </div> ); - else if (tagDoc) + if (tagDoc) return ( + // eslint-disable-next-line react/no-array-index-key <div key={index} className="selectedList-items"> {index + 1}. {curDoc.title} </div> ); - else if (curDoc) + if (curDoc) return ( + // eslint-disable-next-line react/no-array-index-key <div key={index} className="selectedList-items"> {index + 1}. {curDoc.title} </div> ); + return null; }); } @action selectPres = () => { - const presDocView = DocumentManager.Instance.getDocumentView(this.Document); - presDocView && SelectionManager.SelectView(presDocView, false); + const presDocView = DocumentView.getDocumentView(this.Document); + presDocView && DocumentView.SelectView(presDocView, false); }; - focusElement = (doc: Doc, options: FocusViewOptions) => { + focusElement = (doc: Doc) => { this.selectElement(doc); return undefined; }; - //Regular click + // Regular click @action selectElement = (doc: Doc, noNav = false) => { - CollectionStackedTimeline.CurrentlyPlaying?.map((clip, i) => clip?.ComponentView?.Pause?.()); + DocumentView.CurrentlyPlaying?.map(clip => clip?.ComponentView?.Pause?.()); if (noNav) { const index = this.childDocs.indexOf(doc); if (index >= 0 && index < this.childDocs.length) { @@ -1139,7 +1241,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.updateCurrentPresentation(DocCast(doc.embedContainer)); }; - //Command click + // Command click @action multiSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { if (!this.selectedArray.has(doc)) { @@ -1154,7 +1256,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.selectPres(); }; - //Shift click + // Shift click @action shiftSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement) => { this.clearSelectedArray(); @@ -1169,7 +1271,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.selectPres(); }; - //regular click + // regular click @action regularSelect = (doc: Doc, ref: HTMLElement, drag: HTMLElement, noNav: boolean, selectPres = true) => { this.clearSelectedArray(); @@ -1192,6 +1294,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @action keyEvents = (e: KeyboardEvent) => { if (e.target instanceof HTMLInputElement) return; + if (e.target instanceof HTMLTextAreaElement) return; let handled = false; const anchorNode = document.activeElement as HTMLDivElement; if (anchorNode && anchorNode.className?.includes('lm_title')) return; @@ -1200,9 +1303,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (this.layoutDoc.presentation_status === 'edit') { undoBatch( action(() => { - for (const doc of this.selectedArray) { - this.removeDocument(doc); - } + Array.from(this.selectedArray).forEach(doc => this.removeDocument(doc)); this.clearSelectedArray(); this._eleArray.length = 0; this._dragArray.length = 0; @@ -1269,8 +1370,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.childDocs.forEach(doc => this.addToSelectedArray(doc)); handled = true; } - default: break; + default: } if (handled) { e.stopPropagation(); @@ -1285,11 +1386,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const order: JSX.Element[] = []; const docs = new Set<Doc>(); const presCollection = collection; - const dv = DocumentManager.Instance.getDocumentView(presCollection); + const dv = DocumentView.getDocumentView(presCollection); this.childDocs.forEach((doc, index) => { const tagDoc = PresBox.targetRenderedDoc(doc); const srcContext = Cast(tagDoc.embedContainer, Doc, null); const labelCreator = (top: number, left: number, edge: number, fontSize: number) => ( + // eslint-disable-next-line react/no-array-index-key <div className="pathOrder" key={tagDoc.id + 'pres' + index} style={{ top, left, width: edge, height: edge, fontSize }} onClick={() => this.selectElement(doc)}> <div className="pathOrder-frame">{index + 1}</div> </div> @@ -1322,7 +1424,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { order.push( <> {labelCreator(top - indEdge / 2, left - indEdge / 2, indEdge, indFontSize)} - <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }}></div> + <div className="pathOrder-presPinView" style={{ top, left, width, height, borderWidth: indEdge / 10 }} /> </> ); } @@ -1345,17 +1447,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { .filter(doc => PresBox.targetRenderedDoc(doc)?.embedContainer === collection) .forEach((doc, index) => { const tagDoc = PresBox.targetRenderedDoc(doc); - if (tagDoc) { - const n1x = NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2; - const n1y = NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2; - if ((index = 0)) pathPoints = n1x + ',' + n1y; - else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; - } else if (doc.config_pinView) { - const n1x = NumCast(doc.config_panX); - const n1y = NumCast(doc.config_panY); - if ((index = 0)) pathPoints = n1x + ',' + n1y; - else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; - } + const [n1x, n1y] = tagDoc // + ? [NumCast(tagDoc.x) + NumCast(tagDoc._width) / 2, NumCast(tagDoc.y) + NumCast(tagDoc._height) / 2] + : [NumCast(doc.config_panX), NumCast(doc.config_panY)]; + + if (index === 0) pathPoints = n1x + ',' + n1y; + else pathPoints = pathPoints + ' ' + n1x + ',' + n1y; }); return ( <> @@ -1401,7 +1498,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @undoBatch updateTransitionTime = (number: String, change?: number) => { - PresBox.SetTransitionTime(number, (timeInMS: number) => this.selectedArray.forEach(doc => (doc.presentation_transition = timeInMS)), change); + PresBox.SetTransitionTime( + number, + (timeInMS: number) => + this.selectedArray.forEach(doc => { + doc.presentation_transition = timeInMS; + }), + change + ); }; // Converts seconds to ms and updates presentation_transition @@ -1411,7 +1515,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (change) scale += change; if (scale < 0.01) scale = 0.01; if (scale > 1) scale = 1; - this.selectedArray.forEach(doc => (doc.config_zoom = scale)); + this.selectedArray.forEach(doc => { + doc.config_zoom = scale; + }); }; /* @@ -1423,257 +1529,290 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (change) timeInMS += change; if (timeInMS < 100) timeInMS = 100; if (timeInMS > 20000) timeInMS = 20000; - this.selectedArray.forEach(doc => (doc.presentation_duration = timeInMS)); + this.selectedArray.forEach(doc => { + doc.presentation_duration = timeInMS; + }); }; @undoBatch - updateMovement = action((movement: PresMovement, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_movement = movement))); + updateMovement = action((movement: PresMovement, all?: boolean) => + (all ? this.childDocs : this.selectedArray).forEach(doc => { + doc.presentation_movement = movement; + }) + ); @undoBatch updateHideBefore = (activeItem: Doc) => { activeItem.presentation_hideBefore = !activeItem.presentation_hideBefore; - this.selectedArray.forEach(doc => (doc.presentation_hideBefore = activeItem.presentation_hideBefore)); + this.selectedArray.forEach(doc => { + doc.presentation_hideBefore = activeItem.presentation_hideBefore; + }); }; @undoBatch updateHide = (activeItem: Doc) => { activeItem.presentation_hide = !activeItem.presentation_hide; - this.selectedArray.forEach(doc => (doc.presentation_hide = activeItem.presentation_hide)); + this.selectedArray.forEach(doc => { + doc.presentation_hide = activeItem.presentation_hide; + }); }; @undoBatch updateHideAfter = (activeItem: Doc) => { activeItem.presentation_hideAfter = !activeItem.presentation_hideAfter; - this.selectedArray.forEach(doc => (doc.presentation_hideAfter = activeItem.presentation_hideAfter)); + this.selectedArray.forEach(doc => { + doc.presentation_hideAfter = activeItem.presentation_hideAfter; + }); }; @undoBatch updateOpenDoc = (activeItem: Doc) => { activeItem.presentation_openInLightbox = !activeItem.presentation_openInLightbox; - this.selectedArray.forEach(doc => (doc.presentation_openInLightbox = activeItem.presentation_openInLightbox)); + this.selectedArray.forEach(doc => { + doc.presentation_openInLightbox = activeItem.presentation_openInLightbox; + }); }; @undoBatch updateEaseFunc = (activeItem: Doc) => { - activeItem.presEaseFunc = activeItem.presEaseFunc === 'linear' ? 'ease' : 'linear'; - this.selectedArray.forEach(doc => (doc.presEaseFunc = activeItem.presEaseFunc)); + activeItem.presentation_easeFunc = activeItem.presentation_easeFunc === 'linear' ? 'ease' : 'linear'; + this.selectedArray.forEach(doc => { + doc.presentation_easeFunc = activeItem.presentation_easeFunc; + }); }; + setEaseFunc = (activeItem: Doc, easeFunc: string) => { + activeItem.presentation_easeFunc = easeFunc; + this.selectedArray.forEach(doc => { + doc.presentation_easeFunc = activeItem.presentation_easeFunc; + }); + }; + + @undoBatch + updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => + (all ? this.childDocs : this.selectedArray).forEach(doc => { + doc.presentation_effectDirection = effect; + }); + @undoBatch - updateEffectDirection = (effect: PresEffectDirection, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (doc.presentation_effectDirection = effect)); + updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => + (all ? this.childDocs : this.selectedArray).forEach(doc => { + bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect); + }); @undoBatch - updateEffect = (effect: PresEffect, bullet: boolean, all?: boolean) => (all ? this.childDocs : this.selectedArray).forEach(doc => (bullet ? (doc.presBulletEffect = effect) : (doc.presentation_effect = effect))); + updateEffectTiming = (activeItem: Doc, timing: SpringSettings) => { + activeItem.presentation_effectTiming = JSON.stringify(timing); + this.selectedArray.forEach(doc => { + doc.presentation_effectTiming = activeItem.presentation_effectTiming; + }); + }; static _sliderBatch: any; static endBatch = () => { PresBox._sliderBatch.end(); document.removeEventListener('pointerup', PresBox.endBatch, true); }; - public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => { - return ( - <input - type="range" - step={step} - min={min} - max={max} - value={value} - readOnly={true} - style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }} - className={`toolbar-slider ${active ? '' : 'none'}`} - onPointerDown={e => { - PresBox._sliderBatch = UndoManager.StartBatch('pres slider'); - document.addEventListener('pointerup', PresBox.endBatch, true); - e.stopPropagation(); - }} - onChange={e => { - e.stopPropagation(); - change(e.target.value); - }} - /> - ); - }; - + public static inputter = (min: string, step: string, max: string, value: number, active: boolean, change: (val: string) => void, hmargin?: number) => ( + <input + type="range" + step={step} + min={min} + max={max} + value={value} + readOnly + style={{ marginLeft: hmargin, marginRight: hmargin, width: `calc(100% - ${2 * (hmargin ?? 0)}px)`, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }} + className={`toolbar-slider ${active ? '' : 'none'}`} + onPointerDown={e => { + PresBox._sliderBatch = UndoManager.StartBatch('pres slider'); + document.addEventListener('pointerup', PresBox.endBatch, true); + e.stopPropagation(); + }} + onChange={e => { + e.stopPropagation(); + change(e.target.value); + }} + /> + ); + + // Applies the slide transiiton settings to all docs in the array @undoBatch applyTo = (array: Doc[]) => { this.updateMovement(this.activeItem.presentation_movement as PresMovement, true); this.updateEffect(this.activeItem.presentation_effect as PresEffect, false, true); this.updateEffect(this.activeItem.presBulletEffect as PresEffect, true, true); this.updateEffectDirection(this.activeItem.presentation_effectDirection as PresEffectDirection, true); - const { presentation_transition, presentation_duration, presentation_hideBefore, presentation_hideAfter } = this.activeItem; + // eslint-disable-next-line camelcase + const { presentation_transition: pt, presentation_duration: pd, presentation_hideBefore: ph, presentation_hideAfter: pa } = this.activeItem; array.forEach(curDoc => { - curDoc.presentation_transition = presentation_transition; - curDoc.presentation_duration = presentation_duration; - curDoc.presentation_hideBefore = presentation_hideBefore; - curDoc.presentation_hideAfter = presentation_hideAfter; + curDoc.presentation_transition = pt; + curDoc.presentation_duration = pd; + curDoc.presentation_hideBefore = ph; + curDoc.presentation_hideAfter = pa; }); }; @computed get visibilityDurationDropdown() { - const activeItem = this.activeItem; + const { activeItem } = this; if (activeItem && this.targetDoc) { const targetType = this.targetDoc.type; let duration = activeItem.presentation_duration ? NumCast(activeItem.presentation_duration) / 1000 : 0; if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); return ( - <div className="presBox-ribbon"> - <div className="ribbon-doubleButton"> - <Tooltip title={<div className="dash-tooltip">Hide before presented</div>}> - <div - className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`} - style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideBefore ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }} - onClick={() => this.updateHideBefore(activeItem)}> - Hide before - </div> - </Tooltip> - <Tooltip title={<div className="dash-tooltip">{'Hide while presented'}</div>}> - <div - className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`} - style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hide ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }} - onClick={() => this.updateHide(activeItem)}> - Hide - </div> - </Tooltip> - - <Tooltip title={<div className="dash-tooltip">{'Hide after presented'}</div>}> - <div - className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`} - style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presentation_hideAfter ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }} - onClick={() => this.updateHideAfter(activeItem)}> - Hide after - </div> - </Tooltip> - - <Tooltip title={<div className="dash-tooltip">{'Open in lightbox view'}</div>}> - <div - className="ribbon-toggle" - style={{ - border: `solid 1px ${SettingsManager.userColor}`, - color: SettingsManager.userColor, - background: activeItem.presentation_openInLightbox ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor, - }} - onClick={() => this.updateOpenDoc(activeItem)}> - Lightbox - </div> - </Tooltip> - <Tooltip title={<div className="dash-tooltip">Transition movement style</div>}> - <div - className="ribbon-toggle" - style={{ border: `solid 1px ${SettingsManager.userColor}`, color: SettingsManager.userColor, background: activeItem.presEaseFunc === 'ease' ? SettingsManager.userVariantColor : SettingsManager.userBackgroundColor }} - 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" style={{ border: `solid 1px ${SettingsManager.userColor}` }}> - <input className="presBox-input" type="number" readOnly={true} value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s + <div className="presBox-option-block"> + <div className="presBox-ribbon"> + <div className="presBox-toggles"> + <Tooltip title={<div className="dash-tooltip">Hide before presented</div>}> + <div + className={`ribbon-toggle ${activeItem.presentation_hideBefore ? 'active' : ''}`} + style={{ + border: `solid 1px ${SnappingManager.userColor}`, + color: SnappingManager.userColor, + background: activeItem.presentation_hideBefore ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, + }} + onClick={() => this.updateHideBefore(activeItem)}> + Hide before </div> - <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}> - <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'} /> + </Tooltip> + <Tooltip title={<div className="dash-tooltip">Hide while presented</div>}> + <div + className={`ribbon-toggle ${activeItem.presentation_hide ? 'active' : ''}`} + style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hide ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }} + onClick={() => this.updateHide(activeItem)}> + Hide + </div> + </Tooltip> + <Tooltip title={<div className="dash-tooltip">Hide after presented</div>}> + <div + className={`ribbon-toggle ${activeItem.presentation_hideAfter ? 'active' : ''}`} + style={{ border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, background: activeItem.presentation_hideAfter ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor }} + onClick={() => this.updateHideAfter(activeItem)}> + Hide after + </div> + </Tooltip> + + <Tooltip title={<div className="dash-tooltip">Open in lightbox view</div>}> + <div + className="ribbon-toggle" + style={{ + border: `solid 1px ${SnappingManager.userColor}`, + color: SnappingManager.userColor, + background: activeItem.presentation_openInLightbox ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, + }} + onClick={() => this.updateOpenDoc(activeItem)}> + Lightbox + </div> + </Tooltip> + </div> + {[DocumentType.AUDIO, DocumentType.VID].includes(targetType as any as DocumentType) ? null : ( + <> + <div className="ribbon-doubleButton"> + <div className="presBox-subheading">Slide Duration</div> + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}> + <input className="presBox-input" type="number" readOnly value={duration} onKeyDown={e => e.stopPropagation()} onChange={e => this.updateDurationTime(e.target.value)} /> s </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> - </> - )} + {PresBox.inputter('0.1', '0.1', '20', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)} + <div className="slider-headers" style={{ display: targetType === DocumentType.AUDIO ? 'none' : 'grid' }}> + <div className="slider-text">Short</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Long</div> + </div> + </> + )} + </div> </div> ); } + return undefined; } @computed get progressivizeDropdown() { - const activeItem = this.activeItem; + const { activeItem } = this; if (activeItem && this.targetDoc) { const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None; - const bulletEffect = (effect: PresEffect) => ( + const bulletEffect = (presEffect: PresEffect) => ( <div - className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`} + className={`presBox-dropdownOption ${activeItem.presentation_effect === presEffect || (presEffect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`} onPointerDown={StopEvent} - onClick={() => this.updateEffect(effect, true)}> - {effect} + onClick={() => this.updateEffect(presEffect, true)}> + {presEffect} </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, border: `solid 1px ${SettingsManager.userColor}` }} - type="checkbox" - onChange={() => { - activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined; - activeItem.presentation_hideBefore = activeItem.presentation_indexed !== undefined; - const tagDoc = PresBox.targetRenderedDoc(this.activeItem); - const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type; - activeItem.presentation_indexedStart = type === DocumentType.COL ? 1 : 0; - // a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized. - // to avoid creating a new slide to correspond to each of the target's data list, we create a computedField to refernce the target's data list. - let dataField = Doc.LayoutFieldKey(tagDoc); - if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField = dataField + '_annotations'; - - if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`); - else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`); - }} - checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? true : false} - /> - </div> - <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> - <div className="presBox-subheading">Progressivize First Bullet</div> - <input - className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }} - type="checkbox" - onChange={() => (activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1)} - checked={!NumCast(activeItem.presentation_indexedStart)} - /> - </div> - <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> - <div className="presBox-subheading">Expand Current Bullet</div> - <input - className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }} - type="checkbox" - onChange={() => (activeItem.presBulletExpand = !activeItem.presBulletExpand)} - checked={BoolCast(activeItem.presBulletExpand)} - /> - </div> + <div className="presBox-option-block"> + <div className="presBox-ribbon"> + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Progressivize Collection</div> + <input + className="presBox-checkbox" + style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }} + type="checkbox" + onChange={() => { + activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined; + activeItem.presentation_hideBefore = activeItem.presentation_indexed !== undefined; + const tagDoc = PresBox.targetRenderedDoc(this.activeItem); + const type = DocCast(tagDoc?.annotationOn)?.type ?? tagDoc.type; + activeItem.presentation_indexedStart = type === DocumentType.COL ? 1 : 0; + // a progressivized slide doesn't have sub-slides, but rather iterates over the data list of the target being progressivized. + // to avoid creating a new slide to correspond to each of the target's data list, we create a computedField to refernce the target's data list. + let dataField = Doc.LayoutFieldKey(tagDoc); + if (Cast(tagDoc[dataField], listSpec(Doc), null)?.filter(d => d instanceof Doc) === undefined) dataField += '_annotations'; + + if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`); + else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`); + }} + checked={Cast(activeItem.presentation_indexed, 'number', null) !== undefined} + /> + </div> + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Progressivize First Bullet</div> + <input + className="presBox-checkbox" + style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }} + type="checkbox" + onChange={() => { + activeItem.presentation_indexedStart = activeItem.presentation_indexedStart ? 0 : 1; + }} + checked={!NumCast(activeItem.presentation_indexedStart)} + /> + </div> + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Expand Current Bullet</div> + <input + className="presBox-checkbox" + style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }} + type="checkbox" + onChange={() => { + activeItem.presBulletExpand = !activeItem.presBulletExpand; + }} + checked={BoolCast(activeItem.presBulletExpand)} + /> + </div> - <div className="ribbon-box"> - Bullet Effect - <div - className="presBox-dropdown" - onClick={action(e => { - e.stopPropagation(); - this._openBulletEffectDropdown = !this._openBulletEffectDropdown; - })} - style={{ - color: SettingsManager.userColor, - background: SettingsManager.userVariantColor, - borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, - border: this._openBulletEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`, - }}> - {effect?.toString()} - <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> + <div className="ribbon-box"> + Bullet Effect <div - className={'presBox-dropdownOptions'} - style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SettingsManager.userColor, background: SettingsManager.userBackgroundColor }} - onPointerDown={e => e.stopPropagation()}> - {Object.values(PresEffect) - .filter(v => isNaN(Number(v))) - .map(effect => bulletEffect(effect))} + className="presBox-dropdown" + onClick={action(e => { + e.stopPropagation(); + this._openBulletEffectDropdown = !this._openBulletEffectDropdown; + })} + style={{ + color: SnappingManager.userColor, + background: SnappingManager.userVariantColor, + borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, + border: this._openBulletEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`, + }}> + {effect?.toString()} + <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openBulletEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon="angle-down" /> + <div + className="presBox-dropdownOptions" + style={{ display: this._openBulletEffectDropdown ? 'grid' : 'none', color: SnappingManager.userColor, background: SnappingManager.userBackgroundColor }} + onPointerDown={e => e.stopPropagation()}> + {Object.values(PresEffect) + .filter(v => isNaN(Number(v))) + .map(pEffect => bulletEffect(pEffect))} + </div> </div> </div> </div> @@ -1682,215 +1821,460 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } return null; } + + @computed get gptDropdown() { + return <div />; + } + @computed get transitionDropdown() { - const activeItem = this.activeItem; - const preseEffect = (effect: PresEffect) => ( - <div - className={`presBox-dropdownOption ${activeItem.presentation_effect === effect || (effect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`} - onPointerDown={StopEvent} - onClick={() => this.updateEffect(effect, false)}> - {effect} - </div> - ); - const presMovement = (movement: PresMovement) => ( - <div className={`presBox-dropdownOption ${activeItem.presentation_movement === movement ? 'active' : ''}`} onPointerDown={StopEvent} onClick={() => this.updateMovement(movement)}> - {movement} - </div> - ); - const presDirection = (direction: PresEffectDirection, icon: string, gridColumn: number, gridRow: number, opts: object) => { - const color = activeItem.presentation_effectDirection === direction || (direction === PresEffectDirection.Center && !activeItem.presentation_effectDirection) ? SettingsManager.userVariantColor : SettingsManager.userColor; - return ( - <Tooltip title={<div className="dash-tooltip">{direction}</div>}> - <div - style={{ ...opts, border: direction === PresEffectDirection.Center ? `solid 2px ${color}` : undefined, borderRadius: '100%', cursor: 'pointer', gridColumn, gridRow, justifySelf: 'center', color }} - onClick={() => this.updateEffectDirection(direction)}> - {icon ? <FontAwesomeIcon icon={icon as any} /> : null} - </div> - </Tooltip> - ); - }; + const { activeItem } = this; + // Retrieving spring timing properties + const timing = StrCast(activeItem.presentation_effectTiming); + let timingConfig: SpringSettings | undefined; + if (timing) { + timingConfig = JSON.parse(timing); + } + + if (!timingConfig) { + timingConfig = { + type: SpringType.GENTLE, + stiffness: 100, + damping: 15, + mass: 1, + }; + } + if (activeItem && this.targetDoc) { const transitionSpeed = activeItem.presentation_transition ? NumCast(activeItem.presentation_transition) / 1000 : 0.5; const zoom = NumCast(activeItem.config_zoom, 1) * 100; - const effect = activeItem.presentation_effect ? activeItem.presentation_effect : PresMovement.None; + const effect = StrCast(activeItem.presentation_effect) ? (StrCast(activeItem.presentation_effect) as any as PresEffect) : PresEffect.None; + const direction = StrCast(activeItem.presentation_effectDirection) as PresEffectDirection; + return ( - <div - className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`} - onPointerDown={StopEvent} - onPointerUp={StopEvent} - onClick={action(e => { - e.stopPropagation(); - this._openMovementDropdown = false; - this._openEffectDropdown = false; - this._openBulletEffectDropdown = false; - })}> - <div className="ribbon-box"> - Movement - <div - className="presBox-dropdown" - onClick={action(e => { - e.stopPropagation(); - this._openMovementDropdown = !this._openMovementDropdown; - })} - style={{ - color: SettingsManager.userColor, - background: SettingsManager.userVariantColor, - borderBottomLeftRadius: this._openMovementDropdown ? 0 : 5, - border: this._openMovementDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`, - }}> - {this.movementName(activeItem)} - <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={{ - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - display: this._openMovementDropdown ? 'grid' : 'none', - }}> - {presMovement(PresMovement.None)} - {presMovement(PresMovement.Center)} - {presMovement(PresMovement.Zoom)} - {presMovement(PresMovement.Pan)} - {presMovement(PresMovement.Jump)} + <> + {/* This chatbox is for customizing the properties of trails, like transition time, movement type (zoom, pan) using GPT */} + <div className="presBox-gpt-chat"> + <span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> + Customize Slide Properties{' '} + <div className="propertiesView-info" onClick={() => window.open('https://brown-dash.github.io/Dash-Documentation/features/trails/#slide-customization')}> + <IconButton icon={<FontAwesomeIcon icon="info-circle" />} color={SnappingManager.userColor} /> </div> - </div> - <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? 'inline-flex' : 'none' }}> - <div className="presBox-subheading">Zoom (% screen filled)</div> - <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}> - <input className="presBox-input" type="number" readOnly={true} value={zoom} onChange={e => this.updateZoom(e.target.value)} />% + </span> + <div className="pres-chat"> + <div className="pres-chatbox-container"> + <ReactTextareaAutosize + placeholder="Describe how you would like to modify the slide properties." + className="pres-chatbox" + value={this.chatInput} + onChange={e => { + this.setChatInput(e.target.value); + }} + onKeyDown={e => { + this.stopDictation(); + e.stopPropagation(); + }} + /> + <IconButton + type={Type.TERT} + color={this.isRecording ? '#2bcaff' : SnappingManager.userVariantColor} + tooltip="Record" + icon={<BiMicrophone size="16px" />} + onClick={() => { + if (!this.isRecording) { + this.recordDictation(); + } else { + this.stopDictation(); + } + }} + /> </div> - <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}> - <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), 0.1)}> - <FontAwesomeIcon icon={'caret-up'} /> + <Button + style={{ alignSelf: 'flex-end' }} + text="Send" + type={Type.TERT} + icon={this.isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />} + iconPlacement="right" + color={SnappingManager.userVariantColor} + onClick={() => { + this.stopDictation(); + this.customizeWithGPT(this.chatInput); + }} + /> + </div> + </div> + {/* Movement */} + <div + className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`} + onPointerDown={StopEvent} + onPointerUp={StopEvent} + onClick={action(e => { + e.stopPropagation(); + this._openMovementDropdown = false; + this._openEffectDropdown = false; + this._openBulletEffectDropdown = false; + })}> + <div + className="presBox-option-block" + // style={{ padding: '16px' }} + > + Movement + <Dropdown + color={SnappingManager.userColor} + formLabel="Movement" + closeOnSelect + items={movementItems} + selectedVal={this.movementName(activeItem)} + setSelectedVal={val => { + this.updateMovement(val as PresMovement); + }} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + /> + <div className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? 'inline-flex' : 'none' }}> + <div className="presBox-subheading">Zoom (% screen filled)</div> + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}> + <input className="presBox-input" readOnly type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} />% </div> - <div className="ribbon-propertyUpDownItem" onClick={() => this.updateZoom(String(zoom), -0.1)}> - <FontAwesomeIcon icon={'caret-down'} /> + </div> + {PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)} + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Transition Time</div> + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}> + <input className="presBox-input" type="number" readOnly value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s </div> </div> - </div> - {PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)} - <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> - <div className="presBox-subheading">Transition Time</div> - <div className="ribbon-property" style={{ border: `solid 1px ${SettingsManager.userColor}` }}> - <input className="presBox-input" type="number" readOnly={true} value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> s + {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.updateTransitionTime)} + <div className="slider-headers"> + <div className="slider-text">Fast</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Slow</div> </div> - <div className="ribbon-propertyUpDown" style={{ color: SettingsManager.userBackgroundColor, background: SettingsManager.userColor }}> - <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), 1000)}> - <FontAwesomeIcon icon={'caret-up'} /> - </div> - <div className="ribbon-propertyUpDownItem" onClick={() => this.updateTransitionTime(String(transitionSpeed), -1000)}> - <FontAwesomeIcon icon={'caret-down'} /> - </div> + {/* Easing function */} + <Dropdown + color={SnappingManager.userColor} + formLabel="Easing Function" + closeOnSelect + items={easeItems} + selectedVal={this.activeItem.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'} + setSelectedVal={val => { + if (typeof val === 'string') { + if (val !== 'custom') { + this.setEaseFunc(this.activeItem, val); + } else { + this.setBezierEditorVisibility(true); + this.setEaseFunc(this.activeItem, TIMING_DEFAULT_MAPPINGS.ease); + } + } + }} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + /> + {/* Custom */} + <div + className="presBox-show-hide-dropdown" + style={{ alignSelf: 'flex-start' }} + onClick={e => { + e.stopPropagation(); + this.setBezierEditorVisibility(!this.showBezierEditor); + }}> + {`${this.showBezierEditor ? 'Hide' : 'Show'} Timing Editor`} + <FontAwesomeIcon icon={this.showBezierEditor ? 'chevron-up' : 'chevron-down'} /> </div> </div> - {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"> + + {/* Cubic bezier editor */} + {this.showBezierEditor && ( + <div className="presBox-option-block" style={{ paddingTop: 0 }}> + <p className="presBox-submenu-label" style={{ alignSelf: 'flex-start' }}> + Custom Timing Function + </p> + <CubicBezierEditor setFunc={this.setBezierControlPoints} currPoints={this.currCPoints} /> + </div> + )} + + {/* This chatbox is for getting slide effect transition suggestions from gpt and visualizing them */} + <div className="presBox-gpt-chat"> Effects - <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> - <div className="presBox-subheading">Play Audio Annotation</div> - <input - className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }} - type="checkbox" - onChange={() => (activeItem.presPlayAudio = !BoolCast(activeItem.presPlayAudio))} - checked={BoolCast(activeItem.presPlayAudio)} + <div className="pres-chat"> + <div className="pres-chatbox-container"> + <ReactTextareaAutosize + placeholder="Customize prompt for effect suggestions. Leave blank for random results." + className="pres-chatbox" + value={this.animationChat} + onChange={e => { + this.setAnimationChat(e.target.value); + }} + onKeyDown={e => { + this.stopDictation(); + e.stopPropagation(); + }} + /> + </div> + <Button + style={{ alignSelf: 'flex-end' }} + text="Send" + type={Type.TERT} + icon={this.isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />} + iconPlacement="right" + color={SnappingManager.userVariantColor} + onClick={this.customizeAnimations} /> </div> - <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> - <div className="presBox-subheading">Zoom Text Selections</div> - <input - className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SettingsManager.userColor}` }} - type="checkbox" - onChange={() => (activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText))} - checked={BoolCast(activeItem.presentation_zoomText)} + </div> + + <div + className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`} + onPointerDown={StopEvent} + onPointerUp={StopEvent} + onClick={action(e => { + e.stopPropagation(); + this._openMovementDropdown = false; + this._openEffectDropdown = false; + this._openBulletEffectDropdown = false; + })}> + <div className="presBox-option-block"> + Click on a box to apply the effect. + <div className="presBox-option-block presBox-option-center"> + {/* Preview Animations */} + <div className="presBox-effects"> + {this.generatedAnimations.map((elem, i) => ( + <div + // eslint-disable-next-line react/no-array-index-key + key={i} + className="presBox-effect-container" + onClick={() => { + this.updateEffect(elem.effect, false); + this.updateEffectDirection(elem.direction); + this.updateEffectTiming(this.activeItem, { + type: SpringType.CUSTOM, + stiffness: elem.stiffness, + damping: elem.damping, + mass: elem.mass, + }); + }}> + <SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite> + <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} /> + </SlideEffect> + </div> + ))} + </div> + </div> + {/* Effect dropdown */} + <Dropdown + color={SnappingManager.userColor} + formLabel="Slide Effect" + closeOnSelect + items={effectItems} + selectedVal={effect?.toString()} + setSelectedVal={val => { + this.updateEffect(val as PresEffect, false); + // set default spring options for that effect + this.updateEffectTiming(activeItem, presEffectDefaultTimings[val as keyof typeof presEffectDefaultTimings]); + }} + dropdownType={DropdownType.SELECT} + type={Type.TERT} /> + {/* Effect direction */} + {/* Only applies to certain effects */} + {(effect === PresEffect.Flip || effect === PresEffect.Bounce || effect === PresEffect.Roll) && ( + <> + <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> + <div className="presBox-subheading">Effect direction</div> + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}` }}> + {StrCast(this.activeItem.presentation_effectDirection)} + </div> + </div> + <div className="presBox-icon-list"> + <IconButton + type={Type.TERT} + color={activeItem.presentation_effectDirection === PresEffectDirection.Left ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor} + tooltip="Left" + icon={<FaArrowRight size="16px" />} + onClick={() => this.updateEffectDirection(PresEffectDirection.Left)} + /> + <IconButton + type={Type.TERT} + color={activeItem.presentation_effectDirection === PresEffectDirection.Right ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor} + tooltip="Right" + icon={<FaArrowLeft size="16px" />} + onClick={() => this.updateEffectDirection(PresEffectDirection.Right)} + /> + {effect !== PresEffect.Roll && ( + <> + <IconButton + type={Type.TERT} + color={activeItem.presentation_effectDirection === PresEffectDirection.Top ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor} + tooltip="Top" + icon={<FaArrowDown size="16px" />} + onClick={() => this.updateEffectDirection(PresEffectDirection.Top)} + /> + <IconButton + type={Type.TERT} + color={activeItem.presentation_effectDirection === PresEffectDirection.Bottom ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor} + tooltip="Bottom" + icon={<FaArrowUp size="16px" />} + onClick={() => this.updateEffectDirection(PresEffectDirection.Bottom)} + /> + </> + )} + </div> + </> + )} + {/* Spring settings */} + {/* No spring settings for jackinthebox (lightspeed) */} + {effect !== PresEffect.Lightspeed && ( + <> + <Dropdown + color={SnappingManager.userColor} + formLabel="Effect Timing" + closeOnSelect + items={effectTimings} + selectedVal={timingConfig.type} + setSelectedVal={val => { + this.updateEffectTiming(activeItem, { + type: val as SpringType, + ...springMappings[val], + }); + }} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + /> + <div + className="presBox-show-hide-dropdown" + onClick={e => { + e.stopPropagation(); + this.setSpringEditorVisibility(!this.showSpringEditor); + }}> + {`${this.showSpringEditor ? 'Hide' : 'Show'} Spring Settings`} + <FontAwesomeIcon icon={this.showSpringEditor ? 'chevron-up' : 'chevron-down'} /> + </div> + {this.showSpringEditor && ( + <> + <div>Tension</div> + <div + onPointerDown={e => { + e.stopPropagation(); + }}> + <Slider + min={1} + max={1000} + step={5} + size="small" + value={timingConfig.stiffness} + onChange={(e, val) => { + if (!timingConfig) return; + this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number }); + }} + valueLabelDisplay="auto" + /> + </div> + <div>Damping</div> + <div + onPointerDown={e => { + e.stopPropagation(); + }}> + <Slider + min={1} + max={100} + step={1} + size="small" + value={timingConfig.damping} + onChange={(e, val) => { + if (!timingConfig) return; + this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, damping: val as number }); + }} + valueLabelDisplay="auto" + /> + </div> + <div>Mass</div> + <div + onPointerDown={e => { + e.stopPropagation(); + }}> + <Slider + min={1} + max={10} + step={1} + size="small" + value={timingConfig.mass} + onChange={(e, val) => { + if (!timingConfig) return; + this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, mass: val as number }); + }} + valueLabelDisplay="auto" + /> + </div> + Preview Effect + <div className="presBox-option-block presBox-option-center"> + <div className="presBox-effect-container"> + <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig} infinite> + <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} /> + </SlideEffect> + </div> + </div> + </> + )} + </> + )} </div> - <div - className="presBox-dropdown" - onClick={action(e => { - e.stopPropagation(); - this._openEffectDropdown = !this._openEffectDropdown; - })} - style={{ - color: SettingsManager.userColor, - background: SettingsManager.userVariantColor, - borderBottomLeftRadius: this._openEffectDropdown ? 0 : 5, - border: this._openEffectDropdown ? `solid 2px ${SettingsManager.userVariantColor}` : `solid 1px ${SettingsManager.userColor}`, - }}> - {effect?.toString()} - <FontAwesomeIcon className="presBox-dropdownIcon" style={{ gridColumn: 2, color: this._openEffectDropdown ? Colors.MEDIUM_BLUE : 'black' }} icon={'angle-down'} /> - <div - className="presBox-dropdownOptions" - id={'presBoxMovementDropdown'} - style={{ - color: SettingsManager.userColor, - background: SettingsManager.userBackgroundColor, - display: this._openEffectDropdown ? 'grid' : 'none', + + {/* Toggles */} + <div className="presBox-option-block"> + <Toggle + formLabel="Play Audio Annotation" + toggleType={ToggleType.SWITCH} + toggleStatus={BoolCast(activeItem.presentation_playAudio)} + onClick={() => { + activeItem.presentation_playAudio = !BoolCast(activeItem.presentation_playAudio); }} - onPointerDown={e => e.stopPropagation()}> - {Object.values(PresEffect) - .filter(v => isNaN(Number(v))) - .map(effect => preseEffect(effect))} - </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" style={{ border: `solid 1px ${SettingsManager.userColor}` }}> - {StrCast(this.activeItem.presentation_effectDirection)} - </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 + color={SnappingManager.userColor} + /> + <Toggle + formLabel="Zoom Text Selections" + toggleType={ToggleType.SWITCH} + toggleStatus={BoolCast(activeItem.presentation_zoomText)} + onClick={() => { + activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText); + }} + color={SnappingManager.userColor} + /> + <Button text="Apply to all" type={Type.TERT} color={SnappingManager.userVariantColor} onClick={() => this.applyTo(this.childDocs)} /> </div> </div> - </div> + </> ); } + return undefined; } @computed get mediaOptionsDropdown() { - const activeItem = this.activeItem; + const { activeItem } = this; if (activeItem && this.targetDoc) { const renderTarget = PresBox.targetRenderedDoc(this.activeItem); const clipStart = NumCast(renderTarget.clipStart); const clipEnd = NumCast(renderTarget.clipEnd, clipStart + NumCast(renderTarget[Doc.LayoutFieldKey(renderTarget) + '_duration'])); - const config_clipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd); + const configClipEnd = NumCast(activeItem.config_clipEnd) < NumCast(activeItem.config_clipStart) ? clipEnd - clipStart : NumCast(activeItem.config_clipEnd); return ( - <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div 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'}> + Start & End Time + <div className="slider-headers"> <div className="slider-block"> <div className="slider-text" style={{ fontWeight: 500 }}> Start time (s) </div> - <div id="startTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}> + <div id="startTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}> <input className="presBox-input" style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} type="number" - readOnly={true} + readOnly value={NumCast(activeItem.config_clipStart).toFixed(2)} onKeyDown={e => e.stopPropagation()} - onChange={action(e => (activeItem.config_clipStart = Number(e.target.value)))} + onChange={action(e => { + activeItem.config_clipStart = Number(e.target.value); + })} /> </div> </div> @@ -1898,23 +2282,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="slider-text" style={{ fontWeight: 500 }}> Duration (s) </div> - <div className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}> - {Math.round((config_clipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10} + <div className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}> + {Math.round((configClipEnd - NumCast(activeItem.config_clipStart)) * 10) / 10} </div> </div> <div className="slider-block"> <div className="slider-text" style={{ fontWeight: 500 }}> End time (s) </div> - <div id="endTime" className="slider-number" style={{ color: SettingsManager.userColor, backgroundColor: SettingsManager.userBackgroundColor }}> + <div id="endTime" className="slider-number" style={{ color: SnappingManager.userColor, backgroundColor: SnappingManager.userBackgroundColor }}> <input className="presBox-input" onKeyDown={e => e.stopPropagation()} style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} type="number" - readOnly={true} - value={config_clipEnd.toFixed(2)} - onChange={action(e => (activeItem.config_clipEnd = Number(e.target.value)))} + readOnly + value={configClipEnd.toFixed(2)} + onChange={action(e => { + activeItem.config_clipEnd = Number(e.target.value); + })} /> </div> </div> @@ -1925,15 +2311,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { step="0.1" min={clipStart} max={clipEnd} - value={config_clipEnd} - style={{ gridColumn: 1, gridRow: 1, background: SettingsManager.userColor, color: SettingsManager.userVariantColor }} + value={configClipEnd} + style={{ gridColumn: 1, gridRow: 1, background: SnappingManager.userColor, color: SnappingManager.userVariantColor }} className={`toolbar-slider ${'end'}`} id="toolbar-slider" onPointerDown={e => { this._batch = UndoManager.StartBatch('config_clipEnd'); const endBlock = document.getElementById('endTime'); if (endBlock) { - endBlock.style.backgroundColor = SettingsManager.userVariantColor; + endBlock.style.backgroundColor = SnappingManager.userVariantColor ?? ''; } e.stopPropagation(); }} @@ -1941,7 +2327,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._batch?.end(); const endBlock = document.getElementById('endTime'); if (endBlock) { - endBlock.style.backgroundColor = SettingsManager.userBackgroundColor; + endBlock.style.backgroundColor = SnappingManager.userBackgroundColor ?? ''; } }} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { @@ -1962,7 +2348,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._batch = UndoManager.StartBatch('config_clipStart'); const startBlock = document.getElementById('startTime'); if (startBlock) { - startBlock.style.backgroundColor = SettingsManager.userVariantColor; + startBlock.style.backgroundColor = SnappingManager.userVariantColor ?? ''; } e.stopPropagation(); }} @@ -1970,7 +2356,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._batch?.end(); const startBlock = document.getElementById('startTime'); if (startBlock) { - startBlock.style.backgroundColor = SettingsManager.userBackgroundColor; + startBlock.style.backgroundColor = SnappingManager.userBackgroundColor ?? ''; } }} onChange={(e: React.ChangeEvent<HTMLInputElement>) => { @@ -1981,7 +2367,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> <div className="slider-headers"> <div className="slider-text">{clipStart.toFixed(2)} s</div> - <div className="slider-text"></div> + <div className="slider-text" /> <div className="slider-text">{clipEnd.toFixed(2)} s</div> </div> </div> @@ -1993,8 +2379,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <input className="presBox-checkbox" type="checkbox" - style={{ border: `solid 1px ${SettingsManager.userColor}` }} - onChange={() => (activeItem.presentation_mediaStart = 'manual')} + style={{ border: `solid 1px ${SnappingManager.userColor}` }} + onChange={() => { + activeItem.presentation_mediaStart = 'manual'; + }} checked={activeItem.presentation_mediaStart === 'manual'} /> <div>On click</div> @@ -2002,9 +2390,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="checkbox-container"> <input className="presBox-checkbox" - style={{ border: `solid 1px ${SettingsManager.userColor}` }} + style={{ border: `solid 1px ${SnappingManager.userColor}` }} type="checkbox" - onChange={() => (activeItem.presentation_mediaStart = 'auto')} + onChange={() => { + activeItem.presentation_mediaStart = 'auto'; + }} checked={activeItem.presentation_mediaStart === 'auto'} /> <div>Automatically</div> @@ -2016,8 +2406,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <input className="presBox-checkbox" type="checkbox" - style={{ border: `solid 1px ${SettingsManager.userColor}` }} - onChange={() => (activeItem.presentation_mediaStop = 'manual')} + style={{ border: `solid 1px ${SnappingManager.userColor}` }} + onChange={() => { + activeItem.presentation_mediaStop = 'manual'; + }} checked={activeItem.presentation_mediaStop === 'manual'} /> <div>At media end time</div> @@ -2026,35 +2418,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <input className="presBox-checkbox" type="checkbox" - style={{ border: `solid 1px ${SettingsManager.userColor}` }} - onChange={() => (activeItem.presentation_mediaStop = 'auto')} + style={{ border: `solid 1px ${SnappingManager.userColor}` }} + onChange={() => { + activeItem.presentation_mediaStop = 'auto'; + }} checked={activeItem.presentation_mediaStop === 'auto'} /> <div>On slide change</div> </div> - {/* <div className="checkbox-container"> - <input className="presBox-checkbox" - type="checkbox" - onChange={() => activeItem.mediaStop = "afterSlide"} - checked={activeItem.mediaStop === "afterSlide"} - /> - <div className="checkbox-dropdown"> - After chosen slide - <select className="presBox-viewPicker" - style={{ opacity: activeItem.mediaStop === "afterSlide" && this.itemIndex !== this.childDocs.length - 1 ? 1 : 0.3 }} - onPointerDown={e => e.stopPropagation()} - onChange={this.mediaStopChanged} - value={mediaStopDocStr}> - {this.mediaStopSlides} - </select> - </div> - </div> */} </div> </div> </div> </div> ); } + return undefined; } @computed get newDocumentToolbarDropdown() { return ( @@ -2118,9 +2496,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @computed get newDocumentDropdown() { return ( - <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="presBox-ribbon" onClick={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="ribbon-box"> - Slide Title: <br></br> + Slide Title: <br /> <input className="ribbon-textInput" placeholder="..." @@ -2129,16 +2507,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { onChange={e => { e.stopPropagation(); e.preventDefault(); - runInAction(() => (this.title = e.target.value)); - }}></input> + runInAction(() => { + this.title = e.target.value; + }); + }} + /> </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))}> + <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))}> + <div + title="Freeform" + className="ribbon-toggle" + style={{ background: this.addFreeform ? Colors.LIGHT_BLUE : '' }} + onClick={action(() => { + this.addFreeform = !this.addFreeform; + })}> Freeform </div> </div> @@ -2146,23 +2539,49 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <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="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="layout" + style={{ border: this.layout === 'header' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'header'; + })}> <div className="title" style={{ alignSelf: 'center', fontSize: 10 }}> Section header </div> </div> - <div className="layout" style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'content'))}> + <div + className="layout" + style={{ border: this.layout === 'content' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} + onClick={action(() => { + this.layout = 'content'; + })}> <div className="title" style={{ alignSelf: 'center' }}> Title </div> <div className="content">Text goes here</div> </div> - <div className="layout" style={{ border: this.layout === 'twoColumns' ? `solid 2px ${Colors.MEDIUM_BLUE}` : '' }} onClick={action(() => (this.layout = 'twoColumns'))}> + <div + className="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> @@ -2174,8 +2593,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> </div> </div> - <div className="open-layout" onClick={action(() => (this.openLayouts = !this.openLayouts))}> - <FontAwesomeIcon style={{ transition: 'all 0.3s', transform: this.openLayouts ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'caret-down'} size={'lg'} /> + <div + 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"> @@ -2190,17 +2613,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } createNewSlide = (layout?: string, title?: string, freeform?: boolean) => { - let doc = undefined; + let doc; if (layout) doc = this.createTemplate(layout); if (freeform && layout) doc = this.createTemplate(layout, title); if (!freeform && !layout) doc = Docs.Create.TextDocument('', { _nativeWidth: 400, _width: 225, title: title }); if (doc) { - const tabMap = CollectionDockingView.Instance?.tabMap; - const tab = tabMap && Array.from(tabMap).find(tab => tab.DashDoc.type === DocumentType.COL)?.DashDoc; - const presCollection = DocumentManager.GetContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc ?? tab; + const docTab = PresBox._getTabDocs().find(tdoc => tdoc.type === DocumentType.COL); + const presCollection = DocCast(DocumentView.getContextPath(this.activeItem).reverse().lastElement().presentation_targetDoc, docTab); const data = Cast(presCollection?.data, listSpec(Doc)); - const config_data = Cast(this.Document.data, listSpec(Doc)); - if (data && config_data) { + const configData = Cast(this.Document.data, listSpec(Doc)); + if (data && configData) { data.push(doc); this._props.pinToPres(doc, {}); this.gotoDocument(this.childDocs.length, this.activeItem); @@ -2222,12 +2644,14 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _text_fontSize: '14pt' }); // prettier-ignore switch (layout) { - case 'blank': return Docs.Create.FreeformDocument([], { title: input ? input : 'Blank slide', _width: 400, _height: 225, x, y }); - case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input ? input : 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y }); - case 'header': return Docs.Create.FreeformDocument([header()], { title: input ? input : 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y }); - case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input ? input : 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y }); - case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input ? input : 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y }) + case 'blank': return Docs.Create.FreeformDocument([], { title: input || 'Blank slide', _width: 400, _height: 225, x, y }); + case 'title': return Docs.Create.FreeformDocument([title(), subtitle()], { title: input || 'Title slide', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y }); + case 'header': return Docs.Create.FreeformDocument([header()], { title: input || 'Section header', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y }); + case 'content': return Docs.Create.FreeformDocument([contentTitle(), content()], { title: input || 'Title and content', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y }); + case 'twoColumns': return Docs.Create.FreeformDocument([contentTitle(), content1(), content2()], { title: input || 'Title and two columns', _width: 400, _height: 225, _freeform_fitContentsToBox: true, x, y }) + default: } + return undefined; }; // Dropdown that appears when the user wants to begin presenting (either minimize or sidebar view) @@ -2270,17 +2694,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } @action - toggleProperties = () => (SettingsManager.Instance.propertiesWidth = SettingsManager.Instance.propertiesWidth > 0 ? 0 : 250); + toggleProperties = () => { + SnappingManager.SetPropertiesWidth(SnappingManager.PropertiesWidth > 0 ? 0 : 250); + }; + + @action + openProperties = () => { + // need to also focus slide + SnappingManager.SetPropertiesWidth(250); + }; @computed get toolbar() { - const propIcon = SettingsManager.Instance.propertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; - const propTitle = SettingsManager.Instance.propertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; + const propIcon = SnappingManager.PropertiesWidth > 0 ? 'angle-double-right' : 'angle-double-left'; + const propTitle = SnappingManager.PropertiesWidth > 0 ? 'Close Presentation Panel' : 'Open Presentation Panel'; const mode = StrCast(this.Document._type_collection) as CollectionViewType; const isMini: boolean = this.toolbarWidth <= 100; - const activeColor = SettingsManager.userVariantColor; - const inactiveColor = lightOrDark(SettingsManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SettingsManager.userBackgroundColor; + const activeColor = SnappingManager.userVariantColor; + const inactiveColor = lightOrDark(SnappingManager.userBackgroundColor) === Colors.WHITE ? Colors.WHITE : SnappingManager.userBackgroundColor; return mode === CollectionViewType.Carousel3D || Doc.IsInMyOverlay(this.Document) ? null : ( - <div id="toolbarContainer" className={'presBox-toolbar'}> + <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"} /> @@ -2290,7 +2722,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? Colors.MEDIUM_BLUE : 'white', width: isMini ? '100%' : undefined }} className="toolbar-button" onClick={this.childDocs.length > 1 ? () => this.togglePath() : undefined}> - <FontAwesomeIcon icon={'exchange-alt'} /> + <FontAwesomeIcon icon="exchange-alt" /> </div> </Tooltip> {isMini ? null : ( @@ -2298,12 +2730,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="toolbar-divider" /> <Tooltip title={<div className="dash-tooltip">{this._presKeyEvents ? 'Keys are active' : 'Keys are not active - click anywhere on the presentation trail to activate keys'}</div>}> <div className="toolbar-button" style={{ cursor: this._presKeyEvents ? 'default' : 'pointer', position: 'absolute', right: 30, fontSize: 16 }}> - <FontAwesomeIcon className={'toolbar-thumbtack'} icon={'keyboard'} style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} /> + <FontAwesomeIcon className="toolbar-thumbtack" icon="keyboard" style={{ color: this._presKeyEvents ? activeColor : inactiveColor }} /> </div> </Tooltip> <Tooltip title={<div className="dash-tooltip">{propTitle}</div>}> <div className="toolbar-button" style={{ position: 'absolute', right: 4, fontSize: 16 }} onClick={this.toggleProperties}> - <FontAwesomeIcon className={'toolbar-thumbtack'} icon={propIcon} style={{ color: SettingsManager.Instance.propertiesWidth > 0 ? activeColor : inactiveColor }} /> + <FontAwesomeIcon className="toolbar-thumbtack" icon={propIcon} style={{ color: SnappingManager.PropertiesWidth > 0 ? activeColor : inactiveColor }} /> </div> </Tooltip> </> @@ -2348,7 +2780,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.gotoDocument(this.itemIndex, this.activeItem); } })}> - <FontAwesomeIcon icon={'play-circle'} /> + <FontAwesomeIcon icon="play-circle" /> <div style={{ display: this._props.PanelWidth() > 200 ? 'inline-flex' : 'none' }}> Present</div> </div> {mode === CollectionViewType.Carousel3D || isMini ? null : ( @@ -2357,7 +2789,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { onClick={action(() => { if (this.childDocs.length) this._presentTools = !this._presentTools; })}> - <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon={'angle-down'} /> + <FontAwesomeIcon className="dropdown" style={{ margin: 0, transform: this._presentTools ? 'rotate(180deg)' : 'rotate(0deg)' }} icon="angle-down" /> {this.presentDropdown} </div> )} @@ -2378,12 +2810,24 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { // Case 1: There are still other frames and should go through all frames before going to next slide return ( <div className="presPanelOverlay" style={{ display: this.layoutDoc.presentation_status !== 'edit' ? 'inline-flex' : 'none' }}> - <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}> + <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'} /> + 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" /> @@ -2408,7 +2852,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { false ) }> - <FontAwesomeIcon icon={'arrow-left'} /> + <FontAwesomeIcon icon="arrow-left" /> </div> <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}> <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, () => this.startOrPause(true), false, false)}> @@ -2436,10 +2880,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { false ) }> - <FontAwesomeIcon icon={'arrow-right'} /> + <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-divider" /> + <Tooltip title={<div className="dash-tooltip">Click to return to 1st slide</div>}> <div className="presPanel-button" style={{ border: 'solid 1px white' }} @@ -2463,7 +2907,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { {inOverlay ? '' : 'Slide'} {this.itemIndex + 1} {this.activeItem?.presentation_indexed !== undefined ? `(${this.activeItem.presentation_indexed}/${this.progressivizedItems(this.activeItem)?.length})` : ''} / {this.childDocs.length} </div> - <div className="presPanel-divider"></div> + <div className="presPanel-divider" /> {this._props.PanelWidth() > 250 ? ( <div className="presPanel-button-text" @@ -2477,7 +2921,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> ) : ( <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, emptyFunction, this.exitClicked, false, false)}> - <FontAwesomeIcon icon={'times'} /> + <FontAwesomeIcon icon="times" /> </div> )} </div> @@ -2492,7 +2936,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }; @action - prevClicked = (e: PointerEvent) => { + prevClicked = () => { this.back(); if (this._presTimer) { clearTimeout(this._presTimer); @@ -2501,7 +2945,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }; @action - nextClicked = (e: PointerEvent) => { + nextClicked = () => { this.next(); if (this._presTimer) { clearTimeout(this._presTimer); @@ -2517,7 +2961,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { AddToMap = (treeViewDoc: Doc, index: number[]) => { if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element. - var indexNum = 0; + let indexNum = 0; for (let i = 0; i < index.length; i++) { indexNum += index[i] * 10 ** -i; } @@ -2529,19 +2973,21 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.dataDoc[this.presFieldKey] = new List<Doc>(sorted); // this is a flat array of Docs } } + return undefined; }; SlideIndex = (slideDoc: Doc) => DocListCast(this.dataDoc[this.presFieldKey]).indexOf(slideDoc); - RemFromMap = (treeViewDoc: Doc, index: number[]) => { + RemFromMap = (treeViewDoc: Doc) => { if (!treeViewDoc.presentation_targetDoc) return this.childDocs; // if treeViewDoc is not a pres elements, then it's a sub-bullet of a progressivized slide which isn't added to the linearized list of pres elements since it's not really a pres element. if (!this._unmounting && this.isTree) { this._treeViewMap.delete(treeViewDoc); this.dataDoc[this.presFieldKey] = new List<Doc>(this.sort(this._treeViewMap)); } + return undefined; }; - sort = (treeView_Map: Map<Doc, number>) => [...treeView_Map.entries()].sort((a: [Doc, number], b: [Doc, number]) => (a[1] > b[1] ? 1 : a[1] < b[1] ? -1 : 0)).map(kv => kv[0]); + 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() { // needed to ensure that the childDocs are loaded for looking up fields @@ -2553,21 +2999,38 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { (this.activeItem.presentation_indexed === undefined || NumCast(this.activeItem.presentation_indexed) === (this.progressivizedItems(this.activeItem)?.length ?? 0)); const presStart = !this.layoutDoc.presLoop && this.itemIndex === 0; return this._props.addDocTab === returnFalse ? ( // bcz: hack!! - addDocTab === returnFalse only when this is being rendered by the OverlayView which means the doc is a mini player - <div className="miniPres" onClick={e => e.stopPropagation()} onPointerEnter={action(e => (this._forceKeyEvents = true))}> + <div + className="miniPres" + onClick={e => e.stopPropagation()} + onPointerEnter={action(() => { + this._forceKeyEvents = true; + })}> <div className="presPanelOverlay" style={{ display: 'inline-flex', height: 30, background: Doc.ActivePresentation === this.Document ? 'green' : '#323232', top: 0, zIndex: 3000000, boxShadow: this._presKeyEvents ? '0 0 0px 3px ' + Colors.MEDIUM_BLUE : undefined }}> - <Tooltip title={<div className="dash-tooltip">{'Loop'}</div>}> + <Tooltip title={<div className="dash-tooltip">Loop</div>}> <div className="presPanel-button" style={{ color: this.layoutDoc.presLoop ? Colors.MEDIUM_BLUE : undefined }} - onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => (this.layoutDoc.presLoop = !this.layoutDoc.presLoop), false, false)}> - <FontAwesomeIcon icon={'redo-alt'} /> + onPointerDown={e => + setupMoveUpEvents( + this, + e, + returnFalse, + returnFalse, + () => { + this.layoutDoc.presLoop = !this.layoutDoc.presLoop; + }, + false, + false + ) + }> + <FontAwesomeIcon icon="redo-alt" /> </div> </Tooltip> - <div className="presPanel-divider"></div> + <div className="presPanel-divider" /> <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'} /> + <FontAwesomeIcon icon="arrow-left" /> </div> <Tooltip title={<div className="dash-tooltip">{this.layoutDoc.presentation_status === PresStatus.Autoplay ? 'Pause' : 'Autoplay'}</div>}> <div className="presPanel-button" onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, () => this.startOrPause(true), false, false)}> @@ -2575,10 +3038,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </div> </Tooltip> <div className="presPanel-button" style={{ opacity: presEnd ? 0.4 : 1 }} onPointerDown={e => setupMoveUpEvents(this, e, returnFalse, returnFalse, this.nextClicked, false, false)}> - <FontAwesomeIcon icon={'arrow-right'} /> + <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-divider" /> + <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> @@ -2602,31 +3065,31 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <div className="Slide"> {mode !== CollectionViewType.Invalid ? ( <CollectionView + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} PanelWidth={this._props.PanelWidth} PanelHeight={this.panelHeight} - childIgnoreNativeSize={true} + childIgnoreNativeSize moveDocument={returnFalse} - ignoreUnrendered={true} + ignoreUnrendered childDragAction={dropActionType.move} setContentViewBox={emptyFunction} - //childLayoutFitWidth={returnTrue} + // childLayoutFitWidth={returnTrue} childOpacity={returnOne} childClickScript={PresBox.navigateToDocScript} childLayoutTemplate={this.childLayoutTemplate} childXPadding={Doc.IsComicStyle(this.Document) ? 20 : undefined} filterAddDocument={this.addDocumentFilter} removeDocument={returnFalse} - dontRegisterView={true} + dontRegisterView focus={this.focusElement} ScreenToLocalTransform={this.getTransform} AddToMap={this.AddToMap} RemFromMap={this.RemFromMap} - hierarchyIndex={emptyPath as any as number[]} + 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>}> @@ -2636,11 +3099,19 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </Tooltip> } */} </div> + {/* presbox chatbox */} + {this.chatActive && <div className="presBox-chatbox" />} </div> ); } } +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function navigateToDoc(bestTarget: Doc, activeItem: Doc) { PresBox.NavigateToTarget(bestTarget, activeItem); }); + +Docs.Prototypes.TemplateMap.set(DocumentType.PRES, { + layout: { view: PresBox, dataField: 'data' }, + options: { acl: '', defaultDoubleClick: 'ignore', hideClickBehaviors: true, layout_hideLinkAnchors: true }, +}); |
