aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorgeireann <60007097+geireann@users.noreply.github.com>2020-11-29 11:15:30 +0800
committergeireann <60007097+geireann@users.noreply.github.com>2020-11-29 11:15:30 +0800
commit534a4e81e84adbd46b629731ac3ca7d3ea41a670 (patch)
tree9c15a85b1f652dd378c21e6f5260346da45b7e2a /src
parentc15513601495e70c8e4161308814e72972d51367 (diff)
temporal media updates for pres. trails
Diffstat (limited to 'src')
-rw-r--r--src/client/views/MainView.tsx8
-rw-r--r--src/client/views/PropertiesView.tsx8
-rw-r--r--src/client/views/nodes/AudioBox.tsx22
-rw-r--r--src/client/views/nodes/PresBox.scss83
-rw-r--r--src/client/views/nodes/PresBox.tsx323
-rw-r--r--src/client/views/nodes/VideoBox.tsx51
-rw-r--r--src/client/views/presentationview/PresElementBox.scss41
-rw-r--r--src/client/views/presentationview/PresElementBox.tsx3
-rw-r--r--src/client/views/presentationview/PresProperties.tsx492
9 files changed, 899 insertions, 132 deletions
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
index 03d2e33df..b1e008dad 100644
--- a/src/client/views/MainView.tsx
+++ b/src/client/views/MainView.tsx
@@ -87,11 +87,11 @@ 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) {
- setTimeout(() => {
- // remove from DOM
- ele.outerHTML = '';
- }, 0);
+ // remove from DOM
+ setTimeout(() => { ele.style.opacity = "0"; }, 0);
+ setTimeout(() => ele.outerHTML = '', 10000);
}
new InkStrokeProperties();
this._sidebarContent.proto = undefined;
diff --git a/src/client/views/PropertiesView.tsx b/src/client/views/PropertiesView.tsx
index 97bca110b..c656a020e 100644
--- a/src/client/views/PropertiesView.tsx
+++ b/src/client/views/PropertiesView.tsx
@@ -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" : "" }}>
- &nbsp; <FontAwesomeIcon icon={"rocket"} /> &nbsp; Transitions
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: "center" }} icon={"rocket"} /> &nbsp; Transitions
<div className="propertiesView-presTrails-title-icon">
<FontAwesomeIcon icon={this.openPresTransitions ? "caret-down" : "caret-right"} size="lg" color="white" />
</div>
@@ -1029,17 +1029,17 @@ export class PropertiesView extends React.Component<PropertiesViewProps> {
{PresBox.Instance.progressivizeDropdown}
</div> : null}
</div>} */}
- {!selectedItem ? (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.openSlideOptions = !this.openSlideOptions; })}
style={{ backgroundColor: this.openSlideOptions ? "black" : "" }}>
- &nbsp; <FontAwesomeIcon icon={"cog"} /> &nbsp; {scrollable ? "Scroll options" : "Pan options"}
+ &nbsp; <FontAwesomeIcon style={{ alignSelf: "center" }} icon={type === DocumentType.AUDIO ? "file-audio" : "file-video"} /> &nbsp; {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 className="propertiesView-presTrails">
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 1bdba7f9e..2527a2db1 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -48,9 +48,9 @@ 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;
@@ -151,7 +151,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
});
this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
- this._disposers._audioStart = reaction(
+ this._disposers.audioStart = reaction(
() => this.Document._audioStart,
(audioStart) => {
if (audioStart !== undefined) {
@@ -165,6 +165,20 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
},
{ 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 56b3b0593..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;
diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx
index 1405016ae..9fb07040d 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";
@@ -193,6 +192,10 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
nextAudioVideo = (targetDoc: Doc, activeItem: Doc) => {
if (targetDoc.type === DocumentType.AUDIO) {
targetDoc._audioStart = NumCast(activeItem.presStartTime);
+
+ } else if (targetDoc.type === DocumentType.VID) {
+ targetDoc._currentTimecode = NumCast(activeItem.presStartTime);
+ setTimeout(() => targetDoc._videoStart = true, 0);
}
// if (targetDoc.type === DocumentType.VID) { VideoBox.Instance.Play() };
activeItem.playNow = false;
@@ -214,7 +217,6 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
// 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) {
if (targetNext.type === DocumentType.AUDIO) targetNext._audioStart = 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'); }
}
@@ -1071,6 +1073,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
@computed get transitionDropdown() {
const activeItem: Doc = this.activeItem;
const targetDoc: Doc = this.targetDoc;
+ const type: DocumentType = targetDoc.type;
const isPresCollection: boolean = (targetDoc === this.layoutDoc.presCollection);
const isPinWithView: boolean = BoolCast(activeItem.presPinView);
if (activeItem && targetDoc) {
@@ -1137,7 +1140,7 @@ 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" >
+ {(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"
@@ -1152,7 +1155,7 @@ export class PresBox extends ViewBoxBaseComponent<FieldViewProps, PresBoxSchema>
<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"
@@ -1235,20 +1238,233 @@ 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">Start playing:</div>
+ <div className="presBox-radioButtons">
+ <div className="checkbox-container">
+ <input className="presBox-checkbox"
+ type="checkbox"
+ onChange={() => activeItem.mediaPlay = "manual"}
+ checked={activeItem.mediaPlay === "manual"}
+ />
+ <div>Start manually</div>
+ </div>
+ <div className="checkbox-container">
+ <input className="presBox-checkbox"
+ type="checkbox"
+ onChange={() => activeItem.mediaPlay = "auto"}
+ checked={activeItem.mediaPlay === "auto"}
+ />
+ <div>Play with slide</div>
+ </div>
+ <div className="checkbox-container">
+ <input className="presBox-checkbox"
+ type="checkbox"
+ onChange={() => activeItem.mediaPlay = "onClick"}
+ checked={activeItem.mediaPlay === "onClick"}
+ />
+ <div>Play on click</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 manually</div>
+ </div>
+ <div className="checkbox-container">
+ <input className="presBox-checkbox"
+ type="checkbox"
+ onChange={() => activeItem.mediaStop = "afterCurrent"}
+ checked={activeItem.mediaStop === "afterCurrent"}
+ />
+ <div>After current slide</div>
+ </div>
+ <div className="checkbox-container">
+ <input className="presBox-checkbox"
+ type="checkbox"
+ onChange={() => activeItem.mediaStop = "afterSlide"}
+ checked={activeItem.mediaStop === "afterSlide"}
+ />
+ <div>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="presBox-subheading">Range:</div>
+ <div className="multiThumb-slider">
+ <input type="range" step="0.1" min="0" max={NumCast(activeItem.duration)} 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={NumCast(activeItem.duration)} 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">{Math.round(NumCast(activeItem.duration))} 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 }}>
@@ -1267,98 +1483,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 >
);
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 ed1aa9a1e..e1eeefc30 100644
--- a/src/client/views/presentationview/PresElementBox.scss
+++ b/src/client/views/presentationview/PresElementBox.scss
@@ -123,12 +123,6 @@ $slide-active: #5B9FDD;
.presItem-slideButtons {
- display: none;
- }
-}
-
-.presItem-slide:hover {
- .presItem-slideButtons {
display: flex;
grid-column: 7;
grid-row: 1/3;
@@ -162,6 +156,41 @@ $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 463249ab8..9ec39b792 100644
--- a/src/client/views/presentationview/PresElementBox.tsx
+++ b/src/client/views/presentationview/PresElementBox.tsx
@@ -348,6 +348,7 @@ export class PresElementBox extends ViewBoxBaseComponent<FieldViewProps, PresDoc
<div className="slideButton"
onClick={() => activeItem.groupWithUp = !activeItem.groupWithUp}
style={{
+ zIndex: 1000 - this.indexInPres,
fontWeight: 700,
backgroundColor: activeItem.groupWithUp ? presColorBool ? presBoxColor : PresColor.DarkBlue : undefined,
height: activeItem.groupWithUp ? 53 : 18,
@@ -367,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