aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/nodes/VideoBox.tsx
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/nodes/VideoBox.tsx')
-rw-r--r--src/client/views/nodes/VideoBox.tsx326
1 files changed, 216 insertions, 110 deletions
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 55a9818ad..bfac7dc1c 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -3,31 +3,30 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked } from "mobx";
import { observer } from "mobx-react";
import * as rp from 'request-promise';
-import { Doc } from "../../../fields/Doc";
+import { Dictionary } from "typescript-collections";
+import { Doc, DocListCast } from "../../../fields/Doc";
+import { documentSchema } from "../../../fields/documentSchemas";
import { InkTool } from "../../../fields/InkField";
import { createSchema, makeInterface } from "../../../fields/Schema";
-import { Cast, StrCast, NumCast } from "../../../fields/Types";
+import { Cast, NumCast, StrCast } from "../../../fields/Types";
import { VideoField } from "../../../fields/URLField";
-import { Utils, emptyFunction, returnOne, returnZero, OmitKeys } from "../../../Utils";
+import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils } from "../../../Utils";
import { Docs, DocUtils } from "../../documents/Documents";
+import { Networking } from "../../Network";
+import { SelectionManager } from "../../util/SelectionManager";
+import { SnappingManager } from "../../util/SnappingManager";
import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CollectionStackedTimeline } from "../collections/CollectionStackedTimeline";
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from "../ContextMenuItem";
import { ViewBoxAnnotatableComponent } from "../DocComponent";
import { DocumentDecorations } from "../DocumentDecorations";
+import { MarqueeAnnotator } from "../MarqueeAnnotator";
+import { StyleProp } from "../StyleProvider";
import { FieldView, FieldViewProps } from './FieldView';
-import "./VideoBox.scss";
-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";
-import { Transform } from "../../util/Transform";
-import { StyleProp } from "../StyleProvider";
-import { Dictionary } from "typescript-collections";
-import { MarqueeAnnotator } from "../MarqueeAnnotator";
-import { AnchorMenu } from "../pdf/AnchorMenu";
+import { LinkDocPreview } from "./LinkDocPreview";
+import "./VideoBox.scss";
const path = require('path');
export const timeSchema = createSchema({
@@ -38,26 +37,50 @@ const VideoDocument = makeInterface(documentSchema, timeSchema);
@observer
export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoDocument>(VideoDocument) {
+ public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); }
static _youtubeIframeCounter: number = 0;
+ static Instance: VideoBox;
+ static heightPercent = 60; // height of timeline in percent of height of videoBox.
private _disposers: { [name: string]: IReactionDisposer } = {};
private _youtubePlayer: YT.Player | undefined = undefined;
private _videoRef: HTMLVideoElement | null = null;
private _youtubeIframeId: number = -1;
private _youtubeContentCreated = false;
- private _isResetClick = 0;
+ private _stackedTimeline = React.createRef<CollectionStackedTimeline>();
private _mainCont: React.RefObject<HTMLDivElement> = React.createRef();
private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef();
- @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+ private _playRegionTimer: any = null;
+ private _playRegionDuration = 0;
+ @observable static _showControls: boolean;
@observable _marqueeing: number[] | undefined;
+ @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>();
+ @observable _screenCapture = false;
+ @observable _visible: boolean = false;
@observable _forceCreateYouTubeIFrame = false;
@observable _playTimer?: NodeJS.Timeout = undefined;
@observable _fullScreen = false;
@observable _playing = false;
- @observable static _showControls: boolean;
- public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); }
+ @computed get links() { return DocListCast(this.dataDoc.links); }
+ @computed get heightPercent() { return this.layoutDoc._timelineShow ? NumCast(this.layoutDoc._videoTimelineHeightPercent, VideoBox.heightPercent) : 100; }
+ @computed get duration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
+ @computed get anchorDocs() { return DocListCast(this.dataDoc[this.annotationKey + "-timeline"]).concat(DocListCast(this.dataDoc[this.annotationKey])); }
- public get player(): HTMLVideoElement | null {
- return this._videoRef;
+ public get player(): HTMLVideoElement | null { return this._videoRef; }
+
+ constructor(props: Readonly<FieldViewProps>) {
+ super(props);
+ VideoBox.Instance = this;
+ }
+
+ anchorStart = (anchor: Doc) => NumCast(anchor.anchorStartTime, NumCast(anchor._timecodeToShow, NumCast(anchor.videoStart)));
+ anchorEnd = (anchor: Doc, defaultVal: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor._timecodeToHide, NumCast(anchor.videoEnd, defaultVal)));
+
+ getAnchor = () => {
+ return this._stackedTimeline.current?.createAnchor(Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc;
+ }
+
+ choosePath(url: string) {
+ return url.indexOf(window.location.origin) === -1 ? Utils.CorsProxy(url) : url;
}
videoLoad = () => {
@@ -68,7 +91,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this.dataDoc[this.fieldKey + "-duration"] = this.player!.duration;
}
+ static keyEventsWrapper = (e: KeyboardEvent) => {
+ VideoBox.Instance._stackedTimeline.current?.keyEvents(e);
+ }
+
@action public Play = (update: boolean = true) => {
+ document.removeEventListener("keydown", VideoBox.keyEventsWrapper, true);
+ document.addEventListener("keydown", VideoBox.keyEventsWrapper, true);
this._playing = true;
try {
update && this.player?.play();
@@ -114,13 +143,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
}
- choosePath(url: string) {
- if (url.indexOf(window.location.origin) === -1) {
- return Utils.CorsProxy(url);
- }
- return url;
- }
-
@action public Snapshot() {
const width = (this.layoutDoc._width || 0);
const height = (this.layoutDoc._height || 0);
@@ -190,39 +212,30 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
componentDidMount() {
+ this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
+
this._disposers.selection = reaction(() => this.props.isSelected(),
selected => {
if (!selected) {
this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove()));
this._savedAnnotations.clear();
- AnchorMenu.Instance.fadeOut(true);
}
},
{ fireImmediately: true });
- 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);
- }
- }
- },
+ this._disposers.triggerVideo = reaction(
+ () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerVideo, null) : undefined,
+ time => time !== undefined && setTimeout(() => {
+ this.player && this.Play();
+ setTimeout(() => this.Document._triggerVideo = undefined, 10);
+ }, this.player ? 0 : 250), // wait for mainCont and try again to play
{ 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);
- }
- }
- },
+ this._disposers.triggerStop = reaction(
+ () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? NumCast(this.Document._triggerVideoStop, null) : undefined,
+ stop => stop !== undefined && setTimeout(() => {
+ this.player && this.Pause();
+ setTimeout(() => this.Document._triggerVideoStop = undefined, 10);
+ }, this.player ? 0 : 250), // wait for mainCont and try again to play
{ fireImmediately: true }
);
if (this.youtubeVideoId) {
@@ -239,7 +252,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
componentWillUnmount() {
this.Pause();
- Object.values(this._disposers).forEach(disposer => disposer?.());
+ Object.keys(this._disposers).forEach(d => this._disposers[d]?.());
+ document.removeEventListener("keydown", VideoBox.keyEventsWrapper, true);
}
@action
@@ -271,7 +285,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
console.log("VideoBox :" + e);
}
}
- @observable _screenCapture = false;
+
specificContextMenu = (e: React.MouseEvent): void => {
const field = Cast(this.dataDoc[this.props.fieldKey], VideoField);
if (field) {
@@ -286,6 +300,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
this._videoRef!.srcObject = !this._screenCapture ? undefined : await (navigator.mediaDevices as any).getDisplayMedia({ video: true });
}), icon: "expand-arrows-alt"
});
+ subitems.push({ description: (this.layoutDoc.playOnSelect ? "Don't play" : "Play") + " when link is selected", event: () => this.layoutDoc.playOnSelect = !this.layoutDoc.playOnSelect, icon: "expand-arrows-alt" });
+ subitems.push({ description: (this.layoutDoc.autoPlay ? "Don't auto play" : "Auto play") + " anchors onClick", event: () => this.layoutDoc.autoPlay = !this.layoutDoc.autoPlay, icon: "expand-arrows-alt" });
ContextMenu.Instance.addItem({ description: "Options...", subitems: subitems, icon: "video" });
}
}
@@ -295,17 +311,21 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
const interactive = Doc.GetSelectedTool() !== InkTool.None || !this.props.isSelected() ? "" : "-interactive";
const style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive;
return !field ? <div>Loading</div> :
- <video className={`${style}`} key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
- style={{ width: this._screenCapture ? "100%" : undefined, height: this._screenCapture ? "100%" : undefined }}
- onCanPlay={this.videoLoad}
- controls={VideoBox._showControls}
- onPlay={() => this.Play()}
- onSeeked={this.updateTimecode}
- onPause={() => this.Pause()}
- onClick={e => e.preventDefault()}>
- <source src={field.url.href} type="video/mp4" />
- Not supported.
- </video>;
+ <div className="container" style={{ pointerEvents: this._isChildActive || this.active() ? "all" : "none" }}>
+ <div className={`${style}`} style={{ width: "100%", height: "100%", left: "0px" }}>
+ <video key="video" autoPlay={this._screenCapture} ref={this.setVideoRef}
+ style={{ height: "100%", width: "auto", display: "flex", margin: "auto" }}
+ onCanPlay={this.videoLoad}
+ controls={VideoBox._showControls}
+ onPlay={() => this.Play()}
+ onSeeked={this.updateTimecode}
+ onPause={() => this.Pause()}
+ onClick={e => e.preventDefault()}>
+ <source src={field.url.href} type="video/mp4" />
+ Not supported.
+ </video>
+ </div>
+ </div>;
}
@computed get youtubeVideoId() {
@@ -357,18 +377,28 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
private get uIButtons() {
const curTime = (this.layoutDoc._currentTimecode || 0);
return ([<div className="videoBox-time" key="time" onPointerDown={this.onResetDown} >
- <span>{"" + Math.round(curTime)}</span>
+ <span>{"" + formatTime(curTime)}</span>
<span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
</div>,
<div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} >
<FontAwesomeIcon icon="camera" size="lg" />
</div>,
+ <div className="timeline-button" key="timeline-button" onPointerDown={action(e => this.layoutDoc._timelineShow = !this.layoutDoc._timelineShow)}
+ style={{
+ transform: `scale(${this.scaling()})`,
+ right: this.scaling() * 10 - 10,
+ bottom: this.scaling() * 10 - 10
+ }}>
+ <FontAwesomeIcon icon={this.layoutDoc._timelineShow ? "eye-slash" : "eye"} style={{ width: "100%" }} />
+ </div>,
VideoBox._showControls ? (null) : [
+ // <div className="control-background">
<div className="videoBox-play" key="play" onPointerDown={this.onPlayDown} >
<FontAwesomeIcon icon={this._playing ? "pause" : "play"} size="lg" />
</div>,
<div className="videoBox-full" key="full" onPointerDown={this.onFullDown} >
F
+ {/* </div> */}
</div>
]]);
}
@@ -388,24 +418,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
onResetDown = (e: React.PointerEvent) => {
- this.Pause();
- e.stopPropagation();
- this._isResetClick = 0;
- document.addEventListener("pointermove", this.onResetMove, true);
- document.addEventListener("pointerup", this.onResetUp, true);
- }
-
- onResetMove = (e: PointerEvent) => {
- this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY);
- this.Seek(Math.max(0, (this.layoutDoc._currentTimecode || 0) + Math.sign(e.movementX) * 0.0333));
- e.stopImmediatePropagation();
- }
-
- @action
- onResetUp = (e: PointerEvent) => {
- document.removeEventListener("pointermove", this.onResetMove, true);
- document.removeEventListener("pointerup", this.onResetUp, true);
- this._isResetClick < 10 && (this.layoutDoc._currentTimecode = 0);
+ setupMoveUpEvents(this, e, (e: PointerEvent) => {
+ this.Seek(Math.max(0, (this.layoutDoc._currentTimecode || 0) + Math.sign(e.movementX) * 0.0333));
+ e.stopImmediatePropagation();
+ return false;
+ }, emptyFunction,
+ (e: PointerEvent) => this.layoutDoc._currentTimecode = 0);
}
@computed get youtubeContent() {
@@ -420,59 +438,147 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
}
@action.bound
- addDocumentWithTimestamp(doc: Doc | Doc[]): boolean {
+ addDocWithTimecode(doc: Doc | Doc[]): boolean {
const docs = doc instanceof Doc ? [doc] : doc;
const curTime = NumCast(this.layoutDoc._currentTimecode);
- docs.forEach(doc => doc.displayTimecode = curTime);
+ docs.forEach(doc => doc._timecodeToShow = curTime);
return this.addDocument(doc);
}
- screenToLocalTransform = () => this.props.ScreenToLocalTransform();
+ // play back the video from time
+ @action
+ playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => {
+ clearTimeout(this._playRegionTimer);
+ this._playRegionDuration = endTime - seekTimeInSeconds;
+ if (Number.isNaN(this.player?.duration)) {
+ setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500);
+ } else if (this.player) {
+ if (seekTimeInSeconds < 0) {
+ if (seekTimeInSeconds > -1) {
+ setTimeout(() => this.playFrom(0), -seekTimeInSeconds * 1000);
+ } else {
+ this.Pause();
+ }
+ } else if (seekTimeInSeconds <= this.player.duration) {
+ this.player.currentTime = seekTimeInSeconds;
+ this.player.play();
+ runInAction(() => this._playing = true);
+ if (endTime !== this.duration) {
+ this._playRegionTimer = setTimeout(() => this.Pause(), (this._playRegionDuration) * 1000); // use setTimeout to play a specific duration
+ }
+ } else {
+ this.Pause();
+ }
+ }
+ }
+
+ playLink = (doc: Doc) => {
+ const startTime = NumCast(doc.anchorStartTime, NumCast(doc._timecodeToShow));
+ const endTime = NumCast(doc.anchorEndTime, NumCast(doc._timecodeToHide, null));
+ if (startTime !== undefined) {
+ if (this.layoutDoc.playOnSelect) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime);
+ else this.Seek(startTime);
+ }
+ }
+
+ // returns the timeline
+ @computed get renderTimeline() {
+ return <div style={{ width: "100%", height: `${100 - this.heightPercent}%`, position: "absolute" }}>
+ <CollectionStackedTimeline ref={this._stackedTimeline}
+ Document={this.props.Document}
+ fieldKey={this.annotationKey}
+ renderDepth={this.props.renderDepth + 1}
+ parentActive={this.props.parentActive}
+ focus={emptyFunction}
+ styleProvider={this.props.styleProvider}
+ docFilters={this.props.docFilters}
+ docRangeFilters={this.props.docRangeFilters}
+ searchFilterDocs={this.props.searchFilterDocs}
+ rootSelected={this.props.rootSelected}
+ addDocTab={this.props.addDocTab}
+ pinToPres={this.props.pinToPres}
+ bringToFront={emptyFunction}
+ ContainingCollectionDoc={this.props.ContainingCollectionDoc}
+ ContainingCollectionView={this.props.ContainingCollectionView}
+ CollectionView={undefined}
+ duration={this.duration}
+ playFrom={this.playFrom}
+ setTime={(time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time}
+ playing={() => this._playing}
+ select={this.props.select}
+ isSelected={this.props.isSelected}
+ whenActiveChanged={action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive))}
+ removeDocument={this.removeDocument}
+ ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().scale(this.scaling()).translate(0, -this.heightPercent / 100 * this.props.PanelHeight())}
+ isChildActive={() => this._isChildActive}
+ Play={this.Play}
+ Pause={this.Pause}
+ active={this.active}
+ playLink={this.playLink}
+ PanelWidth={this.props.PanelWidth}
+ PanelHeight={() => this.props.PanelHeight() * (100 - this.heightPercent) / 100}
+ />
+ </div>;
+ }
+
contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content];
@computed get annotationLayer() {
- return <div className="imageBox-annotationLayer" style={{ height: Doc.NativeHeight(this.Document) || undefined }} ref={this._annotationLayer} />;
+ return <div className="imageBox-annotationLayer" style={{ height: `${this.heightPercent}%` }} ref={this._annotationLayer} />;
}
marqueeDown = action((e: React.PointerEvent) => {
if (!e.altKey && e.button === 0 && this.active(true)) this._marqueeing = [e.clientX, e.clientY];
- })
+ });
finishMarquee = action(() => {
this._marqueeing = undefined;
this.props.select(true);
- })
+ });
+
+ scaling = () => this.props.scaling?.() || 1;
+ panelWidth = () => this.props.PanelWidth() * this.heightPercent / 100;
+ panelHeight = () => this.layoutDoc._fitWidth ? this.panelWidth() / Doc.NativeAspect(this.rootDoc) : this.props.PanelHeight() * this.heightPercent / 100;
+ screenToLocalTransform = () => {
+ const offset = (this.props.PanelWidth() - this.panelWidth()) / 2 / this.scaling();
+ return this.props.ScreenToLocalTransform().translate(-offset, 0).scale(100 / this.heightPercent);
+ }
render() {
const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding);
- const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / (this.props.scaling?.() || 1)}px` : borderRad;
+ const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad;
return (<div className="videoBox" onContextMenu={this.specificContextMenu} ref={this._mainCont}
style={{
pointerEvents: this.props.layerProvider?.(this.layoutDoc) === false ? "none" : undefined,
borderRadius
}} >
- <div className="videoBox-viewer" onPointerDown={this.marqueeDown}>
- <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- forceScaling={true}
- fieldKey={this.annotationKey}
- isAnnotationOverlay={true}
- select={emptyFunction}
- active={this.annotationsActive}
- scaling={returnOne}
- ScreenToLocalTransform={this.screenToLocalTransform}
- whenActiveChanged={this.whenActiveChanged}
- removeDocument={this.removeDocument}
- moveDocument={this.moveDocument}
- addDocument={this.addDocumentWithTimestamp}
- CollectionView={undefined}
- renderDepth={this.props.renderDepth + 1}>
- {this.contentFunc}
- </CollectionFreeFormView>
+ <div className="videoBox-viewer" onPointerDown={this.marqueeDown} >
+ <div style={{ position: "absolute", width: this.panelWidth(), height: this.panelHeight(), top: 0, left: `${(100 - this.heightPercent) / 2}%` }}>
+ <CollectionFreeFormView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ fieldKey={this.annotationKey}
+ isAnnotationOverlay={true}
+ forceScaling={true}
+ select={emptyFunction}
+ active={this.annotationsActive}
+ scaling={returnOne}
+ PanelWidth={this.panelWidth}
+ PanelHeight={this.panelHeight}
+ ScreenToLocalTransform={this.screenToLocalTransform}
+ whenActiveChanged={this.whenActiveChanged}
+ removeDocument={this.removeDocument}
+ moveDocument={this.moveDocument}
+ addDocument={this.addDocWithTimecode}
+ CollectionView={undefined}
+ renderDepth={this.props.renderDepth + 1}>
+ {this.contentFunc}
+ </CollectionFreeFormView>
+ </div>
+ {this.uIButtons}
+ {this.annotationLayer}
+ {this.renderTimeline}
+ {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
+ <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocWithTimecode} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
</div>
- {this.uIButtons}
- {this.annotationLayer}
- {!this._marqueeing || !this._mainCont.current || !this._annotationLayer.current ? (null) :
- <MarqueeAnnotator rootDoc={this.rootDoc} down={this._marqueeing} scaling={this.props.scaling} addDocument={this.addDocumentWithTimestamp} finishMarquee={this.finishMarquee} savedAnnotations={this._savedAnnotations} annotationLayer={this._annotationLayer.current} mainCont={this._mainCont.current} />}
</div >);
}
}