diff options
| -rw-r--r-- | deploy/index.html | 65 | ||||
| -rw-r--r-- | deploy/loader.css | 141 | ||||
| -rw-r--r-- | src/Utils.ts | 43 | ||||
| -rw-r--r-- | src/client/util/CurrentUserUtils.ts | 2 | ||||
| -rw-r--r-- | src/client/views/MainView.tsx | 9 | ||||
| -rw-r--r-- | src/client/views/PropertiesView.tsx | 16 | ||||
| -rw-r--r-- | src/client/views/collections/TabDocView.tsx | 11 | ||||
| -rw-r--r-- | src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 1 | ||||
| -rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 51 | ||||
| -rw-r--r-- | src/client/views/nodes/PresBox.scss | 93 | ||||
| -rw-r--r-- | src/client/views/nodes/PresBox.tsx | 469 | ||||
| -rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 51 | ||||
| -rw-r--r-- | src/client/views/presentationview/PresElementBox.scss | 39 | ||||
| -rw-r--r-- | src/client/views/presentationview/PresElementBox.tsx | 45 | ||||
| -rw-r--r-- | src/client/views/presentationview/PresProperties.tsx | 492 |
15 files changed, 1320 insertions, 208 deletions
diff --git a/deploy/index.html b/deploy/index.html index 282acc0ce..beb7dd205 100644 --- a/deploy/index.html +++ b/deploy/index.html @@ -7,9 +7,74 @@ <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <script src="https://cdnjs.cloudflare.com/ajax/libs/typescript/3.3.1/typescript.min.js"></script> + <style> + .dash-loader { + display: flex; + align-content: center; + justify-content: center; + background-color: #AEDDF8; + z-index: 10; + width: 100vw; + height: 100vh; + } + + .dash-loader-container { + width: 20vw; + height: 20vw; + display: flex; + align-items: center; + justify-content: center; + border-radius: 100%; + background-color: #5B9FDD; + align-self: center; + } + + .dash-d-path { + stroke-dasharray: 1000; + stroke-dashoffset: 1000; + animation: dash-d-path 3s linear infinite; + } + + @keyframes dash-d-path { + 0% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + + 20% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 70% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 90% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + + 100% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + } + </style> </head> <body style="display:flex" id="dash-body"> + <div class="dash-loader" id="loader" style="z-index:10; width:100%; height:100%"> + <div class="dash-loader-container"> + <svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="200px" + height="200px" viewBox="0 0 250 350"> + <path class="dash-d-path" fill-rule="evenodd" stroke="#ececec" stroke-width="20px" stroke-linecap="butt" + stroke-linejoin="miter" fill="none" + d="M196.500,18.500 C195.888,79.462 194.655,160.273 197.500,195.500 C198.117,203.144 199.664,225.773 189.500,246.500 C183.949,257.819 175.192,268.535 163.500,277.500 C147.404,289.842 133.006,294.212 121.500,295.500 C106.618,297.166 92.057,294.673 79.500,288.500 C68.970,283.324 58.384,274.780 49.500,261.500 C39.958,247.237 35.188,230.375 35.500,213.500 C35.760,199.463 39.572,185.692 46.500,173.500 C55.433,157.780 65.945,148.829 75.500,143.500 C85.352,138.005 98.187,134.141 114.500,134.500 C128.982,134.819 143.177,139.076 155.500,146.500 C186.160,164.973 196.208,196.242 197.500,216.500 C197.663,219.061 197.578,222.985 197.500,226.500 C197.092,244.967 195.719,262.030 195.500,280.500 C195.460,283.898 195.500,293.783 195.500,300.500 C195.500,304.834 195.500,309.189 195.500,313.500 " /> + </svg> + </div> + </div> <!-- <script src="https://hypothes.is/embed.js" async></script> --> <div id="root" style="position:absolute;width:100%;height:100%;overflow: hidden;"></div> <script src="/bundle.js"></script> diff --git a/deploy/loader.css b/deploy/loader.css new file mode 100644 index 000000000..065862013 --- /dev/null +++ b/deploy/loader.css @@ -0,0 +1,141 @@ +.dash-loader { + display: flex; + align-content: center; + justify-content: center; + background-color: lightcyan; + z-index: 10; + width: 100%; + height: 100%; +} + +.dash-loader-container { + width: 100; + align-self: center; +} + +.dash-d-path { + stroke-dasharray: 1000; + stroke-dashoffset: 1000; + animation: dash-d-path 10s linear infinite; +} + +@keyframes dash-d-path { + 0% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + + 10% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 90% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 100% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } +} + +.dash-a-path { + stroke-dasharray: 1000; + stroke-dashoffset: 1000; + animation: dash-a-path 10s linear infinite; +} + +@keyframes dash-a-path { + 0% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + + 7% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + + 17% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 90% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 100% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } +} + +.dash-s-path { + stroke-dasharray: 1000; + stroke-dashoffset: 1000; + animation: dash-s-path 10s linear infinite; +} + +@keyframes dash-s-path { + 0% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + + 14% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + + 20% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 90% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 100% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } +} + +.dash-h-path { + stroke-dasharray: 1000; + stroke-dashoffset: 1000; + animation: dash-h-path 10s linear infinite; +} + +@keyframes dash-h-path { + 0% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + + 18% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } + + 28% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 90% { + stroke-dashoffset: 0; + /* stroke-width: 20px; */ + } + + 100% { + stroke-dashoffset: 1000; + /* stroke-width: 0px; */ + } +}
\ No newline at end of file diff --git a/src/Utils.ts b/src/Utils.ts index daacca51d..1a00c0bfb 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -549,6 +549,49 @@ export function simulateMouseClick(element: Element | null | undefined, x: numbe })); } +export function lightOrDark(color: any) { + + // Variables for red, green, blue values + var r, g, b, hsp; + + // Check the format of the color, HEX or RGB? + if (color.match(/^rgb/)) { + + // If RGB --> store the red, green, blue values in separate variables + color = color.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/); + + r = color[1]; + g = color[2]; + b = color[3]; + } + else { + + // If hex --> Convert it to RGB: http://gist.github.com/983661 + color = +("0x" + color.slice(1).replace( + color.length < 5 && /./g, '$&$&')); + + r = color >> 16; + g = color >> 8 & 255; + b = color & 255; + } + + // HSP (Highly Sensitive Poo) equation from http://alienryderflex.com/hsp.html + hsp = Math.sqrt( + 0.299 * (r * r) + + 0.587 * (g * g) + + 0.114 * (b * b) + ); + + // Using the HSP value, determine whether the color is light or dark + if (hsp > 127.5) { + return 'light'; + } + else { + + return 'dark'; + } +} + export function setupMoveUpEvents( target: object, e: React.PointerEvent, diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 99dfbaf1c..213bc3d88 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -63,7 +63,7 @@ export class CurrentUserUtils { [this.ficon({ ignoreClick: true, icon: "mobile", - backgroundColor: "rgba(0,0,0,0)" + backgroundColor: "transparent" }), this.mobileTextContainer({}, [this.mobileButtonText({}, "NEW MOBILE BUTTON"), this.mobileButtonInfo({}, "You can customize this button and make it your own.")])]); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 032d4d3bb..12e361dc1 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -86,6 +86,13 @@ export class MainView extends React.Component { componentDidMount() { document.getElementById("root")?.addEventListener("scroll", e => ((ele) => ele.scrollLeft = ele.scrollTop = 0)(document.getElementById("root")!)); + const ele = document.getElementById("loader"); + + if (ele) { + // remove from DOM + setTimeout(() => { ele.style.opacity = "0"; }, 0); + setTimeout(() => ele.outerHTML = '', 10000); + } new InkStrokeProperties(); this._sidebarContent.proto = undefined; DocServer.setPlaygroundFields(["x", "y", "dataTransition", "_delayAutoHeight", "_autoHeight", "_showSidebar", "_sidebarWidthPercent", "_width", "_height", "_viewTransition", "_panX", "_panY", "_viewScale", "_scrollY", "_scrollTop", "hidden", "_curPage", "_viewType", "_chromeStatus"]); // can play with these fields on someone else's @@ -141,7 +148,7 @@ export class MainView extends React.Component { fa.faSearch, fa.faFileDownload, fa.faFileUpload, fa.faStop, fa.faCalculator, fa.faWindowMaximize, fa.faAddressCard, fa.faQuestionCircle, fa.faArrowLeft, fa.faArrowRight, fa.faArrowDown, fa.faArrowUp, fa.faBolt, fa.faBullseye, fa.faCaretUp, fa.faCat, fa.faCheck, fa.faChevronRight, fa.faChevronLeft, fa.faChevronDown, fa.faChevronUp, fa.faClone, fa.faCloudUploadAlt, fa.faCommentAlt, fa.faCompressArrowsAlt, fa.faCut, fa.faEllipsisV, fa.faEraser, fa.faExclamation, fa.faFileAlt, - fa.faFileAudio, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer, + fa.faFileAudio, fa.faFileVideo, fa.faFilePdf, fa.faFilm, fa.faFilter, fa.faFont, fa.faGlobeAmericas, fa.faGlobeAsia, fa.faHighlighter, fa.faLongArrowAltRight, fa.faMousePointer, fa.faMusic, fa.faObjectGroup, fa.faPause, fa.faPen, fa.faPenNib, fa.faPhone, fa.faPlay, fa.faPortrait, fa.faRedoAlt, fa.faStamp, fa.faStickyNote, fa.faTimesCircle, fa.faThumbtack, fa.faTree, fa.faTv, fa.faUndoAlt, fa.faVideo, fa.faAsterisk, fa.faBrain, fa.faImage, fa.faPaintBrush, fa.faTimes, fa.faEye, fa.faArrowsAlt, fa.faQuoteLeft, fa.faSortAmountDown, fa.faAlignLeft, fa.faAlignCenter, fa.faAlignRight, fa.faHeading, fa.faRulerCombined, diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx index 245e612b3..bdba625f8 100644 --- a/src/client/views/PropertiesView.tsx +++ b/src/client/views/PropertiesView.tsx @@ -992,7 +992,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView-title" style={{ width: this.props.width }}> Presentation </div> - <div className="propertiesView-name"> + <div className="propertiesView-name" style={{ borderBottom: 0 }}> {this.editableTitle} <div className="propertiesView-presSelected"> <div className="propertiesView-selectedCount"> @@ -1007,7 +1007,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { <div className="propertiesView-presTrails-title" onPointerDown={action(() => { this.openPresTransitions = !this.openPresTransitions; })} style={{ backgroundColor: this.openPresTransitions ? "black" : "" }}> - <FontAwesomeIcon icon={"rocket"} /> Transitions + <FontAwesomeIcon style={{ alignSelf: "center" }} icon={"rocket"} /> Transitions <div className="propertiesView-presTrails-title-icon"> <FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" /> </div> @@ -1016,7 +1016,7 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {PresBox.Instance.transitionDropdown} </div> : null} </div>} - {/* {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) : <div className="propertiesView-presTrails"> + {!selectedItem || type === DocumentType.VID || type === DocumentType.AUDIO ? (null) : <div className="propertiesView-presTrails"> <div className="propertiesView-presTrails-title" onPointerDown={action(() => this.openPresProgressivize = !this.openPresProgressivize)} style={{ backgroundColor: this.openPresProgressivize ? "black" : "" }}> @@ -1028,20 +1028,20 @@ export class PropertiesView extends React.Component<PropertiesViewProps> { {this.openPresProgressivize ? <div className="propertiesView-presTrails-content"> {PresBox.Instance.progressivizeDropdown} </div> : null} - </div>} */} - {/* {!selectedItem || (!scrollable && !pannable) ? (null) : <div className="propertiesView-presTrails"> + </div>} + {!selectedItem || (type !== DocumentType.VID && type !== DocumentType.AUDIO) ? (null) : <div className="propertiesView-presTrails"> <div className="propertiesView-presTrails-title" onPointerDown={action(() => { this.openSlideOptions = !this.openSlideOptions; })} style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}> - <FontAwesomeIcon icon={"cog"} /> {scrollable ? "Scroll options" : "Pan options"} + <FontAwesomeIcon style={{ alignSelf: "center" }} icon={type === DocumentType.AUDIO ? "file-audio" : "file-video"} /> {type === DocumentType.AUDIO ? "Audio Options" : "Video Options"} <div className="propertiesView-presTrails-title-icon"> <FontAwesomeIcon icon={this.openSlideOptions ? "caret-down" : "caret-right"} size="lg" color="white" /> </div> </div> {this.openSlideOptions ? <div className="propertiesView-presTrails-content"> - {PresBox.Instance.optionsDropdown} + {PresBox.Instance.mediaOptionsDropdown} </div> : null} - </div>} */} + </div>} {/* <div className="propertiesView-presTrails"> <div className="propertiesView-presTrails-title" onPointerDown={action(() => { this.openAddSlide = !this.openAddSlide; })} diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 82b6d8605..b7e81d7dc 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -166,14 +166,17 @@ export class TabDocView extends React.Component<TabDocViewProps> { pinDoc.presentationTargetDoc = doc; pinDoc.title = doc.title + " - Slide"; pinDoc.presMovement = PresMovement.Zoom; + pinDoc.groupWithUp = false; pinDoc.context = curPres; const presArray: Doc[] = PresBox.Instance?.sortArray(); const size: number = PresBox.Instance?._selectedArray.size; const presSelected: Doc | undefined = presArray && size ? presArray[size - 1] : undefined; Doc.AddDocToList(curPres, "data", pinDoc, presSelected); - if (pinDoc.type === "audio" && !audioRange) { + if (!audioRange && (pinDoc.type === DocumentType.AUDIO || pinDoc.type === DocumentType.VID)) { + pinDoc.mediaStart = "manual"; + pinDoc.mediaStop = "manual"; pinDoc.presStartTime = 0; - pinDoc.presEndTime = doc.duration; + pinDoc.presEndTime = pinDoc.type === DocumentType.AUDIO ? doc.duration : NumCast(doc["data-duration"]); } if (curPres.expandBoolean) pinDoc.presExpandInlineButton = true; const dview = CollectionDockingView.Instance.props.Document; @@ -442,8 +445,8 @@ export class TabDocView extends React.Component<TabDocViewProps> { let docColor = StrCast(doc?._backgroundColor, StrCast(doc?.backgroundColor)); if (!docColor) { switch (doc?.type) { - case DocumentType.PRESELEMENT: docColor = TabDocView.darkScheme ? "dimgrey" : ""; break; - case DocumentType.PRES: docColor = TabDocView.darkScheme ? "#3e3e3e" : "black"; break; + case DocumentType.PRESELEMENT: docColor = TabDocView.darkScheme ? "" : ""; break; + case DocumentType.PRES: docColor = TabDocView.darkScheme ? "#3e3e3e" : "white"; break; case DocumentType.FONTICON: docColor = "black"; break; case DocumentType.RTF: docColor = TabDocView.darkScheme ? "#2d2d2d" : "#f1efeb"; case DocumentType.LABEL: diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 0eb05500c..c4d51f986 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -404,6 +404,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const pinDoc = Doc.MakeAlias(doc); pinDoc.presentationTargetDoc = doc; pinDoc.presMovement = PresMovement.Zoom; + pinDoc.groupWithUp = false; pinDoc.context = curPres; pinDoc.title = doc.title + " - Slide"; const presArray: Doc[] = PresBox.Instance?.sortArray(); diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 6323a6100..8570b35af 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -27,6 +27,8 @@ import Waveform from "react-audio-waveform"; import axios from "axios"; import { SnappingManager } from "../../util/SnappingManager"; import { CurrentUserUtils } from "../../util/CurrentUserUtils"; +import { LinkDocPreview } from "./LinkDocPreview"; +import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment"; declare class MediaRecorder { // whatever MediaRecorder has @@ -46,9 +48,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD static RangeScript: ScriptField; static LabelScript: ScriptField; - _linkPlayDisposer: IReactionDisposer | undefined; - _reactionDisposer: IReactionDisposer | undefined; - _scrubbingDisposer: IReactionDisposer | undefined; + // _linkPlayDisposer: IReactionDisposer | undefined; + // _reactionDisposer: IReactionDisposer | undefined; + // _scrubbingDisposer: IReactionDisposer | undefined; + private _disposers: { [name: string]: IReactionDisposer } = {}; _ele: HTMLAudioElement | null = null; _recorder: any; _recordStart = 0; @@ -106,9 +109,10 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD } componentWillUnmount() { - this._reactionDisposer?.(); - this._linkPlayDisposer?.(); - this._scrubbingDisposer?.(); + this._disposers.reaction?.(); + this._disposers.linkPlay?.(); + this._disposers.scrubbing?.(); + this._disposers.audioStart?.(); } @action @@ -118,7 +122,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD } this.audioState = this.path ? "paused" : undefined; - this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID, + this._disposers.linkPlay = reaction(() => this.layoutDoc.scrollToLinkID, scrollLinkId => { if (scrollLinkId) { DocListCast(this.dataDoc.links).filter(l => l[Id] === scrollLinkId).map(l => { @@ -130,7 +134,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD }, { fireImmediately: true }); // for play when link is selected - this._reactionDisposer = reaction(() => SelectionManager.SelectedDocuments(), + this._disposers.reaction = reaction(() => SelectionManager.SelectedDocuments(), selected => { const sel = selected.length ? selected[0].props.Document : undefined; let link; @@ -145,7 +149,36 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD this.layoutDoc.playOnSelect && this.recordingStart && !sel && this.pause(); } }); - this._scrubbingDisposer = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime)); + this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime)); + + this._disposers.audioStart = reaction( + () => this.Document._audioStart, + (audioStart) => { + if (audioStart !== undefined) { + if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { + const delay = this._audioRef.current ? 0 : 250; // wait for mainCont and try again to play + const startTime: number = NumCast(this.Document._audioStart); + setTimeout(() => this._audioRef.current && this.playFrom(startTime), delay); + setTimeout(() => { this.Document._currentTimecode = startTime; this.Document._audioStart = undefined; }, 10 + delay); + } + } + }, + { fireImmediately: true } + ); + + this._disposers.audioStop = reaction( + () => this.Document._audioStop, + (audioStop) => { + if (audioStop !== undefined) { + if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { + const delay = this._audioRef.current ? 0 : 250; // wait for mainCont and try again to play + setTimeout(() => this._audioRef.current && this.pause(), delay); + setTimeout(() => { this.Document._audioStop = undefined; }, 10 + delay); + } + } + }, + { fireImmediately: true } + ); } playLink = (doc: Doc) => { diff --git a/src/client/views/nodes/PresBox.scss b/src/client/views/nodes/PresBox.scss index de2aee8fa..6579dbf41 100644 --- a/src/client/views/nodes/PresBox.scss +++ b/src/client/views/nodes/PresBox.scss @@ -127,6 +127,17 @@ $light-background: #ececec; opacity: 0.8; } +.presBox-radioButtons { + background-color: rgba(0, 0, 0, 0.1); + + .checkbox-container { + margin-left: 10px; + display: inline-flex; + height: 20px; + align-items: center; + } +} + .presBox-ribbon { position: relative; display: inline; @@ -209,6 +220,42 @@ $light-background: #ececec; } @media screen and (-webkit-min-device-pixel-ratio:0) { + + .multiThumb-slider { + display: grid; + background-color: $light-background; + height: 10px; + border-radius: 10px; + overflow: hidden; + + .toolbar-slider { + margin-top: 0px; + background: none; + -webkit-appearance: none; + pointer-events: none; + } + + .toolbar-slider.start::-webkit-slider-thumb { + width: 10px; + pointer-events: auto; + -webkit-appearance: none; + height: 10px; + cursor: ew-resize; + background: $dark-blue; + box-shadow: -100vw 0 0 100vw $light-background; + } + + .toolbar-slider.end::-webkit-slider-thumb { + width: 10px; + pointer-events: auto; + -webkit-appearance: none; + height: 10px; + cursor: ew-resize; + background: $dark-blue; + box-shadow: -100vw 0 0 100vw $light-blue; + } + } + .toolbar-slider { margin-top: 5px; position: relative; @@ -219,7 +266,7 @@ $light-background: #ececec; height: 10px; border-radius: 10px; -webkit-appearance: none; - background-color: #ececec; + background-color: $light-background; } .toolbar-slider:focus { @@ -234,14 +281,44 @@ $light-background: #ececec; .toolbar-slider::-webkit-slider-thumb { width: 10px; + pointer-events: auto; -webkit-appearance: none; height: 10px; cursor: ew-resize; - background: #5b9ddd; - box-shadow: -100vw 0 0 100vw #aedef8; + background: $dark-blue; + box-shadow: -100vw 0 0 100vw $light-blue; + } + + .presBox-checkbox { + -webkit-appearance: none; + -webkit-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + margin: 0; + margin-right: 3px; + border-radius: 100%; + height: 15px; + width: 15px; + cursor: pointer; + background: $light-background; + } + + .presBox-checkbox:focus { + outline: none; + } + + .presBox-checkbox:hover { + background: #c0c0c0; + } + + .presBox-checkbox:checked { + background: $light-blue; } } + + .slider-headers { position: relative; display: grid; @@ -420,20 +497,20 @@ $light-background: #ececec; background-color: #ececec; border: 1px solid #9f9f9f; grid-template-rows: max-content; - + .frameList-header { display: grid; width: 100%; height: 20px; background-color: #9f9f9f; - + .frameList-headerButtons { display: flex; grid-column: 7; width: 60px; justify-self: right; justify-content: flex-end; - + .headerButton { cursor: pointer; position: relative; @@ -452,7 +529,7 @@ $light-background: #ececec; transition: 0.2s; margin-right: 3px; } - + .headerButton:hover { background-color: rgba(0, 0, 0, 1); transform: scale(1.15); @@ -1061,7 +1138,7 @@ $light-background: #ececec; background-color: #5a5a5a; } - + } // .miniPres { diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 455ee87e1..54818f67e 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -24,7 +24,6 @@ import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionView, CollectionViewType } from "../collections/CollectionView"; import { TabDocView } from "../collections/TabDocView"; import { ViewBoxBaseComponent } from "../DocComponent"; -import { AudioBox } from "./AudioBox"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; import { FieldView, FieldViewProps } from './FieldView'; import "./PresBox.scss"; @@ -57,7 +56,7 @@ enum PresStatus { Edit = "edit" } -enum PresColor { +export enum PresColor { LightBlue = "#AEDDF8", DarkBlue = "#5B9FDD", LightBackground = "#ececec", @@ -117,7 +116,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (Doc.UserDoc().activePresentation = this.rootDoc) runInAction(() => PresBox.Instance = this); if (!this.presElement) { // create exactly one presElmentBox template to use by any and all presentations. Doc.UserDoc().presElement = new PrefetchProxy(Docs.Create.PresElementBoxDocument({ - title: "pres element template", type: DocumentType.PRESELEMENT, backgroundColor: "transparent", _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" + title: "pres element template", type: DocumentType.PRESELEMENT, _xMargin: 0, isTemplateDoc: true, isTemplateForField: "data" })); // this script will be called by each presElement to get rendering-specific info that the PresBox knows about but which isn't written to the PresElement // this is a design choice -- we could write this data to the presElements which would require a reaction to keep it up to date, and it would prevent @@ -191,23 +190,37 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played nextAudioVideo = (targetDoc: Doc, activeItem: Doc) => { - if (targetDoc.type === DocumentType.AUDIO) AudioBox.Instance.playFrom(NumCast(activeItem.presStartTime)); - // if (targetDoc.type === DocumentType.VID) { VideoBox.Instance.Play() }; + if (targetDoc.type === DocumentType.AUDIO) { + console.log("play audio"); + targetDoc._audioStart = NumCast(activeItem.presStartTime); + } else if (targetDoc.type === DocumentType.VID) { + console.log("play video"); + targetDoc._videoStop = true; + setTimeout(() => targetDoc._currentTimecode = NumCast(activeItem.presStartTime), 10); + setTimeout(() => targetDoc._videoStart = true, 20); + } activeItem.playNow = false; } // No more frames in current doc and next slide is defined, therefore move to next slide nextSlide = (targetDoc: Doc, activeNext: Doc) => { - const nextSelected = this.itemIndex + 1; + const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); + let nextSelected = this.itemIndex + 1; this.gotoDocument(nextSelected); - - // const targetNext = Cast(activeNext.presentationTargetDoc, Doc, null); + for (nextSelected = nextSelected + 1; nextSelected < this.childDocs.length; nextSelected++) { + if (!this.childDocs[nextSelected].groupWithUp) { + break; + } else { + console.log("Title: " + this.childDocs[nextSelected].title); + this.gotoDocument(nextSelected, true); + } + } // If next slide is audio / video 'Play automatically' then the next slide should be played - // if (activeNext && (targetNext.type === DocumentType.AUDIO || targetNext.type === DocumentType.VID) && activeNext.playAuto) { - // console.log('play next automatically'); - // if (targetNext.type === DocumentType.AUDIO) AudioBox.Instance.playFrom(NumCast(activeNext.presStartTime)); - // // if (targetNext.type === DocumentType.VID) { VideoBox.Instance.Play() }; - // } else if (targetNext.type === DocumentType.AUDIO || targetNext.type === DocumentType.VID) { activeNext.playNow = true; console.log('play next after it is navigated to'); } + if ((targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && (activeNext.mediaStart === "auto" || activeNext.playNow)) { + this.nextAudioVideo(targetNext, activeNext); + } else if ((targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && !activeNext.playNow && activeNext.mediaStart === "onClick") { + activeNext.playNow = true; + } } // Called when the user activates 'next' - to move to the next part of the pres. trail @@ -224,16 +237,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> // Case 1: There are still other frames and should go through all frames before going to next slide this.nextInternalFrame(targetDoc, activeItem); } else if (this.childDocs[this.itemIndex + 1] !== undefined) { - // Case 3: No more frames in current doc and next slide is defined, therefore move to next slide + // Case 2: No more frames in current doc and next slide is defined, therefore move to next slide this.nextSlide(targetDoc, activeNext); - } else if (this.childDocs[this.itemIndex + 1] === undefined && this.layoutDoc.presLoop) { - // Case 4: Last slide and presLoop is toggled ON + } else if (this.childDocs[this.itemIndex + 1] === undefined && (this.layoutDoc.presLoop || this.layoutDoc.presStatus === PresStatus.Edit)) { + // Case 3: Last slide and presLoop is toggled ON or it is in Edit mode this.gotoDocument(0); } - // else if ((targetDoc.type === DocumentType.AUDIO || targetDoc.type === DocumentType.VID) && !activeItem.playAuto && activeItem.playNow && this.layoutDoc.presStatus !== PresStatus.Autoplay) { - // // Case 2: 'Play on next' for audio or video therefore first navigate to the audio/video before it should be played - // this.nextAudioVideo(targetDoc, activeItem); - // } } // Called when the user activates 'back' - to move to the previous part of the pres. trail @@ -246,6 +255,11 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> const lastFrame = Cast(targetDoc.lastFrame, "number", null); const curFrame = NumCast(targetDoc._currentFrame); let prevSelected = this.itemIndex; + // Functionality for group with up + let didZoom = activeItem.presMovement; + for (; !didZoom && prevSelected > 0 && this.childDocs[prevSelected].groupButton; prevSelected--) { + didZoom = this.childDocs[prevSelected].presMovement; + } if (lastFrame !== undefined && curFrame >= 1) { // Case 1: There are still other frames and should go through all frames before going to previous slide this.prevKeyframe(targetDoc, activeItem); @@ -262,7 +276,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> //The function that is called when a document is clicked or reached through next or back. //it'll also execute the necessary actions if presentation is playing. - public gotoDocument = action((index: number) => { + public gotoDocument = action((index: number, group?: boolean) => { Doc.UnBrushAllDocs(); if (index >= 0 && index < this.childDocs.length) { this.rootDoc._itemIndex = index; @@ -278,9 +292,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> if (presTargetDoc?.lastFrame !== undefined) { presTargetDoc._currentFrame = 0; } - this._selectedArray.clear(); + if (!group) this._selectedArray.clear(); this.childDocs[index] && this._selectedArray.set(this.childDocs[index], undefined); //Update selected array - if (this.layoutDoc._viewType === "stacking") this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list + if (this.layoutDoc._viewType === "stacking" && !group) this.navigateToElement(this.childDocs[index]); //Handles movement to element only when presTrail is list this.onHideDocument(); //Handles hide after/before } }); @@ -503,10 +517,9 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> try { doc.opacity = 1; } catch (e) { - console.log("REset presentation error: ", e); + console.log("Reset presentation error: ", e); } }); - ///for (const doc of this.childDocs) Cast(doc.presentationTargetDoc, Doc, null).opacity = 1; } @action togglePath = (srcContext: Doc, off?: boolean) => { @@ -610,9 +623,12 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> addDocumentFilter = (doc: Doc | Doc[]) => { const docs = doc instanceof Doc ? [doc] : doc; docs.forEach((doc, i) => { + if (doc.presentationTargetDoc) return true; if (doc.type === DocumentType.LABEL) { const audio = Cast(doc.annotationOn, Doc, null); if (audio) { + audio.mediaStart = "manual"; + audio.mediaStop = "manual"; audio.presStartTime = NumCast(doc.audioStart); audio.presEndTime = NumCast(doc.audioEnd); audio.presDuration = NumCast(doc.audioEnd) - NumCast(doc.audioStart); @@ -1058,6 +1074,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> @computed get transitionDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; + const type = targetDoc.type; const isPresCollection: boolean = (targetDoc === this.layoutDoc.presCollection); const isPinWithView: boolean = BoolCast(activeItem.presPinView); if (activeItem && targetDoc) { @@ -1087,7 +1104,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> </div> } <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}> - <div className="presBox-subheading">Transition Speed</div> + <div className="presBox-subheading">Movement Speed</div> <div className="ribbon-property"> <input className="presBox-input" type="number" value={transitionSpeed} @@ -1124,34 +1141,36 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfter ? "active" : ""}`} onClick={() => this.updateHideAfter(activeItem)}>Hide after</div></Tooltip>} <Tooltip title={<><div className="dash-tooltip">{"Open document in a new tab"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? PresColor.LightBlue : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Open</div></Tooltip> </div> - <div className="ribbon-doubleButton" > - <div className="presBox-subheading">Slide Duration</div> - <div className="ribbon-property"> - <input className="presBox-input" - type="number" value={duration} - onChange={action((e) => this.setDurationTime(e.target.value))} /> s + {(type === DocumentType.AUDIO || type === DocumentType.VID) ? (null) : <> + <div className="ribbon-doubleButton" > + <div className="presBox-subheading">Slide Duration</div> + <div className="ribbon-property"> + <input className="presBox-input" + type="number" value={duration} + onChange={action((e) => this.setDurationTime(e.target.value))} /> s </div> - <div className="ribbon-propertyUpDown"> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}> - <FontAwesomeIcon icon={"caret-up"} /> - </div> - <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}> - <FontAwesomeIcon icon={"caret-down"} /> + <div className="ribbon-propertyUpDown"> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}> + <FontAwesomeIcon icon={"caret-up"} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}> + <FontAwesomeIcon icon={"caret-down"} /> + </div> </div> </div> - </div> - <input type="range" step="0.1" min="0.1" max="20" value={duration} - style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} - className={"toolbar-slider"} id="duration-slider" - onPointerDown={() => { this._batch = UndoManager.StartBatch("presDuration"); }} - onPointerUp={() => { if (this._batch) this._batch.end(); }} - onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} - /> - <div className={"slider-headers"} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "grid" }}> - <div className="slider-text">Short</div> - <div className="slider-text">Medium</div> - <div className="slider-text">Long</div> - </div> + <input type="range" step="0.1" min="0.1" max="20" value={duration} + style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} + className={"toolbar-slider"} id="duration-slider" + onPointerDown={() => { this._batch = UndoManager.StartBatch("presDuration"); }} + onPointerUp={() => { if (this._batch) this._batch.end(); }} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} + /> + <div className={"slider-headers"} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "grid" }}> + <div className="slider-text">Short</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Long</div> + </div> + </>} </div> {isPresCollection ? (null) : <div className="ribbon-box"> Effects @@ -1222,20 +1241,259 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> } }); } - @computed get optionsDropdown() { + + @computed get presPinViewOptionsDropdown() { const activeItem: Doc = this.activeItem; const targetDoc: Doc = this.targetDoc; const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 16, filter: 'invert(1)' }} />; + return ( + <> + {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : (null)} + <div className="ribbon-doubleButton"> + <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? PresColor.LightBlue : "" }} + onClick={() => { + activeItem.presPinView = !activeItem.presPinView; + targetDoc.presPinView = activeItem.presPinView; + if (activeItem.presPinView) { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { + const scroll = targetDoc._scrollTop; + activeItem.presPinView = true; + activeItem.presPinViewScroll = scroll; + } else if (targetDoc.type === DocumentType.VID) { + activeItem.presPinTimecode = targetDoc._currentTimecode; + } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinView = true; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const width = targetDoc._clipWidth; + activeItem.presPinClipWidth = width; + activeItem.presPinView = true; + } + } + }}>{presPinWithViewIcon}</div></Tooltip> + {activeItem.presPinView ? <Tooltip title={<><div className="dash-tooltip">{"Update the pinned view with the view of the selected document"}</div></>}><div className="ribbon-button" + onClick={() => { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { + const scroll = targetDoc._scrollTop; + activeItem.presPinViewScroll = scroll; + } else if (targetDoc.type === DocumentType.VID) { + activeItem.presPinTimecode = targetDoc._currentTimecode; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const clipWidth = targetDoc._clipWidth; + activeItem.presPinClipWidth = clipWidth; + } else { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } + }}>Update</div></Tooltip> : (null)} + </div> + </> + ); + } + + @computed get panOptionsDropdown() { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + return ( + <> + {this.panable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Pan X</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewX)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} /> + </div> + </div> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Pan Y</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewY)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} /> + </div> + </div> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Scale</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewScale)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} /> + </div> + </div> + </div> : (null)} + </> + ); + } + + @computed get scrollOptionsDropdown() { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + return ( + <> + {this.scrollable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Scroll</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewScroll)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} /> + </div> + </div> + </div> : (null)} + </> + ); + } + + @computed get mediaOptionsDropdown() { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; if (activeItem && targetDoc) { return ( <div> <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> - <div className="ribbon-box"> + <div> + <div className="presBox-subheading">Range:</div> + <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}> + <div className="slider-text"> + Start time: + </div> + <div className="slider-text"> + Duration: + </div> + <div className="slider-text"> + End time: + </div> + </div> + <div className="presBox-subheading">Start playing:</div> + <div className="presBox-radioButtons"> + <div className="checkbox-container"> + <input className="presBox-checkbox" + type="checkbox" + onChange={() => activeItem.mediaStart = "manual"} + checked={activeItem.mediaStart === "manual"} + /> + <div>Play manually</div> + </div> + <div className="checkbox-container"> + <input className="presBox-checkbox" + type="checkbox" + onChange={() => activeItem.mediaStart = "auto"} + checked={activeItem.mediaStart === "auto"} + /> + <div>Play automatically (with slide)</div> + </div> + </div> + <div className="presBox-subheading">Stop playing:</div> + <div className="presBox-radioButtons"> + <div className="checkbox-container"> + <input className="presBox-checkbox" + type="checkbox" + onChange={() => activeItem.mediaStop = "manual"} + checked={activeItem.mediaStop === "manual"} + /> + <div>Stop at end time</div> + </div> + <div className="checkbox-container"> + <input className="presBox-checkbox" + type="checkbox" + onChange={() => activeItem.mediaStop = "afterCurrent"} + checked={activeItem.mediaStop === "afterCurrent"} + /> + <div>Stop automatically (current slide)</div> + </div> + <div className="checkbox-container"> + <input className="presBox-checkbox" + type="checkbox" + onChange={() => activeItem.mediaStop = "afterSlide"} + checked={activeItem.mediaStop === "afterSlide"} + /> + <div>Stop after slide + {activeItem.mediaStop !== "afterSlide" ? + (null) + : + <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${PresColor.DarkBlue}` : 'solid 1px black' }}> + {this.setMovementName(activeItem.presMovement, activeItem)} + <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? PresColor.DarkBlue : 'black' }} icon={"angle-down"} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>None</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>Pan {"&"} Zoom</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}>Pan</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}>Jump cut</div> + </div> + </div>} + </div> + </div> + </div> + <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}> + <div className="slider-text"> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presStartTime)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presStartTime = Number(e.target.value); })} + /> s + </div> + </div> + <div className="slider-text"> + {Math.round((NumCast(activeItem.presEndTime) - NumCast(activeItem.presStartTime)) * 10) / 10} s + </div> + <div className="slider-text"> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'right', width: 50 }} + type="number" value={NumCast(activeItem.presEndTime)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presEndTime = Number(e.target.value); })} + /> s + </div> + </div> + </div> + <div className="multiThumb-slider"> + <input type="range" step="0.1" min="0" max={activeItem.type === DocumentType.AUDIO ? Math.round(NumCast(activeItem.duration) * 10) / 10 : Math.round(NumCast(activeItem["data-duration"]) * 10) / 10} value={NumCast(activeItem.presEndTime)} + style={{ gridColumn: 1, gridRow: 1 }} + className={`toolbar-slider ${"end"}`} + id="toolbar-slider" + onPointerDown={() => this._batch = UndoManager.StartBatch("presEndTime")} + onPointerUp={() => this._batch?.end()} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + e.stopPropagation(); + activeItem.presEndTime = Number(e.target.value); + }} /> + <input type="range" step="0.1" min="0" max={activeItem.type === DocumentType.AUDIO ? Math.round(NumCast(activeItem.duration) * 10) / 10 : Math.round(NumCast(activeItem["data-duration"]) * 10) / 10} value={NumCast(activeItem.presStartTime)} + style={{ gridColumn: 1, gridRow: 1 }} + className={`toolbar-slider ${"start"}`} + id="toolbar-slider" + onPointerDown={() => this._batch = UndoManager.StartBatch("presEndTime")} + onPointerUp={() => this._batch?.end()} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + e.stopPropagation(); + activeItem.presStartTime = Number(e.target.value); + }} /> + </div> + <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}> + <div className="slider-text">0 s</div> + <div className="slider-text"></div> + <div className="slider-text">{activeItem.type === DocumentType.AUDIO ? Math.round(NumCast(activeItem.duration) * 10) / 10 : Math.round(NumCast(activeItem["data-duration"]) * 10) / 10} s</div> + </div> + </div> + {/* <div className="ribbon-box"> <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.AUDIO ? "inline-flex" : "none" }}> <div className="ribbon-toggle" style={{ backgroundColor: activeItem.playAuto ? PresColor.LightBlue : "" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play automatically</div> <div className="ribbon-toggle" style={{ display: "flex", backgroundColor: activeItem.playAuto ? "" : PresColor.LightBlue }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play on next</div> </div> - {/* {targetDoc.type === DocumentType.VID ? <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presVidFullScreen ? PresColor.LightBlue : "" }} onClick={() => activeItem.presVidFullScreen = !activeItem.presVidFullScreen}>Full screen</div> : (null)} */} {targetDoc.type === DocumentType.AUDIO ? <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> <div className="presBox-subheading">Start time</div> <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> @@ -1254,98 +1512,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presEndTime = Number(val); })} /> </div> </div> : (null)} - {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : (null)} - <div className="ribbon-doubleButton"> - <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? PresColor.LightBlue : "" }} - onClick={() => { - activeItem.presPinView = !activeItem.presPinView; - targetDoc.presPinView = activeItem.presPinView; - if (activeItem.presPinView) { - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { - const scroll = targetDoc._scrollTop; - activeItem.presPinView = true; - activeItem.presPinViewScroll = scroll; - } else if (targetDoc.type === DocumentType.VID) { - activeItem.presPinTimecode = targetDoc._currentTimecode; - } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinView = true; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const width = targetDoc._clipWidth; - activeItem.presPinClipWidth = width; - activeItem.presPinView = true; - } - } - }}>{presPinWithViewIcon}</div></Tooltip> - {activeItem.presPinView ? <Tooltip title={<><div className="dash-tooltip">{"Update the pinned view with the view of the selected document"}</div></>}><div className="ribbon-button" - onClick={() => { - if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { - const scroll = targetDoc._scrollTop; - activeItem.presPinViewScroll = scroll; - } else if (targetDoc.type === DocumentType.VID) { - activeItem.presPinTimecode = targetDoc._currentTimecode; - } else if (targetDoc.type === DocumentType.COMPARISON) { - const clipWidth = targetDoc._clipWidth; - activeItem.presPinClipWidth = clipWidth; - } else { - const x = targetDoc._panX; - const y = targetDoc._panY; - const scale = targetDoc._viewScale; - activeItem.presPinViewX = x; - activeItem.presPinViewY = y; - activeItem.presPinViewScale = scale; - } - }}>Update</div></Tooltip> : (null)} - </div> - {this.panable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Pan X</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" value={NumCast(activeItem.presPinViewX)} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} /> - </div> - </div> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Pan Y</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" value={NumCast(activeItem.presPinViewY)} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} /> - </div> - </div> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Scale</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" value={NumCast(activeItem.presPinViewScale)} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} /> - </div> - </div> - </div> : (null)} - {this.scrollable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> - <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> - <div className="presBox-subheading">Scroll</div> - <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> - <input className="presBox-input" - style={{ textAlign: 'left', width: 50 }} - type="number" value={NumCast(activeItem.presPinViewScroll)} - onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} /> - </div> - </div> - </div> : (null)} - {/* <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.WEB ? "inline-flex" : "none" }}> - <div className="ribbon-toggle" onClick={this.progressivizeText}>Store original website</div> - </div> */} - </div> + </div> */} </div> </div > ); @@ -1499,10 +1666,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return ( <div className={`dropdown-play ${this.presentTools ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> <div className="dropdown-play-button" onClick={undoBatch(action(() => { this.updateMinimize(); this.turnOffEdit(true); }))}> - Minimize + Mini-player </div> <div className="dropdown-play-button" onClick={undoBatch(action(() => { this.layoutDoc.presStatus = "manual"; this.turnOffEdit(true); }))}> - Sidebar view + Sidebar player </div> </div> ); @@ -1572,7 +1739,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> return ( <div> <div className={`presBox-ribbon ${this.progressivizeTools && this.layoutDoc.presStatus === "edit" ? "active" : ""}`} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> - {/* <div className="ribbon-box"> + <div className="ribbon-box"> {this.stringType} selected <div className="ribbon-doubleButton" style={{ borderTop: 'solid 1px darkgrey', display: (targetDoc.type === DocumentType.COL && targetDoc._viewType === 'freeform') || targetDoc.type === DocumentType.IMG || targetDoc.type === DocumentType.RTF ? "inline-flex" : "none" }}> <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeChild}>Contents</div> @@ -1598,7 +1765,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <div className="ribbon-toggle" style={{ backgroundColor: activeItem.scrollProgressivize ? PresColor.LightBlue : "" }} onClick={this.progressivizeScroll}>Scroll</div> <div className="ribbon-toggle" style={{ opacity: activeItem.scrollProgressivize ? 1 : 0.4, backgroundColor: targetDoc.editScrollProgressivize ? PresColor.LightBlue : "" }} onClick={this.editScrollProgressivize}>Edit</div> </div> - </div> */} + </div> <div className="ribbon-final-box"> Frames <div className="ribbon-doubleButton"> @@ -1926,7 +2093,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema> <FontAwesomeIcon className={`dropdown ${this.newDocumentTools ? "active" : ""}`} icon={"angle-down"} /> </div></Tooltip> */} <Tooltip title={<><div className="dash-tooltip">{"View paths"}</div></>}> - <div style={{ opacity: this.childDocs.length > 1 ? 1 : 0.3, color: this._pathBoolean ? PresColor.DarkBlue : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 ? this.viewPaths : undefined}> + <div style={{ opacity: this.childDocs.length > 1 && this.layoutDoc.presCollection ? 1 : 0.3, color: this._pathBoolean ? PresColor.DarkBlue : 'white', width: isMini ? "100%" : undefined }} className={"toolbar-button"} onClick={this.childDocs.length > 1 && this.layoutDoc.presCollection ? this.viewPaths : undefined}> <FontAwesomeIcon icon={"exchange-alt"} /> </div> </Tooltip> diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 8ce76a347..998ca839d 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -21,6 +21,8 @@ import { documentSchema } from "../../../fields/documentSchemas"; import { Networking } from "../../Network"; import { SnappingManager } from "../../util/SnappingManager"; import { SelectionManager } from "../../util/SelectionManager"; +import { LinkDocPreview } from "./LinkDocPreview"; +import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment"; const path = require('path'); export const timeSchema = createSchema({ @@ -32,8 +34,9 @@ const VideoDocument = makeInterface(documentSchema, timeSchema); @observer export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) { static _youtubeIframeCounter: number = 0; - private _reactionDisposer?: IReactionDisposer; - private _youtubeReactionDisposer?: IReactionDisposer; + // private _reactionDisposer?: IReactionDisposer; + // private _youtubeReactionDisposer?: IReactionDisposer; + private _disposers: { [name: string]: IReactionDisposer } = {}; private _youtubePlayer: YT.Player | undefined = undefined; private _videoRef: HTMLVideoElement | null = null; private _youtubeIframeId: number = -1; @@ -181,7 +184,32 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD componentDidMount() { if (this.props.setVideoBox) this.props.setVideoBox(this); - + this._disposers.videoStart = reaction( + () => this.Document._videoStart, + (videoStart) => { + if (videoStart !== undefined) { + if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { + const delay = this.player ? 0 : 250; // wait for mainCont and try again to play + setTimeout(() => this.player && this.Play(), delay); + setTimeout(() => { this.Document._videoStart = undefined; }, 10 + delay); + } + } + }, + { fireImmediately: true } + ); + this._disposers.videoStop = reaction( + () => this.Document._videoStop, + (videoStop) => { + if (videoStop !== undefined) { + if (this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc) { + const delay = this.player ? 0 : 250; // wait for mainCont and try again to play + setTimeout(() => this.player && this.Pause(), delay); + setTimeout(() => { this.Document._videoStop = undefined; }, 10 + delay); + } + } + }, + { fireImmediately: true } + ); if (this.youtubeVideoId) { const youtubeaspect = 400 / 315; const nativeWidth = Doc.NativeWidth(this.layoutDoc); @@ -196,8 +224,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD componentWillUnmount() { this.Pause(); - this._reactionDisposer?.(); - this._youtubeReactionDisposer?.(); + this._disposers.reactionDisposer?.(); + this._disposers.youtubeReactionDisposer?.(); + this._disposers.videoStart?.(); } @action @@ -207,8 +236,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD this._videoRef!.ontimeupdate = this.updateTimecode; // @ts-ignore vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen); - this._reactionDisposer?.(); - this._reactionDisposer = reaction(() => (this.layoutDoc._currentTimecode || 0), + this._disposers.reactionDisposer?.(); + this._disposers.reactionDisposer = reaction(() => (this.layoutDoc._currentTimecode || 0), time => !this._playing && (vref.currentTime = time), { fireImmediately: true }); } } @@ -293,10 +322,10 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD if (event.data === YT.PlayerState.PAUSED && this._playing) this.Pause(false); }); const onYoutubePlayerReady = (event: any) => { - this._reactionDisposer?.(); - this._youtubeReactionDisposer?.(); - this._reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek((this.layoutDoc._currentTimecode || 0))); - this._youtubeReactionDisposer = reaction( + this._disposers.reactionDisposer?.(); + this._disposers.youtubeReactionDisposer?.(); + this._disposers.reactionDisposer = reaction(() => this.layoutDoc._currentTimecode, () => !this._playing && this.Seek((this.layoutDoc._currentTimecode || 0))); + this._disposers.youtubeReactionDisposer = reaction( () => !this.props.Document.isAnnotating && Doc.GetSelectedTool() === InkTool.None && this.props.isSelected(true) && !SnappingManager.GetIsDragging() && !DocumentDecorations.Instance.Interacting, (interactive) => iframe.style.pointerEvents = interactive ? "all" : "none", { fireImmediately: true }); }; diff --git a/src/client/views/presentationview/PresElementBox.scss b/src/client/views/presentationview/PresElementBox.scss index 73a08b6de..e1eeefc30 100644 --- a/src/client/views/presentationview/PresElementBox.scss +++ b/src/client/views/presentationview/PresElementBox.scss @@ -126,14 +126,14 @@ $slide-active: #5B9FDD; display: flex; grid-column: 7; grid-row: 1/3; - width: 60px; + width: max-content; justify-self: right; justify-content: flex-end; .slideButton { cursor: pointer; position: relative; - border-radius: 100%; + border-radius: 100px; z-index: 300; width: 18px; height: 18px; @@ -156,7 +156,40 @@ $slide-active: #5B9FDD; } } - +// .presItem-slide:hover { + // .presItem-slideButtons { + // display: flex; + // grid-column: 7; + // grid-row: 1/3; + // width: max-content; + // justify-self: right; + // justify-content: flex-end; + + // .slideButton { + // cursor: pointer; + // position: relative; + // border-radius: 100px; + // z-index: 300; + // width: 18px; + // height: 18px; + // display: flex; + // font-size: 12px; + // justify-self: center; + // align-self: center; + // background-color: rgba(0, 0, 0, 0.5); + // color: white; + // justify-content: center; + // align-items: center; + // transition: 0.2s; + // margin-right: 3px; + // } + + // .slideButton:hover { + // background-color: rgba(0, 0, 0, 1); + // transform: scale(1.2); + // } + // } + // } .presItem-slide.active { box-shadow: 0 0 0px 1.5px $dark-blue; diff --git a/src/client/views/presentationview/PresElementBox.tsx b/src/client/views/presentationview/PresElementBox.tsx index 899897a6d..0d1defbfe 100644 --- a/src/client/views/presentationview/PresElementBox.tsx +++ b/src/client/views/presentationview/PresElementBox.tsx @@ -6,14 +6,14 @@ import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from "../../../fields/FieldSymbols"; import { createSchema, makeInterface } from '../../../fields/Schema'; import { Cast, NumCast, StrCast } from "../../../fields/Types"; -import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, setupMoveUpEvents } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, returnTrue, returnOne, setupMoveUpEvents, lightOrDark } from "../../../Utils"; import { Transform } from "../../util/Transform"; import { ViewBoxBaseComponent } from '../DocComponent'; import { ContentFittingDocumentView } from '../nodes/ContentFittingDocumentView'; import { FieldView, FieldViewProps } from '../nodes/FieldView'; import "./PresElementBox.scss"; import React = require("react"); -import { PresBox, PresMovement } from "../nodes/PresBox"; +import { PresBox, PresColor, PresMovement } from "../nodes/PresBox"; import { DocumentType } from "../../documents/DocumentTypes"; import { Tooltip } from "@material-ui/core"; import { DragManager } from "../../util/DragManager"; @@ -117,7 +117,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc @computed get duration() { let durationInS: number; - if (this.rootDoc.type === DocumentType.AUDIO) { durationInS = NumCast(this.rootDoc.presEndTime) - NumCast(this.rootDoc.presStartTime); durationInS = Math.round(durationInS * 10) / 10; } + 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"; @@ -284,12 +284,19 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc 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 !== "white" && presBoxColor !== "transparent") : false; const targetDoc: Doc = this.targetDoc; const activeItem: Doc = this.rootDoc; return ( - <div className={`presItem-container`} key={this.props.Document[Id] + this.indexInPres} + <div className={`presItem-container`} + key={this.props.Document[Id] + this.indexInPres} ref={this._itemRef} - style={{ backgroundColor: isSelected ? "#AEDDF8" : "rgba(0,0,0,0)", opacity: this._dragging ? 0.3 : 1 }} + style={{ + backgroundColor: presColorBool ? isSelected ? "rgba(250,250,250,0.3)" : "transparent" : isSelected ? "#AEDDF8" : "transparent", + opacity: this._dragging ? 0.3 : 1 + }} onClick={e => { e.stopPropagation(); e.preventDefault(); @@ -305,15 +312,21 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc onPointerUp={this.headerUp} > {miniView ? + // when width is LESS than 110 px <div className={`presItem-miniSlide ${isSelected ? "active" : ""}`} ref={miniView ? this._dragRef : null}> {`${this.indexInPres + 1}.`} </div> : + // when width is MORE than 110 px <div className="presItem-number"> {`${this.indexInPres + 1}.`} </div>} - {miniView ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`} style={{ backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props.renderDepth, "backgroundColor", this.props.layerProvider) }}> - <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 185) : toolbarWidth - 95, cursor: isSelected ? 'text' : 'grab' }}> + {miniView ? (null) : <div ref={miniView ? null : this._dragRef} className={`presItem-slide ${isSelected ? "active" : ""}`} + style={{ + backgroundColor: this.props.styleProvider?.(this.layoutDoc, this.props.renderDepth, "color", this.props.layerProvider), + boxShadow: presBoxColor && presBoxColor !== "white" && presBoxColor !== "transparent" ? isSelected ? "0 0 0px 1.5px" + presBoxColor : undefined : undefined + }}> + <div className="presItem-name" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105, cursor: isSelected ? 'text' : 'grab' }}> <EditableView ref={this._titleRef} editing={!isSelected ? false : undefined} @@ -331,13 +344,21 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc onClick={() => this.updateView(targetDoc, activeItem)} style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}>V</div> </Tooltip> - {/* <Tooltip title={<><div className="dash-tooltip">{"Group with up"}</div></>}> + {this.indexInPres === 0 ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Group with up"}</div></>}> <div className="slideButton" onClick={() => activeItem.groupWithUp = !activeItem.groupWithUp} - style={{ fontWeight: 700, display: activeItem.presPinView ? "flex" : "none" }}> - <FontAwesomeIcon icon={""} onPointerDown={e => e.stopPropagation()} /> + style={{ + zIndex: 1000 - this.indexInPres, + fontWeight: 700, + backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : PresColor.DarkBlue : undefined, + height: activeItem.groupWithUp ? 53 : 18, + transform: activeItem.groupWithUp ? "translate(0, -17px)" : undefined + }}> + <div style={{ transform: activeItem.groupWithUp ? "rotate(180deg) translate(0, -17.5px)" : "rotate(0deg)" }}> + <FontAwesomeIcon icon={"arrow-up"} onPointerDown={e => e.stopPropagation()} /> + </div> </div> - </Tooltip> */} + </Tooltip>} <Tooltip title={<><div className="dash-tooltip">{this.rootDoc.presExpandInlineButton ? "Minimize" : "Expand"}</div></>}><div className={"slideButton"} onClick={e => { e.stopPropagation(); this.presExpandDocumentClick(); }}> <FontAwesomeIcon icon={this.rootDoc.presExpandInlineButton ? "eye-slash" : "eye"} onPointerDown={e => e.stopPropagation()} /> </div></Tooltip> @@ -347,7 +368,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc <FontAwesomeIcon icon={"trash"} onPointerDown={e => e.stopPropagation()} /> </div></Tooltip> </div> - <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 185) : toolbarWidth - 95 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div> + <div className="presItem-docName" style={{ maxWidth: showMore ? (toolbarWidth - 195) : toolbarWidth - 105 }}>{activeItem.presPinView ? (<><i>View of </i> {targetDoc.title}</>) : targetDoc.title}</div> {this.renderEmbeddedInline} </div>} </div >); diff --git a/src/client/views/presentationview/PresProperties.tsx b/src/client/views/presentationview/PresProperties.tsx new file mode 100644 index 000000000..edad41861 --- /dev/null +++ b/src/client/views/presentationview/PresProperties.tsx @@ -0,0 +1,492 @@ +import React from "react"; +import { action, computed, observable, reaction } from "mobx"; +import { Doc } from "../../../fields/Doc"; +import { BoolCast, Cast, NumCast } from "../../../fields/Types"; +import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { PresBox, PresColor, PresEffect, PresMovement } from "../nodes/PresBox"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { CollectionViewType } from "../collections/CollectionView"; +import { Tooltip } from "@material-ui/core"; + + +export class PresProperties { + + @observable public static Instance: PresProperties; + + @computed get pres(): PresBox { return PresBox.Instance; } + + @computed get activeItem() { return Cast(this.pres.childDocs[NumCast(PresBox.Instance.rootDoc._itemIndex)], Doc, null); } + @computed get targetDoc() { return Cast(this.pres.activeItem?.presentationTargetDoc, Doc, null); } + + @computed get scrollable(): boolean { + if (this.targetDoc.type === DocumentType.PDF || this.targetDoc.type === DocumentType.WEB || this.targetDoc.type === DocumentType.RTF || this.targetDoc._viewType === CollectionViewType.Stacking) return true; + else return false; + } + @computed get panable(): boolean { + if ((this.targetDoc.type === DocumentType.COL && this.targetDoc._viewType === CollectionViewType.Freeform) || this.targetDoc.type === DocumentType.IMG) return true; + else return false; + } + + + @observable private openMovementDropdown: boolean = false; + @observable private openEffectDropdown: boolean = false; + + _batch: UndoManager.Batch | undefined = undefined; + + + @computed get effectDirection(): string { + let effect = ''; + switch (this.targetDoc.presEffectDirection) { + case 'left': effect = "Enter from left"; break; + case 'right': effect = "Enter from right"; break; + case 'top': effect = "Enter from top"; break; + case 'bottom': effect = "Enter from bottom"; break; + default: effect = "Enter from center"; break; + } + return effect; + } + + @undoBatch + @action + updateHideBefore = (activeItem: Doc) => { + activeItem.presHideBefore = !activeItem.presHideBefore; + Array.from(this.pres._selectedArray.keys()).forEach((doc) => doc.presHideBefore = activeItem.presHideBefore); + } + + @undoBatch + @action + updateHideAfter = (activeItem: Doc) => { + activeItem.presHideAfter = !activeItem.presHideAfter; + Array.from(this.pres._selectedArray.keys()).forEach((doc) => doc.presHideAfter = activeItem.presHideAfter); + } + + @undoBatch + @action + updateOpenDoc = (activeItem: Doc) => { + activeItem.openDocument = !activeItem.openDocument; + Array.from(this.pres._selectedArray.keys()).forEach((doc) => { + doc.openDocument = activeItem.openDocument; + }); + } + + /** + * When the movement dropdown is changes + */ + @undoBatch + updateMovement = action((movement: any, all?: boolean) => { + const array: any[] = all ? this.pres.childDocs : Array.from(this.pres._selectedArray.keys()); + array.forEach((doc) => { + switch (movement) { + case PresMovement.Zoom: //Pan and zoom + doc.presMovement = PresMovement.Zoom; + break; + case PresMovement.Pan: //Pan + doc.presMovement = PresMovement.Pan; + break; + case PresMovement.Jump: //Jump Cut + doc.presJump = true; + doc.presMovement = PresMovement.Jump; + break; + case PresMovement.None: default: + doc.presMovement = PresMovement.None; + break; + } + }); + }); + + @undoBatch + @action + updateEffect = (effect: any, all?: boolean) => { + const array: any[] = all ? this.pres.childDocs : Array.from(this.pres._selectedArray.keys()); + array.forEach((doc) => { + const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); + switch (effect) { + case PresEffect.Bounce: + tagDoc.presEffect = PresEffect.Bounce; + break; + case PresEffect.Fade: + tagDoc.presEffect = PresEffect.Fade; + break; + case PresEffect.Flip: + tagDoc.presEffect = PresEffect.Flip; + break; + case PresEffect.Roll: + tagDoc.presEffect = PresEffect.Roll; + break; + case PresEffect.Rotate: + tagDoc.presEffect = PresEffect.Rotate; + break; + case PresEffect.None: default: + tagDoc.presEffect = PresEffect.None; + break; + } + }); + } + + @undoBatch + @action + updateEffectDirection = (effect: any, all?: boolean) => { + const array: any[] = all ? this.pres.childDocs : Array.from(this.pres._selectedArray.keys()); + array.forEach((doc) => { + const tagDoc = Cast(doc.presentationTargetDoc, Doc, null); + switch (effect) { + case PresEffect.Left: + tagDoc.presEffectDirection = PresEffect.Left; + break; + case PresEffect.Right: + tagDoc.presEffectDirection = PresEffect.Right; + break; + case PresEffect.Top: + tagDoc.presEffectDirection = PresEffect.Top; + break; + case PresEffect.Bottom: + tagDoc.presEffectDirection = PresEffect.Bottom; + break; + case PresEffect.Center: default: + tagDoc.presEffectDirection = PresEffect.Center; + break; + } + }); + } + + // Converts seconds to ms and updates presTransition + setTransitionTime = (number: String, change?: number) => { + let timeInMS = Number(number) * 1000; + if (change) timeInMS += change; + if (timeInMS < 100) timeInMS = 100; + if (timeInMS > 10000) timeInMS = 10000; + Array.from(PresBox.Instance._selectedArray.keys()).forEach((doc) => doc.presTransition = timeInMS); + } + + // Converts seconds to ms and updates presDuration + setDurationTime = (number: String, change?: number) => { + let timeInMS = Number(number) * 1000; + if (change) timeInMS += change; + if (timeInMS < 100) timeInMS = 100; + if (timeInMS > 20000) timeInMS = 20000; + Array.from(PresBox.Instance._selectedArray.keys()).forEach((doc) => doc.presDuration = timeInMS); + } + + setMovementName = action((movement: any, activeItem: Doc): string => { + let output: string = 'none'; + switch (movement) { + case PresMovement.Zoom: output = 'Pan & Zoom'; break; //Pan and zoom + case PresMovement.Pan: output = 'Pan'; break; //Pan + case PresMovement.Jump: output = 'Jump cut'; break; //Jump Cut + case PresMovement.None: output = 'No Movement'; break; //None + default: output = 'Zoom'; activeItem.presMovement = 'zoom'; break; //default set as zoom + } + return output; + }); + + @computed get transitionDropdown() { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + const isPresCollection: boolean = (targetDoc === this.pres.layoutDoc.presCollection); + const isPinWithView: boolean = BoolCast(activeItem.presPinView); + if (activeItem && targetDoc) { + const transitionSpeed = activeItem.presTransition ? NumCast(activeItem.presTransition) / 1000 : 0.5; + let duration = activeItem.presDuration ? NumCast(activeItem.presDuration) / 1000 : 2; + if (activeItem.type === DocumentType.AUDIO) duration = NumCast(activeItem.duration); + const effect = targetDoc.presEffect ? targetDoc.presEffect : 'None'; + activeItem.presMovement = activeItem.presMovement ? activeItem.presMovement : 'Zoom'; + return ( + <div className={`presBox-ribbon ${this.pres.layoutDoc.presStatus === "edit" ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = false; this.openEffectDropdown = false; })}> + <div className="ribbon-box"> + Movement + {isPresCollection || (isPresCollection && isPinWithView) ? + <div className="ribbon-property" style={{ marginLeft: 0, height: 25, textAlign: 'left', paddingLeft: 5, paddingRight: 5, fontSize: 10 }}> + {this.scrollable ? "Scroll to pinned view" : !isPinWithView ? "No movement" : "Pan & Zoom to pinned view"} + </div> + : + <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openMovementDropdown = !this.openMovementDropdown; })} style={{ borderBottomLeftRadius: this.openMovementDropdown ? 0 : 5, border: this.openMovementDropdown ? `solid 2px ${PresColor.DarkBlue}` : 'solid 1px black' }}> + {this.setMovementName(activeItem.presMovement, activeItem)} + <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openMovementDropdown ? PresColor.DarkBlue : 'black' }} icon={"angle-down"} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} onPointerDown={e => e.stopPropagation()} style={{ display: this.openMovementDropdown ? "grid" : "none" }}> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.None ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.None)}>None</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Zoom ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Zoom)}>Pan {"&"} Zoom</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Pan ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Pan)}>Pan</div> + <div className={`presBox-dropdownOption ${activeItem.presMovement === PresMovement.Jump ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateMovement(PresMovement.Jump)}>Jump cut</div> + </div> + </div> + } + <div className="ribbon-doubleButton" style={{ display: activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "inline-flex" : "none" }}> + <div className="presBox-subheading">Transition Speed</div> + <div className="ribbon-property"> + <input className="presBox-input" + type="number" value={transitionSpeed} + onChange={action((e) => this.setTransitionTime(e.target.value))} /> s + </div> + <div className="ribbon-propertyUpDown"> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), 1000))}> + <FontAwesomeIcon icon={"caret-up"} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setTransitionTime(String(transitionSpeed), -1000))}> + <FontAwesomeIcon icon={"caret-down"} /> + </div> + </div> + </div> + <input type="range" step="0.1" min="0.1" max="10" value={transitionSpeed} + className={`toolbar-slider ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`} + id="toolbar-slider" + onPointerDown={() => this._batch = UndoManager.StartBatch("presTransition")} + onPointerUp={() => this._batch?.end()} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { + e.stopPropagation(); + this.setTransitionTime(e.target.value); + }} /> + <div className={`slider-headers ${activeItem.presMovement === PresMovement.Pan || activeItem.presMovement === PresMovement.Zoom ? "" : "none"}`}> + <div className="slider-text">Fast</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Slow</div> + </div> + </div> + <div className="ribbon-box"> + Visibility {"&"} Duration + <div className="ribbon-doubleButton"> + {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide before presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideBefore ? "active" : ""}`} onClick={() => this.updateHideBefore(activeItem)}>Hide before</div></Tooltip>} + {isPresCollection ? (null) : <Tooltip title={<><div className="dash-tooltip">{"Hide after presented"}</div></>}><div className={`ribbon-toggle ${activeItem.presHideAfter ? "active" : ""}`} onClick={() => this.updateHideAfter(activeItem)}>Hide after</div></Tooltip>} + <Tooltip title={<><div className="dash-tooltip">{"Open document in a new tab"}</div></>}><div className="ribbon-toggle" style={{ backgroundColor: activeItem.openDocument ? PresColor.LightBlue : "" }} onClick={() => this.updateOpenDoc(activeItem)}>Open</div></Tooltip> + </div> + <div className="ribbon-doubleButton" > + <div className="presBox-subheading">Slide Duration</div> + <div className="ribbon-property"> + <input className="presBox-input" + type="number" value={duration} + onChange={action((e) => this.setDurationTime(e.target.value))} /> s + </div> + <div className="ribbon-propertyUpDown"> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), 1000))}> + <FontAwesomeIcon icon={"caret-up"} /> + </div> + <div className="ribbon-propertyUpDownItem" onClick={undoBatch(() => this.setDurationTime(String(duration), -1000))}> + <FontAwesomeIcon icon={"caret-down"} /> + </div> + </div> + </div> + <input type="range" step="0.1" min="0.1" max="20" value={duration} + style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "block" }} + className={"toolbar-slider"} id="duration-slider" + onPointerDown={() => { this._batch = UndoManager.StartBatch("presDuration"); }} + onPointerUp={() => { if (this._batch) this._batch.end(); }} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { e.stopPropagation(); this.setDurationTime(e.target.value); }} + /> + <div className={"slider-headers"} style={{ display: targetDoc.type === DocumentType.AUDIO ? "none" : "grid" }}> + <div className="slider-text">Short</div> + <div className="slider-text">Medium</div> + <div className="slider-text">Long</div> + </div> + </div> + {isPresCollection ? (null) : <div className="ribbon-box"> + Effects + <div className="presBox-dropdown" onClick={action(e => { e.stopPropagation(); this.openEffectDropdown = !this.openEffectDropdown; })} style={{ borderBottomLeftRadius: this.openEffectDropdown ? 0 : 5, border: this.openEffectDropdown ? `solid 2px ${PresColor.DarkBlue}` : 'solid 1px black' }}> + {effect} + <FontAwesomeIcon className='presBox-dropdownIcon' style={{ gridColumn: 2, color: this.openEffectDropdown ? PresColor.DarkBlue : 'black' }} icon={"angle-down"} /> + <div className={'presBox-dropdownOptions'} id={'presBoxMovementDropdown'} style={{ display: this.openEffectDropdown ? "grid" : "none" }} onPointerDown={e => e.stopPropagation()}> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.None || !targetDoc.presEffect ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.None)}>None</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Fade ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Fade)}>Fade In</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Flip ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Flip)}>Flip</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Rotate ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Rotate)}>Rotate</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Bounce ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Bounce)}>Bounce</div> + <div className={`presBox-dropdownOption ${targetDoc.presEffect === PresEffect.Roll ? "active" : ""}`} onPointerDown={e => e.stopPropagation()} onClick={() => this.updateEffect(PresEffect.Roll)}>Roll</div> + </div> + </div> + <div className="ribbon-doubleButton" style={{ display: effect === 'None' ? "none" : "inline-flex" }}> + <div className="presBox-subheading" >Effect direction</div> + <div className="ribbon-property"> + {this.effectDirection} + </div> + </div> + <div className="effectDirection" style={{ display: effect === 'None' ? "none" : "grid", width: 40 }}> + <Tooltip title={<><div className="dash-tooltip">{"Enter from left"}</div></>}><div style={{ gridColumn: 1, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Left ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Left)}><FontAwesomeIcon icon={"angle-right"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from right"}</div></>}><div style={{ gridColumn: 3, gridRow: 2, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Right ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Right)}><FontAwesomeIcon icon={"angle-left"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from top"}</div></>}><div style={{ gridColumn: 2, gridRow: 1, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Top ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Top)}><FontAwesomeIcon icon={"angle-down"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from bottom"}</div></>}><div style={{ gridColumn: 2, gridRow: 3, justifySelf: 'center', color: targetDoc.presEffectDirection === PresEffect.Bottom ? PresColor.LightBlue : "black", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Bottom)}><FontAwesomeIcon icon={"angle-up"} /></div></Tooltip> + <Tooltip title={<><div className="dash-tooltip">{"Enter from center"}</div></>}><div style={{ gridColumn: 2, gridRow: 2, width: 10, height: 10, alignSelf: 'center', justifySelf: 'center', border: targetDoc.presEffectDirection === PresEffect.Center || !targetDoc.presEffectDirection ? `solid 2px ${PresColor.LightBlue}` : "solid 2px black", borderRadius: "100%", cursor: "pointer" }} onClick={() => this.updateEffectDirection(PresEffect.Center)}></div></Tooltip> + </div> + </div>} + <div className="ribbon-final-box"> + <div className="ribbon-final-button-hidden" onClick={() => this.applyTo(this.pres.childDocs)}> + Apply to all + </div> + </div> + </div > + ); + } + } + + @undoBatch + @action + applyTo = (array: Doc[]) => { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + this.updateMovement(activeItem.presMovement, true); + this.updateEffect(targetDoc.presEffect, true); + this.updateEffectDirection(targetDoc.presEffectDirection, true); + array.forEach((doc) => { + const curDoc = Cast(doc, Doc, null); + const tagDoc = Cast(curDoc.presentationTargetDoc, Doc, null); + if (tagDoc && targetDoc) { + curDoc.presTransition = activeItem.presTransition; + curDoc.presDuration = activeItem.presDuration; + curDoc.presHideBefore = activeItem.presHideBefore; + curDoc.presHideAfter = activeItem.presHideAfter; + } + }); + } + + @computed get mediaOptionsDropdown() { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + if (activeItem && targetDoc) { + return ( + <div> + <div className={'presBox-ribbon'} onClick={e => e.stopPropagation()} onPointerUp={e => e.stopPropagation()} onPointerDown={e => e.stopPropagation()}> + <div className="ribbon-box"> + <div className="ribbon-doubleButton" style={{ display: targetDoc.type === DocumentType.AUDIO ? "inline-flex" : "none" }}> + <div className="ribbon-toggle" style={{ backgroundColor: activeItem.playAuto ? PresColor.LightBlue : "" }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play automatically</div> + <div className="ribbon-toggle" style={{ display: "flex", backgroundColor: activeItem.playAuto ? "" : PresColor.LightBlue }} onClick={() => activeItem.playAuto = !activeItem.playAuto}>Play on next</div> + </div> + {/* {targetDoc.type === DocumentType.VID ? <div className="ribbon-toggle" style={{ backgroundColor: activeItem.presVidFullScreen ? PresColor.LightBlue : "" }} onClick={() => activeItem.presVidFullScreen = !activeItem.presVidFullScreen}>Full screen</div> : (null)} */} + {targetDoc.type === DocumentType.AUDIO ? <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Start time</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presStartTime)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { activeItem.presStartTime = Number(e.target.value); })} /> + </div> + </div> : (null)} + {targetDoc.type === DocumentType.AUDIO ? <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">End time</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presEndTime)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presEndTime = Number(val); })} /> + </div> + </div> : (null)} + </div> + </div> + </div > + ); + } + } + + @computed get presPinViewOptionsDropdown() { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + const presPinWithViewIcon = <img src="/assets/pinWithView.png" style={{ margin: "auto", width: 16, filter: 'invert(1)' }} />; + return ( + <> + {this.panable || this.scrollable || this.targetDoc.type === DocumentType.COMPARISON ? 'Pinned view' : (null)} + <div className="ribbon-doubleButton"> + <Tooltip title={<><div className="dash-tooltip">{activeItem.presPinView ? "Turn off pin with view" : "Turn on pin with view"}</div></>}><div className="ribbon-toggle" style={{ width: 20, padding: 0, backgroundColor: activeItem.presPinView ? PresColor.LightBlue : "" }} + onClick={() => { + activeItem.presPinView = !activeItem.presPinView; + targetDoc.presPinView = activeItem.presPinView; + if (activeItem.presPinView) { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.RTF || targetDoc.type === DocumentType.WEB || targetDoc._viewType === CollectionViewType.Stacking) { + const scroll = targetDoc._scrollTop; + activeItem.presPinView = true; + activeItem.presPinViewScroll = scroll; + } else if (targetDoc.type === DocumentType.VID) { + activeItem.presPinTimecode = targetDoc._currentTimecode; + } else if ((targetDoc.type === DocumentType.COL && targetDoc._viewType === CollectionViewType.Freeform) || targetDoc.type === DocumentType.IMG) { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinView = true; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const width = targetDoc._clipWidth; + activeItem.presPinClipWidth = width; + activeItem.presPinView = true; + } + } + }}>{presPinWithViewIcon}</div></Tooltip> + {activeItem.presPinView ? <Tooltip title={<><div className="dash-tooltip">{"Update the pinned view with the view of the selected document"}</div></>}><div className="ribbon-button" + onClick={() => { + if (targetDoc.type === DocumentType.PDF || targetDoc.type === DocumentType.WEB || targetDoc.type === DocumentType.RTF) { + const scroll = targetDoc._scrollTop; + activeItem.presPinViewScroll = scroll; + } else if (targetDoc.type === DocumentType.VID) { + activeItem.presPinTimecode = targetDoc._currentTimecode; + } else if (targetDoc.type === DocumentType.COMPARISON) { + const clipWidth = targetDoc._clipWidth; + activeItem.presPinClipWidth = clipWidth; + } else { + const x = targetDoc._panX; + const y = targetDoc._panY; + const scale = targetDoc._viewScale; + activeItem.presPinViewX = x; + activeItem.presPinViewY = y; + activeItem.presPinViewScale = scale; + } + }}>Update</div></Tooltip> : (null)} + </div> + </> + ); + } + + @computed get panOptionsDropdown() { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + return ( + <> + {this.panable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Pan X</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewX)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewX = Number(val); })} /> + </div> + </div> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Pan Y</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewY)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewY = Number(val); })} /> + </div> + </div> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Scale</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewScale)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScale = Number(val); })} /> + </div> + </div> + </div> : (null)} + </> + ); + } + + @computed get scrollOptionsDropdown() { + const activeItem: Doc = this.activeItem; + const targetDoc: Doc = this.targetDoc; + return ( + <> + {this.scrollable ? <div style={{ display: activeItem.presPinView ? "block" : "none" }}> + <div className="ribbon-doubleButton" style={{ marginRight: 10 }}> + <div className="presBox-subheading">Scroll</div> + <div className="ribbon-property" style={{ paddingRight: 0, paddingLeft: 0 }}> + <input className="presBox-input" + style={{ textAlign: 'left', width: 50 }} + type="number" value={NumCast(activeItem.presPinViewScroll)} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => { const val = e.target.value; activeItem.presPinViewScroll = Number(val); })} /> + </div> + </div> + </div> : (null)} + </> + ); + } +}
\ No newline at end of file |
