import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { Tooltip } from "@material-ui/core"; import { action, computed, IReactionDisposer, observable, reaction } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../fields/Doc"; import { Id } from "../../../../fields/FieldSymbols"; import { BoolCast, Cast, NumCast, StrCast } from "../../../../fields/Types"; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents } from "../../../../Utils"; import { Docs, DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; import { CollectionViewType } from "../../collections/CollectionView"; import { ViewBoxBaseComponent } from '../../DocComponent'; import { EditableView } from "../../EditableView"; import { Colors } from "../../global/globalEnums"; import { DocumentView, DocumentViewProps } from "../../nodes/DocumentView"; import { FieldView, FieldViewProps } from '../../nodes/FieldView'; import { StyleProp } from "../../StyleProvider"; import { PresBox } from "./PresBox"; import "./PresElementBox.scss"; import { PresMovement } from "./PresEnums"; import React = require("react"); import { List } from "../../../../fields/List"; /** * This class models the view a document added to presentation will have in the presentation. * It involves some functionality for its buttons and options. */ @observer export class PresElementBox extends ViewBoxBaseComponent() { public static LayoutString(fieldKey: string) { return FieldView.LayoutString(PresElementBox, fieldKey); } _heightDisposer: IReactionDisposer | undefined; @observable _dragging = false; @computed get indexInPres() { return DocListCast(this.presBox[StrCast(this.presBox.presFieldKey, "data")]).indexOf(this.rootDoc); } // the index field is where this document is in the presBox display list (since this value is different for each presentation element, the value can't be stored on the layout template which is used by all display elements) @computed get collapsedHeight() { return [CollectionViewType.Tree, CollectionViewType.Stacking].includes(this.presBox._viewType as any) ? 35 : 31; } // the collapsed height changes depending on the state of the presBox. We could store this on the presentation element template if it's used by only one presentation - but if it's shared by multiple, then this value must be looked up @computed get presStatus() { return this.presBox.presStatus; } @computed get presBox() { return (this.props.DocumentView?.().props.treeViewDoc ?? this.props.ContainingCollectionDoc)!; } @computed get targetDoc() { return Cast(this.rootDoc.presentationTargetDoc, Doc, null) || this.rootDoc; } componentDidMount() { this.layoutDoc.hideLinkButton = true; this._heightDisposer = reaction(() => [this.rootDoc.presExpandInlineButton, this.collapsedHeight], params => this.layoutDoc._height = NumCast(params[1]) + (Number(params[0]) ? 100 : 0), { fireImmediately: true }); } componentWillUnmount() { this._heightDisposer?.(); } /** * Returns a local transformed coordinate array for given coordinates. */ ScreenToLocalListTransform = (xCord: number, yCord: number) => [xCord, yCord]; @action presExpandDocumentClick = () => { this.rootDoc.presExpandInlineButton = !this.rootDoc.presExpandInlineButton; } embedHeight = (): number => 97; // embedWidth = () => this.props.PanelWidth(); // embedHeight = () => Math.min(this.props.PanelWidth() - 20, this.props.PanelHeight() - this.collapsedHeight); embedWidth = (): number => this.props.PanelWidth() - 35; styleProvider = (doc: (Doc | undefined), props: Opt, property: string): any => { if (property === StyleProp.Opacity) return 1; return this.props.styleProvider?.(doc, props, property); } /** * The function that is responsible for rendering a preview or not for this * presentation element. */ @computed get renderEmbeddedInline() { return !this.rootDoc.presExpandInlineButton || !this.targetDoc ? (null) :
; } @computed get renderGroupSlides() { const childDocs = DocListCast(this.targetDoc.data); const groupSlides = childDocs.map((doc: Doc, ind: number) =>
{ console.log("Clicked on slide with index: ", ind); e.stopPropagation(); e.preventDefault(); PresBox.Instance.modifierSelect(doc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); this.presExpandDocumentClick(); }}>
{`${ind + 1}.`}
{/* style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }} */}
StrCast(doc.title)} SetValue={(value: string) => { doc.title = !value.trim().length ? "-untitled-" : value; return true; }} />
); return ( groupSlides ); } @computed get duration() { let durationInS: number; if (this.rootDoc.type === DocumentType.AUDIO || this.rootDoc.type === DocumentType.VID) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; } else if (this.rootDoc.presDuration) durationInS = NumCast(this.rootDoc.presDuration) / 1000; else durationInS = 2; return "D: " + durationInS + "s"; } @computed get transition() { let transitionInS: number; if (this.rootDoc.presTransition) transitionInS = NumCast(this.rootDoc.presTransition) / 1000; else transitionInS = 0.5; return this.rootDoc.presMovement === PresMovement.Jump || this.rootDoc.presMovement === PresMovement.None ? (null) : "M: " + transitionInS + "s"; } private _itemRef: React.RefObject = React.createRef(); private _dragRef: React.RefObject = React.createRef(); private _titleRef: React.RefObject = React.createRef(); @action headerDown = (e: React.PointerEvent) => { const element = e.target as any; e.stopPropagation(); e.preventDefault(); if (element && !(e.ctrlKey || e.metaKey)) { if (PresBox.Instance._selectedArray.has(this.rootDoc)) { PresBox.Instance._selectedArray.size === 1 && PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false); setupMoveUpEvents(this, e, this.startDrag, emptyFunction, emptyFunction); } else { setupMoveUpEvents(this, e, ((e: PointerEvent) => { PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, false, false); return this.startDrag(e); }), emptyFunction, emptyFunction); } } } headerUp = (e: React.PointerEvent) => { e.stopPropagation(); e.preventDefault(); } /** * Function to drag and drop the pres element to a diferent location */ startDrag = (e: PointerEvent) => { const miniView: boolean = this.toolbarWidth <= 100; const activeItem = this.rootDoc; const dragArray = PresBox.Instance._dragArray; const dragData = new DragManager.DocumentDragData(PresBox.Instance.sortArray()); if (!dragData.draggedDocuments.length) dragData.draggedDocuments.push(this.rootDoc); dragData.dropAction = "move"; dragData.treeViewDoc = this.props.docViewPath().lastElement()?.props.treeViewDoc; dragData.moveDocument = this.props.docViewPath().lastElement()?.props.moveDocument; const dragItem: HTMLElement[] = []; if (dragArray.length === 1) { const doc = this._itemRef.current || dragArray[0]; doc.className = miniView ? "presItem-miniSlide" : "presItem-slide"; dragItem.push(doc); } else if (dragArray.length >= 1) { const doc = document.createElement('div'); doc.className = "presItem-multiDrag"; doc.innerText = "Move " + PresBox.Instance._selectedArray.size + " slides"; doc.style.position = 'absolute'; doc.style.top = (e.clientY) + 'px'; doc.style.left = (e.clientX - 50) + 'px'; dragItem.push(doc); } // const dropEvent = () => runInAction(() => this._dragging = false); if (activeItem) { DragManager.StartDocumentDrag(dragItem.map(ele => ele), dragData, e.clientX, e.clientY, undefined); // runInAction(() => this._dragging = true); return true; } return false; } onPointerOver = (e: any) => { document.removeEventListener("pointermove", this.onPointerMove); document.addEventListener("pointermove", this.onPointerMove); } onPointerMove = (e: PointerEvent) => { const slide = this._itemRef.current!; let dragIsPresItem: boolean = DragManager.docsBeingDragged.length > 0 ? true : false; for (const doc of DragManager.docsBeingDragged) { if (!doc.presentationTargetDoc) dragIsPresItem = false; } if (slide && dragIsPresItem) { const rect = slide.getBoundingClientRect(); const y = e.clientY - rect.top; //y position within the element. const height = slide.clientHeight; const halfLine = height / 2; if (y <= halfLine) { slide.style.borderTop = `solid 2px ${Colors.MEDIUM_BLUE}`; slide.style.borderBottom = "0px"; } else if (y > halfLine) { slide.style.borderTop = "0px"; slide.style.borderBottom = `solid 2px ${Colors.MEDIUM_BLUE}`; } } document.removeEventListener("pointermove", this.onPointerMove); } onPointerLeave = (e: any) => { this._itemRef.current!.style.borderTop = "0px"; this._itemRef.current!.style.borderBottom = "0px"; document.removeEventListener("pointermove", this.onPointerMove); } @action toggleProperties = () => { if (CurrentUserUtils.propertiesWidth < 5) { action(() => (CurrentUserUtils.propertiesWidth = 250)); } } @undoBatch removeItem = action((e: React.MouseEvent) => { e.stopPropagation(); this.props.removeDocument?.(this.rootDoc); if (PresBox.Instance._selectedArray.has(this.rootDoc)) { PresBox.Instance._selectedArray.delete(this.rootDoc); } this.removeAllRecordingInOverlay() }); // set the value/title of the individual pres element @undoBatch @action onSetValue = (value: string) => { this.rootDoc.title = !value.trim().length ? "-untitled-" : value; return true; } /** * Method called for updating the view of the currently selected document * * @param targetDoc * @param activeItem */ @undoBatch @action updateView = (targetDoc: Doc, activeItem: Doc) => { switch (targetDoc.type) { case DocumentType.PDF: case DocumentType.WEB: case DocumentType.RTF : const scroll = targetDoc._scrollTop; activeItem.presPinViewScroll = scroll; break; case DocumentType.VID: case DocumentType.AUDIO: activeItem.presStartTime = targetDoc._currentTimecode; break; case DocumentType.COMPARISON : const clipWidth = targetDoc._clipWidth; activeItem.presPinClipWidth = clipWidth; break; default: const x = targetDoc._panX; const y = targetDoc._panY; const scale = targetDoc._viewScale; activeItem.presPinViewX = x; activeItem.presPinViewY = y; activeItem.presPinViewScale = scale; } } @computed get recordingIsInOverlay() { let isInOverlay = false DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => { if (doc.slides === this.rootDoc) { isInOverlay = true return } }) return isInOverlay } removeAllRecordingInOverlay = () => { DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => { if (doc.slides === this.rootDoc) { Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc); } }) } @undoBatch @action hideRecording = (e: React.MouseEvent) => { e.stopPropagation() this.removeAllRecordingInOverlay() } @undoBatch @action showRecording = (activeItem: Doc) => { this.removeAllRecordingInOverlay() if (activeItem.recording) { // if we already have an existing recording Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null)); } } @undoBatch @action startRecording = (activeItem: Doc) => { // Remove every recording that already exists in overlay view DocListCast(CurrentUserUtils.MyOverlayDocs.data).forEach((doc) => { if (doc.slides !== null) { Doc.RemoveDocFromList(CurrentUserUtils.MyOverlayDocs, undefined, doc); } }) if (activeItem.recording) { // if we already have an existing recording Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, Cast(activeItem.recording, Doc, null)); } else { // if we dont have any recording const recording = Docs.Create.WebCamDocument("", { _width: 384, _height: 216, hideDocumentButtonBar: true, hideDecorationTitle: true, hideOpenButton: true, // hideDeleteButton: true, cloneFieldFilter: new List(["system"]) }); // attach the recording to the slide, and attach the slide to the recording recording.slides = activeItem activeItem.recording = recording // make recording box appear in the bottom right corner of the screen recording.x = window.innerWidth - recording[WidthSym]() - 20; recording.y = window.innerHeight - recording[HeightSym]() - 20; Doc.AddDocToList(CurrentUserUtils.MyOverlayDocs, undefined, recording); } } @computed get toolbarWidth(): number { const presBoxDocView = DocumentManager.Instance.getDocumentView(this.presBox); let width: number = NumCast(this.presBox._width); if (presBoxDocView) width = presBoxDocView.props.PanelWidth(); if (width === 0) width = 300; return width; } @computed get mainItem() { const isSelected: boolean = PresBox.Instance?._selectedArray.has(this.rootDoc); const toolbarWidth: number = this.toolbarWidth; const showMore: boolean = this.toolbarWidth >= 300; const miniView: boolean = this.toolbarWidth <= 110; const presBox: Doc = this.presBox; //presBox const presBoxColor: string = StrCast(presBox._backgroundColor); const presColorBool: boolean = presBoxColor ? (presBoxColor !== Colors.WHITE && presBoxColor !== "transparent") : false; const targetDoc: Doc = this.targetDoc; const activeItem: Doc = this.rootDoc; return (
{ e.stopPropagation(); e.preventDefault(); PresBox.Instance.modifierSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, !e.shiftKey && !e.ctrlKey && !e.metaKey, e.ctrlKey || e.metaKey, e.shiftKey); this.showRecording(activeItem); }} onDoubleClick={action(e => { this.toggleProperties(); PresBox.Instance.regularSelect(this.rootDoc, this._itemRef.current!, this._dragRef.current!, true); })} onPointerOver={this.onPointerOver} onPointerLeave={this.onPointerLeave} onPointerDown={this.headerDown} onPointerUp={this.headerUp} > {/* {miniView ? // when width is LESS than 110 px
{`${this.indexInPres + 1}.`}
: // when width is MORE than 110 px
{`${this.indexInPres + 1}.`}
} */} {/*
{`${this.indexInPres + 1}.`}
*/} {miniView ? (null) :
{`${this.indexInPres + 1}. `}
StrCast(activeItem.title)} SetValue={this.onSetValue} />
{/*
{"Movement speed"}
}>
{this.transition}
*/} {/*
{"Duration"}
}>
{this.duration}
*/}
{"Update view"}
}>
this.updateView(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V
{this.recordingIsInOverlay ?
{"Hide Recording"}
}>
e.stopPropagation()} />
:
{"Start recording"}
}>
this.startRecording(activeItem)} style={{ fontWeight: 700 }}> e.stopPropagation()} />
} {/* {this.indexInPres === 0 ? (null) :
{activeItem.groupWithUp ? "Ungroup" : "Group with up"}
}>
activeItem.groupWithUp = !activeItem.groupWithUp} style={{ zIndex: 1000 - this.indexInPres, fontWeight: 700, backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : Colors.MEDIUM_BLUE : undefined, height: activeItem.groupWithUp ? 53 : 18, transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined }}>
e.stopPropagation()} />
} */}
{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}
}>
{ e.stopPropagation(); this.presExpandDocumentClick(); }}> e.stopPropagation()} />
{"Remove from presentation"}
}>
e.stopPropagation()} />
{/*
{activeItem.presPinView ? (<>View of {targetDoc.title}) : targetDoc.title}
*/} {this.renderEmbeddedInline}
}
); } render() { return !(this.rootDoc instanceof Doc) || this.targetDoc instanceof Promise ? (null) : this.mainItem; } }