diff options
Diffstat (limited to 'src/client/views/nodes/trails/PresBox.tsx')
-rw-r--r-- | src/client/views/nodes/trails/PresBox.tsx | 1132 |
1 files changed, 523 insertions, 609 deletions
diff --git a/src/client/views/nodes/trails/PresBox.tsx b/src/client/views/nodes/trails/PresBox.tsx index 7448fa898..9ab5fb1bd 100644 --- a/src/client/views/nodes/trails/PresBox.tsx +++ b/src/client/views/nodes/trails/PresBox.tsx @@ -1,12 +1,12 @@ +import { Button, Dropdown, DropdownType, IconButton, Size, Type } from '@dash/components'; 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 _ from 'lodash'; import { IReactionDisposer, ObservableSet, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; 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'; @@ -14,7 +14,7 @@ import { StopEvent, lightOrDark, returnFalse, returnOne, setupMoveUpEvents } fro import { emptyFunction, stringHash } from '../../../../Utils'; import { Doc, DocListCast, Field, FieldResult, FieldType, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; import { Animation, DocData, TransitionTimer } from '../../../../fields/DocSymbols'; -import { Copy } from '../../../../fields/FieldSymbols'; +import { Copy, Id } from '../../../../fields/FieldSymbols'; import { InkField } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; import { ObjectField } from '../../../../fields/ObjectField'; @@ -25,12 +25,12 @@ import { DocServer } from '../../../DocServer'; import { getSlideTransitionSuggestions, gptSlideProperties, gptTrailSlideCustomization } from '../../../apis/gpt/PresCustomization'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; -import { DictationManager } from '../../../util/DictationManager'; import { dropActionType } from '../../../util/DropActionTypes'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; import { SerializationHelper } from '../../../util/SerializationHelper'; import { SnappingManager } from '../../../util/SnappingManager'; import { UndoManager, undoBatch, undoable } from '../../../util/UndoManager'; +import { DictationButton } from '../../DictationButton'; import { ViewBoxBaseComponent } from '../../DocComponent'; import { pinDataTypes as dataTypes } from '../../PinFuncs'; import { CollectionView } from '../../collections/CollectionView'; @@ -40,7 +40,7 @@ import { CollectionFreeFormPannableContents } from '../../collections/collection import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../DocumentView'; import { FieldView, FieldViewProps } from '../FieldView'; -import { FocusViewOptions } from '../FocusViewOptions'; +import { FocusEffectDelay, FocusViewOptions } from '../FocusViewOptions'; import { OpenWhere, OpenWhereMod } from '../OpenWhere'; import { ScriptingBox } from '../ScriptingBox'; import CubicBezierEditor, { EaseFuncToPoints, TIMING_DEFAULT_MAPPINGS } from './CubicBezierEditor'; @@ -60,6 +60,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } static navigateToDocScript: ScriptField; + public static PanelName = 'PRESBOX'; // name of dockingview tab where presentations get added + constructor(props: FieldViewProps) { super(props); makeObservable(this); @@ -71,10 +73,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { private _disposers: { [name: string]: IReactionDisposer } = {}; public selectedArray = new ObservableSet<Doc>(); + public slideToModify: Doc | null = null; _batch: UndoManager.Batch | undefined = undefined; // undo batch for dragging sliders which generate multiple scene edit events as the cursor moves _keyTimer: NodeJS.Timeout | undefined; // timer for turning off transition flag when key frame change has completed. Need to clear this if you do a second navigation before first finishes, or else first timer can go off during second naviation. _unmounting = false; // flag that view is unmounting used to block RemFromMap from deleting things _presTimer: NodeJS.Timeout | undefined; + _animationDictation: DictationButton | null = null; + _slideDictation: DictationButton | null = null; // eslint-disable-next-line no-use-before-define @observable public static Instance: PresBox; @@ -96,14 +101,15 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { @observable _presKeyEvents: boolean = false; @observable _forceKeyEvents: boolean = false; + @observable _showAIGallery = false; + @observable _showPreview = true; + @observable _easeDropdownVal = 'ease'; + // 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 _chatActive: boolean = false; + @observable _animationChat: string = ''; + @observable _chatInput: string = ''; + @observable _isLoading: boolean = false; @observable generatedAnimations: AnimationSettings[] = [ // default presets @@ -137,54 +143,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }, ]; - @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[] }) => { + setGeneratedAnimations = action((input: AnimationSettings[]) => { this.generatedAnimations = input; }) // prettier-ignore + setChatInput = action((input: string) => { this._chatInput = input; }); // prettier-ignore + setAnimationChat = action((input: string) => { this._animationChat = input; }); // prettier-ignore + setIsLoading = action((input?: boolean) => { this._isLoading = !!input; }); // prettier-ignore + setShowAIGalleryVisibilty = action((visible: boolean) => { this._showAIGallery = visible; }); // prettier-ignore + setBezierControlPoints = action((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 showEaseFunctions() { + return ![PresMovement.None, PresMovement.Jump, ''].includes(StrCast(this.activeItem?.presentation_movement) as PresMovement); + } @computed get currCPoints() { - const strPoints = this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease'; - return EaseFuncToPoints(strPoints); + return EaseFuncToPoints(this.activeItem.presentation_easeFunc ? StrCast(this.activeItem.presentation_easeFunc) : 'ease'); } @computed @@ -220,25 +194,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if ([DocumentType.PDF, DocumentType.WEB, DocumentType.RTF].includes(this.targetDoc.type as DocumentType) || this.targetDoc._type_collection === CollectionViewType.Stacking) return true; return false; } - @computed get panable() { - if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._type_collection === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; - return false; - } @computed get selectedDocumentView() { if (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; - } - @computed get selectedDoc() { - return this.selectedDocumentView?.Document; - } - isActiveItemTarget = (layoutDoc: Doc) => this.activeItem?.presentation_targetDoc === layoutDoc; - clearSelectedArray = () => this.selectedArray.clear(); - addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc)); - removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc)); componentWillUnmount() { this._unmounting = true; @@ -255,7 +215,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { () => this.pauseAutoPres() ); this._disposers.keyboard = reaction( - () => this.selectedDoc, + () => this.selectedDocumentView?.Document, selected => { document.removeEventListener('keydown', PresBox.keyEventsWrapper, true); (this._presKeyEvents = selected === this.Document) && document.addEventListener('keydown', PresBox.keyEventsWrapper, true); @@ -292,13 +252,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { ); } + clearSelectedArray = () => this.selectedArray.clear(); + addToSelectedArray = action((doc: Doc) => this.selectedArray.add(doc)); + removeFromSelectedArray = action((doc: Doc) => this.selectedArray.delete(doc)); + @action updateCurrentPresentation = (pres?: Doc) => { Doc.ActivePresentation = pres ?? this.Document; PresBox.Instance = this; }; - _mediaTimer!: [NodeJS.Timeout, Doc]; // 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played startTempMedia = (targetDoc: Doc, activeItem: Doc) => { const duration: number = NumCast(activeItem.config_clipEnd) - NumCast(activeItem.config_clipStart); @@ -316,84 +279,33 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } }; - // 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); - }; + setDictationContent = (value: string) => this.setChatInput(value); - @action - customizeAnimations = async () => { + customizeAnimations = action(() => { 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); - }; + getSlideTransitionSuggestions(this._animationChat) + .then(res => this.setGeneratedAnimations(JSON.parse(res) as AnimationSettings[])) + .catch(err => console.error(err)) + .finally(this.setIsLoading); + }); - @action - customizeWithGPT = async (input: string) => { + customizeWithGPT = action((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]: FieldResult } = {}; - 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); - }; + const slideDefaults: { [key: string]: FieldResult } = { presentation_transition: 500, config_zoom: 1 }; + const currSlideProperties = gptSlideProperties.reduce( + (prev, key) => { prev[key] = Field.toString(this.activeItem[key]) ?? prev[key]; return prev; }, + slideDefaults); // prettier-ignore + + gptTrailSlideCustomization(input, JSON.stringify(currSlideProperties)) + .then(res => + (Object.entries(JSON.parse(res)) as string[][]).forEach(([key, val]) => { + this.activeItem[key] = (+val).toString() === val ? +val : (val ?? this.activeItem[key]); + }) + ) + .catch(e => console.error(e)) + .finally(this.setIsLoading); + }); // 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 @@ -452,27 +364,25 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { const progressiveReveal = (first: boolean) => { const presIndexed = Cast(this.activeItem?.presentation_indexed, 'number', null); if (presIndexed !== undefined) { - const targetRenderedDoc = PresBox.targetRenderedDoc(this.activeItem); - targetRenderedDoc._dataTransition = 'all 1s'; - targetRenderedDoc.opacity = 1; - setTimeout(() => { - targetRenderedDoc._dataTransition = 'inherit'; - }, 1000); const listItems = this.progressivizedItems(this.activeItem); - if (listItems && presIndexed < listItems.length) { + const listItemDoc = listItems?.[presIndexed]; + if (listItems && listItemDoc) { if (!first) { - const listItemDoc = listItems[presIndexed]; - const targetView = listItems && DocumentView.getFirstDocumentView(listItemDoc); + const presBulletTiming = 500; // bcz: hardwired for now Doc.linkFollowUnhighlight(); - Doc.HighlightDoc(listItemDoc); + Doc.linkFollowHighlight(listItemDoc); listItemDoc.presentation_effect = this.activeItem.presBulletEffect; - listItemDoc.presentation_transition = 500; - targetView?.setAnimEffect(listItemDoc, 500); - if (targetView && this.activeItem.presBulletExpand) { - targetView.setAnimateScaling(1.2, 400); - Doc.AddUnHighlightWatcher(() => targetView?.setAnimateScaling(0, undefined)); - } + listItemDoc.presentation_transition = presBulletTiming; listItemDoc.opacity = undefined; + + const targetView = DocumentView.getFirstDocumentView(listItemDoc); + if (targetView) { + targetView.setAnimEffect(listItemDoc, presBulletTiming); + if (this.activeItem.presBulletExpand) { + targetView.setAnimateScaling(1.2, presBulletTiming * 0.8); + Doc.AddUnHighlightWatcher(() => targetView.setAnimateScaling(0, undefined)); + } + } this.activeItem.presentation_indexed = presIndexed + 1; } return true; @@ -547,8 +457,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (!group) this.clearSelectedArray(); this.childDocs[index] && this.addToSelectedArray(this.childDocs[index]); // Update selected array this.turnOffEdit(); - this.navigateToActiveItem(finished); // Handles movement to element only when presentationTrail is list - this.doHideBeforeAfter(); // Handles hide after/before + this.navigateToActiveItem((options: FocusViewOptions) => { + setTimeout(this.doHideBeforeAfter, FocusEffectDelay(options)); // Handles hide after/before + finished?.(); + }); // Handles movement to element only when presentationTrail is list } }); static pinDataTypes(target?: Doc): dataTypes { @@ -562,11 +474,11 @@ 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 typeCollection = targetType === DocumentType.COL; + const collectionType = targetType === DocumentType.COL; const filters = true; const pivot = true; const dataannos = false; - return { scrollable, pannable, inkable, type_collection: typeCollection, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos }; + return { scrollable, pannable, inkable, collectionType, pivot, map, filters, temporal, clippable, dataview, datarange, poslayoutview, dataannos }; } @action @@ -574,7 +486,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { /* empty */ }; @action - // 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 undefined; @@ -700,15 +611,27 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { changed = true; } } - if ((pinDataTypes?.type_collection && activeItem.config_viewType !== undefined) || (!pinDataTypes && activeItem.config_viewType !== undefined)) { - if (bestTarget._type_collection !== activeItem.config_viewType) { - bestTarget._type_collection = activeItem.config_viewType; + if ((pinDataTypes?.collectionType && activeItem.config_card_curDoc !== undefined) || (!pinDataTypes && activeItem.config_card_curDoc !== undefined)) { + if (bestTarget._card_curDoc !== activeItem.config_card_curDoc) { + bestTarget._card_curDoc = activeItem.config_card_curDoc; + changed = true; + } + } + if ((pinDataTypes?.collectionType && activeItem.config_carousel_index !== undefined) || (!pinDataTypes && activeItem.config_carousel_index !== undefined)) { + if (bestTarget._carousel_index !== activeItem.config_carousel_index) { + bestTarget._carousel_index = activeItem.config_carousel_index; + changed = true; + } + } + if ((pinDataTypes?.collectionType && activeItem.config_type_collection !== undefined) || (!pinDataTypes && activeItem.config_type_collection !== undefined)) { + if (bestTarget._type_collection !== activeItem.config_type_collection) { + bestTarget._type_collection = activeItem.config_type_collection; changed = true; } } if ((pinDataTypes?.filters && activeItem.config_docFilters !== undefined) || (!pinDataTypes && activeItem.config_docFilters !== undefined)) { - if (bestTarget.childFilters !== activeItem.config_docFilters) { + if (!_.isEqual(Array.from(StrListCast(bestTarget.childFilters)), Array.from(StrListCast(activeItem.config_docFilters)))) { bestTarget.childFilters = ObjectField.MakeCopy(activeItem.config_docFilters as ObjectField) || new List<string>([]); changed = true; } @@ -773,11 +696,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { }); setTimeout( () => - Array.from(transitioned).forEach( - action(doc => { - doc._dataTransition = undefined; - }) - ), + Array.from(transitioned).forEach(doc => { + doc._dataTransition = undefined; + }), transTime + 10 ); } @@ -815,16 +736,16 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { * a new tab. If presCollection is undefined it will open the document * on the right. */ - navigateToActiveItem = (afterNav?: () => void) => { + navigateToActiveItem = (afterNav?: (options: FocusViewOptions) => void) => { const { activeItem, targetDoc } = this; - const finished = () => { - afterNav?.(); + const finished = (options: FocusViewOptions) => { + afterNav?.(options); targetDoc[Animation] = undefined; }; const selViewCache = Array.from(this.selectedArray); const dragViewCache = Array.from(this._dragArray); const eleViewCache = Array.from(this._eleArray); - const resetSelection = action(() => { + const resetSelection = action((options: FocusViewOptions) => { if (!this._props.isSelected()) { const presDocView = DocumentView.getDocumentView(this.Document); if (presDocView) DocumentView.SelectView(presDocView, false); @@ -833,14 +754,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this._dragArray.splice(0, this._dragArray.length, ...dragViewCache); this._eleArray.splice(0, this._eleArray.length, ...eleViewCache); } - finished(); + finished(options); }); PresBox.NavigateToTarget(targetDoc, activeItem, resetSelection); }; - public static PanelName = 'PRESBOX'; - - static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: () => void) { + static NavigateToTarget(targetDoc: Doc, activeItem: Doc, finished?: (options: FocusViewOptions) => void) { if (activeItem.presentation_movement === PresMovement.None && targetDoc.type === DocumentType.SCRIPTING) { (DocumentView.getFirstDocumentView(targetDoc)?.ComponentView as ScriptingBox)?.onRun?.(); return; @@ -875,9 +794,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (!DocumentView.getLightboxDocumentView(DocCast(targetDoc.annotationOn) ?? targetDoc)) { DocumentView.SetLightboxDoc(undefined); } - DocumentView.showDocument(targetDoc, options, finished); + DocumentView.showDocument(targetDoc, options, () => finished?.(options)); }); - } else finished?.(); + } else finished?.(options); } /** @@ -889,8 +808,8 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { this.childDocs.forEach((doc, index) => { const curDoc = Cast(doc, Doc, null); const tagDoc = PresBox.targetRenderedDoc(curDoc); - const itemIndexes: number[] = this.getAllIndexes(this.tagDocs, curDoc); - let opacity: Opt<number> = index === this.itemIndex ? 1 : undefined; + const itemIndexes = this.getAllIndexes(this.tagDocs, curDoc); + let opacity = index === this.itemIndex ? 1 : undefined; if (curDoc.presentation_hide) { if (index !== this.itemIndex) { opacity = 1; @@ -902,9 +821,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { opacity = 0; } else if (index === this.itemIndex || !curDoc.presentation_hideAfter) { opacity = 1; - setTimeout(() => { - tagDoc._dataTransition = undefined; - }, 1000); } } const hidingIndAft = @@ -1134,7 +1050,6 @@ 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; @@ -1166,8 +1081,7 @@ 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"> + <div key={doc[Id]} className="selectedList-items"> <b> {index + 1}. {StrCast(curDoc.title)}) </b> @@ -1175,15 +1089,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { ); if (tagDoc) return ( - // eslint-disable-next-line react/no-array-index-key - <div key={index} className="selectedList-items"> + <div key={doc[Id]} className="selectedList-items"> {index + 1}. {StrCast(curDoc.title)} </div> ); if (curDoc) return ( - // eslint-disable-next-line react/no-array-index-key - <div key={index} className="selectedList-items"> + <div key={doc[Id]} className="selectedList-items"> {index + 1}. {StrCast(curDoc.title)} </div> ); @@ -1368,7 +1280,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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> @@ -1619,7 +1530,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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); - // 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 = pt; @@ -1682,20 +1592,22 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { </Tooltip> </div> {[DocumentType.AUDIO, DocumentType.VID].includes(targetType 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 className="ribbon-doubleButton"> + <Tooltip title={<div>How long to view the slide before transitioning to the next slide</div>}> + <div className="presBox-subheading">DURATION</div> + </Tooltip> + <div className="presBox-subheading-slider"> + {PresBox.inputter('0.1', '0.1', '10', duration, targetType !== DocumentType.AUDIO, this.updateDurationTime)} + <div className="slider-headers"> + <div className="slider-text">Short</div> + <div className="slider-text">Long</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 className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}> + <input className="presBox-inputNumber" type="number" value={duration} onChange={action(e => this.updateDurationTime(e.target.value))} /> + <span>s</span> </div> - </> + </div> )} </div> </div> @@ -1703,28 +1615,62 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } return undefined; } - @computed get progressivizeDropdown() { + + @computed get mediaDropdown() { const { activeItem } = this; if (activeItem && this.targetDoc) { - const effect = activeItem.presBulletEffect ? activeItem.presBulletEffect : PresMovement.None; - const bulletEffect = (presEffect: PresEffect) => ( - <div - className={`presBox-dropdownOption ${activeItem.presentation_effect === presEffect || (presEffect === PresEffect.None && !activeItem.presentation_effect) ? 'active' : ''}`} - onPointerDown={StopEvent} - onClick={() => this.updateEffect(presEffect, true)}> - {presEffect} + return ( + <div className="presBox-option-block"> + <div className="presBox-ribbon presbox-toggles"> + <Tooltip title={<div className="dash-tooltip">Should first bullet be progressively disclosed or does it appear with slide.</div>}> + <div + className={`ribbon-toggle ${BoolCast(activeItem.presentation_playAudio) ? 'active' : ''}`} + style={{ + border: `solid 1px ${SnappingManager.userColor}`, + color: SnappingManager.userColor, + background: BoolCast(activeItem.presentation_playAudio) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, + }} + onClick={() => { + activeItem.presentation_playAudio = !BoolCast(activeItem.presentation_playAudio); + }}> + Play Audio Annotation + </div> + </Tooltip> + <Tooltip title={<div className="dash-tooltip">Should first bullet be progressively disclosed or does it appear with slide.</div>}> + <div + className={`ribbon-toggle ${BoolCast(activeItem.presentation_zoomText) ? 'active' : ''}`} + style={{ + border: `solid 1px ${SnappingManager.userColor}`, + color: SnappingManager.userColor, + background: BoolCast(activeItem.presentation_zoomText) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, + }} + onClick={() => { + activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText); + }}> + Zoom Text Selections + </div> + </Tooltip> + </div> </div> ); + } + return null; + } + @computed get progressivizeDropdown() { + const { activeItem } = this; + if (activeItem && this.targetDoc) { return ( <div className="presBox-option-block"> - <div className="presBox-ribbon"> - <div className="ribbon-doubleButton" style={{ display: 'inline-flex' }}> - <div className="presBox-subheading">Progressivize Collection</div> - <input - className="presBox-checkbox" - style={{ margin: 10, border: `solid 1px ${SnappingManager.userColor}` }} - type="checkbox" - onChange={() => { + <div className="presBox-toggles presBox-ribbon"> + <Tooltip title={<div className="dash-tooltip">whether progressivization is active for this slide</div>}> + <div + className={`ribbon-toggle ${Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? 'active' : ''}`} + style={{ + border: `solid 1px ${SnappingManager.userColor}`, + color: SnappingManager.userColor, + background: Cast(activeItem.presentation_indexed, 'number', null) !== undefined ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, + }} + onClick={() => { activeItem.presentation_indexed = activeItem.presentation_indexed === undefined ? 0 : undefined; activeItem.presentation_hideBefore = activeItem.presentation_indexed !== undefined; const tagDoc = PresBox.targetRenderedDoc(this.activeItem); @@ -1737,62 +1683,51 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { if (DocCast(activeItem.presentation_targetDoc).annotationOn) activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc.annotationOn?.["${dataField}"]`); else activeItem.data = ComputedField.MakeFunction(`this.presentation_targetDoc?.["${dataField}"]`); + }}> + Enable + </div> + </Tooltip> + <Tooltip title={<div className="dash-tooltip">Should first bullet be progressively disclosed or does it appear with slide.</div>}> + <div + className={`ribbon-toggle ${!NumCast(activeItem.presentation_indexedStart) ? 'active' : ''}`} + style={{ + border: `solid 1px ${SnappingManager.userColor}`, + color: SnappingManager.userColor, + background: !NumCast(activeItem.presentation_indexedStart) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, }} - 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={() => { + onClick={() => { 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 + }}> + All Bullets + </div> + </Tooltip> + <Tooltip title={<div className="dash-tooltip">Whether the active bullet expands when active.</div>}> <div - className="presBox-dropdown" - onClick={action(e => { - e.stopPropagation(); - this._openBulletEffectDropdown = !this._openBulletEffectDropdown; - })} + className={`ribbon-toggle ${BoolCast(activeItem.presBulletExpand) ? 'active' : ''}`} style={{ + border: `solid 1px ${SnappingManager.userColor}`, color: SnappingManager.userColor, - background: SnappingManager.userVariantColor, - borderBottomLeftRadius: this._openBulletEffectDropdown ? 0 : 5, - border: this._openBulletEffectDropdown ? `solid 2px ${SnappingManager.userVariantColor}` : `solid 1px ${SnappingManager.userColor}`, + background: BoolCast(activeItem.presBulletExpand) ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, + }} + onClick={() => { + activeItem.presBulletExpand = !activeItem.presBulletExpand; }}> - {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> + Expand Active </div> - </div> + </Tooltip> </div> + <Dropdown + color={SnappingManager.userColor} + formLabel="Effect" + toolTip="Animation effect to use when bullet activates" + formLabelPlacement="left" + closeOnSelect + items={Object.values(PresEffect).map(v => ({ text: v.toString(), val: v }))} + selectedVal={StrCast(activeItem.presBulletEffect, PresMovement.None)} + setSelectedVal={val => this.updateEffect(val as PresEffect, true)} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + /> </div> ); } @@ -1803,23 +1738,95 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return <div />; } + /** + * This chatbox is for getting slide effect transition suggestions from gpt and visualizing them + */ + @computed get aiEffects() { + return ( + <div className="presBox-gpt-chat" style={{ display: SnappingManager.PropertiesWidth < 1 || !this._showAIGallery ? 'none' : undefined }}> + {/* Custom */} + <div className="pres-chat"> + <div className="pres-chatbox-container-ai"> + <ReactTextareaAutosize + placeholder="Use AI to suggest effects. Leave blank for random results." + className="pres-chatbox" + ref={r => { + setTimeout(() => { + if (r && !r.textContent) { + r.style.height = ''; + r.style.height = r.scrollHeight + 'px'; + } + }); + }} + value={this._animationChat} + onChange={e => { + e.currentTarget.style.height = ''; + e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px'; + this.setAnimationChat(e.target.value); + }} + onKeyDown={e => { + this._animationDictation?.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} + /> + <DictationButton + ref={r => { + this._animationDictation = r; + }} + setInput={this.setAnimationChat} + /> + </div> + <div style={{ alignItems: 'center' }}> + Click a box to use the effect. + {/* Preview Animations */} + <div className="presBox-effects"> + {this.generatedAnimations.map((elem, i) => ( + <div + key={i} + className="presBox-effect-container" + onClick={() => { + this.updateEffect(elem.effect, false); + this.updateEffectDirection(elem.direction); + this.updateEffectTiming(this.activeItem, { + type: SpringType.CUSTOM, + stiffness: elem.stiffness, + damping: elem.damping, + mass: elem.mass, + }); + }}> + <SlideEffect dir={elem.direction} presEffect={elem.effect} springSettings={elem} infinite> + <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[i] }} /> + </SlideEffect> + </div> + ))} + </div> + </div> + </div> + ); + } + @computed get transitionDropdown() { 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, - }; - } + const timing = StrCast(activeItem?.presentation_effectTiming); + const timingConfig: SpringSettings = timing + ? JSON.parse(timing) + : { + 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; @@ -1830,180 +1837,136 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { return ( <> {/* This chatbox is for customizing the properties of trails, like transition time, movement type (zoom, pan) using GPT */} - <div className="presBox-gpt-chat"> - <span style={{ display: 'flex', alignItems: 'center', gap: '8px' }}> + <div className="presBox-gpt-chat" style={{ display: SnappingManager.PropertiesWidth < 1 ? 'none' : undefined }}> + <span className="presBox-gpt-chat-span"> 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> </span> <div className="pres-chat"> - <div className="pres-chatbox-container"> + <div className="pres-chatbox-container-ai"> <ReactTextareaAutosize - placeholder="Describe how you would like to modify the slide properties." + placeholder="Describe how to modify the slide properties." className="pres-chatbox" - value={this.chatInput} + ref={r => { + setTimeout(() => { + if (r && !r.textContent) { + r.style.height = ''; + r.style.height = r.scrollHeight + 'px'; + } + }); + }} + value={this._chatInput} onChange={e => { + e.currentTarget.style.height = ''; + e.currentTarget.style.height = e.currentTarget.scrollHeight + 'px'; this.setChatInput(e.target.value); }} onKeyDown={e => { - this.stopDictation(); + this._slideDictation?.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(); - } + <DictationButton + ref={r => { + this._slideDictation = r; }} + setInput={this.setChatInput} /> </div> <Button style={{ alignSelf: 'flex-end' }} text="Send" type={Type.TERT} - icon={this.isLoading ? <ReactLoading type="spin" color="#ffffff" width={20} height={20} /> : <AiOutlineSend />} + 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); + this._slideDictation?.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} + onPointerDown={e => e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._openMovementDropdown = false; this._openEffectDropdown = false; this._openBulletEffectDropdown = false; })}> - <div - className="presBox-option-block" - // style={{ padding: '16px' }} - > - Movement + <div className="presBox-option-block"> + <div className="ribbon-doubleButton"> + <Tooltip title={<div>How long the transition (view navigation and slide animation effect) lasts</div>}> + <div className="presBox-subheading">SPEED</div> + </Tooltip> + <div className="presBox-subheading-slider"> + {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">Slow</div> + </div> + </div> + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}> + <input className="presBox-inputNumber" type="number" value={transitionSpeed} onKeyDown={e => e.stopPropagation()} onChange={action(e => this.updateTransitionTime(e.target.value))} /> + <span>s</span> + </div> + </div> <Dropdown color={SnappingManager.userColor} - formLabel="Movement" + formLabel="View" + formLabelPlacement="left" closeOnSelect items={movementItems} selectedVal={this.movementName(activeItem)} - setSelectedVal={val => { - this.updateMovement(val as PresMovement); - }} + 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 className="ribbon-doubleButton" style={{ display: activeItem.presentation_movement === PresMovement.Zoom ? undefined : 'none' }}> + <Tooltip title={<div>How much (%) of screen target should occupy</div>}> + <div className="presBox-subheading">ZOOM %</div> + </Tooltip> + <div className="presBox-subheading-slider">{PresBox.inputter('0', '1', '100', zoom, activeItem.presentation_movement === PresMovement.Zoom, this.updateZoom)}</div> + <div className="ribbon-property" style={{ border: `solid 1px ${SnappingManager.userColor}`, display: 'flex', maxWidth: 60, width: '100%' }}> + <input className="presBox-inputNumber" type="number" value={zoom} onChange={e => this.updateZoom(e.target.value)} /> + <span>%</span> </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 ${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> - {PresBox.inputter('0.1', '0.1', '10', transitionSpeed, true, this.updateTransitionTime)} - <div className="slider-headers"> - <div className="slider-text">Fast</div> - <div className="slider-text">Medium</div> - <div className="slider-text">Slow</div> - </div> {/* Easing function */} - <Dropdown - color={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> + {!this.showEaseFunctions ? null : ( + <Dropdown + color={SnappingManager.userColor} + formLabel="Timing" + formLabelPlacement="left" + closeOnSelect + items={easeItems} + selectedVal={this.activeItem.presentation_easeFunc ? (StrCast(this.activeItem.presentation_easeFunc).startsWith('cubic') ? 'custom' : StrCast(this.activeItem.presentation_easeFunc)) : 'ease'} + setSelectedVal={val => typeof val === 'string' && this.setEaseFunc(this.activeItem, val !== 'custom' ? val : TIMING_DEFAULT_MAPPINGS.ease)} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + /> + )} </div> </div> {/* 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> + {!this.showEaseFunctions || !StrCast(activeItem.presentation_easeFunc).includes('cubic-bezier') ? null : ( + <div className="presBox-option-block" style={{ paddingTop: 0, alignItems: 'center' }}> <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="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> - <div className={`presBox-ribbon ${this._transitionTools && this.layoutDoc.presentation_status === PresStatus.Edit ? 'active' : ''}`} - onPointerDown={StopEvent} - onPointerUp={StopEvent} + onPointerDown={e => e.stopPropagation()} + onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this._openMovementDropdown = false; @@ -2011,214 +1974,169 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { 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> - ))} + {/* Effect dropdown */} + <div style={{ display: 'flex' }}> + <Dropdown + color={SnappingManager.userColor} + formLabel="Effect" + toolTip="Animation effect to apply when transitiong to slide" + formLabelPlacement="left" + 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} + /> + + <div + className={`ribbon-toggle ${this._showAIGallery ? 'active' : ''}`} + style={{ + border: `solid 1px ${SnappingManager.userColor}`, + color: SnappingManager.userColor, + background: this._showAIGallery ? SnappingManager.userVariantColor : SnappingManager.userBackgroundColor, + }} + onClick={() => this.setShowAIGalleryVisibilty(!this._showAIGallery)}> + MORE </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 && ( - <> + + {this.aiEffects} + <div className="presBox-gpt-chat"> + {/* Effect direction */} + {/* Only applies to certain effects */} + {(effect === PresEffect.Flip || effect === PresEffect.Bounce || effect === PresEffect.Roll) && ( + <div className="ribbon-doubleButton"> + <div className="presBox-subheading">DIRECTION</div> + <div style={{ width: '100%' }}> + <div className="presBox-icon-list" style={{ width: 'fit-content', margin: 'auto' }}> <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)} + 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.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" + 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> - <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> + )} + {![PresEffect.Lightspeed, PresEffect.Fade, PresEffect.None, ''].includes(effect) && ( + <> + <Dropdown + color={SnappingManager.userColor} + formLabel="Springiness" + formLabelPlacement="left" + closeOnSelect + items={effectTimings} + selectedVal={timingConfig.type} + setSelectedVal={val => this.updateEffectTiming(activeItem, { type: val as SpringType, ...springMappings[val] })} + dropdownType={DropdownType.SELECT} + type={Type.TERT} + /> + + <div style={{ display: SnappingManager.PropertiesWidth < 1 ? 'none' : undefined }}> + {/* No spring settings for jackinthebox (lightspeed) */} + {StrCast(activeItem.presentation_effectTiming).includes('custom') && effect !== PresEffect.None && ( + <> + <div className="presBox-springSlider"> + <span>Tension</span> + <div onPointerDown={e => e.stopPropagation()}> + {/* prettier-ignore */} + <Slider min={1} max={1000} step={5} size="small" + value={timingConfig.stiffness} + onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, stiffness: val as number })} + valueLabelDisplay="auto" + /> + </div> + </div> + <div className="presBox-springSlider"> + <span>Damping</span> + <div onPointerDown={e => e.stopPropagation()}> + {/* prettier-ignore */} + <Slider min={1} max={100} step={1} size="small" + value={timingConfig.damping} + onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, damping: val as number })} + valueLabelDisplay="auto" + /> + </div> + </div> + <div className="presBox-springSlider"> + <span>Mass</span> + <div onPointerDown={e => e.stopPropagation()}> + {/* prettier-ignore */} + <Slider min={1} max={10} step={1} size="small" + value={timingConfig.mass} + onChange={(e, val) => timingConfig && this.updateEffectTiming(activeItem, { ...timingConfig, type: SpringType.CUSTOM, mass: val as number })} + valueLabelDisplay="auto" + /> + </div> + </div> + </> + )} + </div> + </> + )} + </div> </div> + </div> - {/* 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); - }} - color={SnappingManager.userColor} - /> - <Toggle - formLabel="Zoom Text Selections" - toggleType={ToggleType.SWITCH} - toggleStatus={BoolCast(activeItem.presentation_zoomText)} - onClick={() => { - activeItem.presentation_zoomText = !BoolCast(activeItem.presentation_zoomText); - }} + {[PresEffect.None, PresEffect.Fade, ''].includes(effect) ? null : ( + <div className="presBox-previewContainer"> + <Button + type={Type.TERT} + tooltip="show preview of slide animation effect" + size={Size.SMALL} color={SnappingManager.userColor} + background="transparent" + onClick={action(() => { + this._showPreview = false; + setTimeout(action(() => { this._showPreview = true; }) ); // prettier-ignore + })} + text="Preview Effect" /> - <Button text="Apply to all" type={Type.TERT} color={SnappingManager.userVariantColor} onClick={() => this.applyTo(this.childDocs)} /> + <div className="presBox-option-block presBox-option-center"> + <div className="presBox-effect-container"> + {!this._showPreview ? null : ( + <SlideEffect dir={direction} presEffect={effect} springSettings={timingConfig}> + <div className="presBox-effect-demo-box" style={{ backgroundColor: springPreviewColors[0] }} /> + </SlideEffect> + )} + </div> + </div> </div> - </div> + )} + + <Button text="Apply to all slides" type={Type.TERT} color={SnappingManager.userVariantColor} onClick={() => this.applyTo(this.childDocs)} /> </> ); } @@ -2244,7 +2162,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <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 value={NumCast(activeItem.config_clipStart).toFixed(2)} @@ -2271,7 +2188,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { <input className="presBox-input" onKeyDown={e => e.stopPropagation()} - style={{ textAlign: 'center', width: '100%', height: 15, fontSize: 10 }} type="number" readOnly value={configClipEnd.toFixed(2)} @@ -2612,13 +2528,13 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { createTemplate = (layout: string, input?: string) => { const x = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.x) : 0; const y = this.activeItem && this.targetDoc ? NumCast(this.targetDoc.y) + NumCast(this.targetDoc._height) + 20 : 0; - const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, _text_fontSize: '24pt' }); - const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, _text_fontSize: '16pt' }); - const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, _text_fontSize: '20pt' }); - const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, _text_fontSize: '24pt' }); - const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, _text_fontSize: '14pt' }); - const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, _text_fontSize: '14pt' }); - const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, _text_fontSize: '14pt' }); + const title = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 58, text_fontSize: '24pt' }); + const subtitle = () => Docs.Create.TextDocument('Click to change subtitle', { title: 'Slide subtitle', _width: 380, _height: 50, x: 10, y: 118, text_fontSize: '16pt' }); + const header = () => Docs.Create.TextDocument('Click to change header', { title: 'Slide header', _width: 380, _height: 65, x: 10, y: 80, text_fontSize: '20pt' }); + const contentTitle = () => Docs.Create.TextDocument('Click to change title', { title: 'Slide title', _width: 380, _height: 60, x: 10, y: 10, text_fontSize: '24pt' }); + const content = () => Docs.Create.TextDocument('Click to change text', { title: 'Slide text', _width: 380, _height: 145, x: 10, y: 70, text_fontSize: '14pt' }); + const content1 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 1', _width: 185, _height: 140, x: 10, y: 80, text_fontSize: '14pt' }); + const content2 = () => Docs.Create.TextDocument('Click to change text', { title: 'Column 2', _width: 185, _height: 140, x: 205, y: 80, text_fontSize: '14pt' }); // prettier-ignore switch (layout) { case 'blank': return Docs.Create.FreeformDocument([], { title: input || 'Blank slide', _width: 400, _height: 225, x, y }); @@ -3045,7 +2961,6 @@ 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} @@ -3054,7 +2969,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { ignoreUnrendered childDragAction={dropActionType.move} setContentViewBox={emptyFunction} - // childLayoutFitWidth={returnTrue} childOpacity={returnOne} childClickScript={PresBox.navigateToDocScript} childLayoutTemplate={this.childLayoutTemplate} @@ -3080,7 +2994,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps>() { } */} </div> {/* presbox chatbox */} - {this.chatActive && <div className="presBox-chatbox" />} + {this._chatActive && <div className="presBox-chatbox" />} </div> ); } |