diff options
Diffstat (limited to 'src/client/views/nodes/VideoBox.tsx')
-rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 411 |
1 files changed, 65 insertions, 346 deletions
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 988dd47d8..c6b9661a2 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -3,35 +3,32 @@ 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, Opt, DocListCast } 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 { List } from "../../../fields/List"; import { createSchema, makeInterface } from "../../../fields/Schema"; -import { Cast, StrCast, NumCast } from "../../../fields/Types"; +import { ComputedField } from "../../../fields/ScriptField"; +import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { VideoField } from "../../../fields/URLField"; -import { Utils, emptyFunction, returnOne, returnZero, OmitKeys, setupMoveUpEvents, returnFalse, returnTrue, formatTime } from "../../../Utils"; +import { emptyFunction, formatTime, OmitKeys, returnOne, Utils, setupMoveUpEvents } 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 { 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 { ComputedField, ScriptField } from "../../../fields/ScriptField"; -import { List } from "../../../fields/List"; -import { DocumentView } from "./DocumentView"; -import { LinkDocPreview } from "./LinkDocPreview"; import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment"; -import { StyleProp } from "../StyleProvider"; -import { computedFn } from "mobx-utils"; -import { Dictionary } from "typescript-collections"; -import { MarqueeAnnotator } from "../MarqueeAnnotator"; -import { Id } from "../../../fields/FieldSymbols"; -import { LabelBox } from "./LabelBox"; +import { LinkDocPreview } from "./LinkDocPreview"; +import { StackedTimeline } from "./StackedTimeline"; +import "./VideoBox.scss"; const path = require('path'); export const timeSchema = createSchema({ @@ -45,35 +42,22 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD public static LayoutString(fieldKey: string) { return FieldView.LayoutString(VideoBox, fieldKey); } static _youtubeIframeCounter: number = 0; static Instance: VideoBox; - static RangeScript: ScriptField; - static LabelScript: ScriptField; - static RangePlayScript: ScriptField; - static LabelPlayScript: ScriptField; static heightPercent = 60; // height of timeline in percent of height of videoBox. private _disposers: { [name: string]: IReactionDisposer } = {}; - private _doubleTime: NodeJS.Timeout | undefined; // bcz: Hack! this must be called _doubleTime since setupMoveDragEvents will use that field name 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<StackedTimeline>(); private _mainCont: React.RefObject<HTMLDivElement> = React.createRef(); private _annotationLayer: React.RefObject<HTMLDivElement> = React.createRef(); - private _play: any = null; - private _timeline: Opt<HTMLDivElement>; - private _audioRef = React.createRef<HTMLDivElement>(); - private _markerStart: number = 0; - private _left: boolean = false; - private _duration = 0; - private _start: boolean = true; - private _currAnchor: Doc | undefined; + private _playRegionTimer: any = null; + private _playRegionDuration = 0; @observable static _showControls: boolean; - @observable static SelectingRegion: VideoBox | undefined = undefined; @observable _marqueeing: number[] | undefined; @observable _savedAnnotations: Dictionary<number, HTMLDivElement[]> = new Dictionary<number, HTMLDivElement[]>(); @observable _screenCapture = false; @observable _visible: boolean = false; - @observable _markerEnd: number = 0; @observable _forceCreateYouTubeIFrame = false; @observable _playTimer?: NodeJS.Timeout = undefined; @observable _fullScreen = false; @@ -88,19 +72,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD constructor(props: Readonly<FieldViewProps>) { super(props); VideoBox.Instance = this; - - // onClick play scripts - VideoBox.RangeScript = VideoBox.RangeScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { this: Doc.name, clientX: "number", scriptContext: "any" })!; - VideoBox.LabelScript = VideoBox.LabelScript || ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { this: Doc.name, clientX: "number", scriptContext: "any" })!; - VideoBox.RangePlayScript = VideoBox.RangePlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, clientX: "number", scriptContext: "any" })!; - VideoBox.LabelPlayScript = VideoBox.LabelPlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, clientX: "number", scriptContext: "any" })!; } 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.createAnchor(Cast(this.layoutDoc._currentTimecode, "number", null)); + return this._stackedTimeline.current?.createAnchor(Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc; } choosePath(url: string) { @@ -115,6 +93,10 @@ 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); @@ -272,9 +254,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD componentWillUnmount() { this.Pause(); - this._disposers.reactionDisposer?.(); - this._disposers.youtubeReactionDisposer?.(); - this._disposers.videoStart?.(); + Object.keys(this._disposers).forEach(d => this._disposers[d]?.()); document.removeEventListener("keydown", VideoBox.keyEventsWrapper, true); } @@ -405,11 +385,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD <div className="videoBox-snapshot" key="snap" onPointerDown={this.onSnapshot} > <FontAwesomeIcon icon="camera" size="lg" /> </div>, - <div className="timeline-button" key="timeline-button" onPointerDown={this.toggleTimeline} style={{ - transform: `scale(${this.scaling()})`, - right: this.scaling() * 10 - 10, - bottom: this.scaling() * 10 - 10 - }}> + <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) : [ @@ -424,7 +405,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD ]]); } - onPlayDown = () => this._playing ? this.Pause() : this.Play(); onFullDown = (e: React.PointerEvent) => { @@ -440,24 +420,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() { @@ -482,8 +450,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD // play back the video from time @action playFrom = (seekTimeInSeconds: number, endTime: number = this.duration) => { - clearTimeout(this._play); - this._duration = endTime - seekTimeInSeconds; + clearTimeout(this._playRegionTimer); + this._playRegionDuration = endTime - seekTimeInSeconds; if (Number.isNaN(this.player?.duration)) { setTimeout(() => this.playFrom(seekTimeInSeconds, endTime), 500); } else if (this.player) { @@ -498,7 +466,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD this.player.play(); runInAction(() => this._playing = true); if (endTime !== this.duration) { - this._play = setTimeout(() => this.Pause(), (this._duration) * 1000); // use setTimeout to play a specific duration + this._playRegionTimer = setTimeout(() => this.Pause(), (this._playRegionDuration) * 1000); // use setTimeout to play a specific duration } } else { this.Pause(); @@ -506,150 +474,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD } } - @action - toggleTimeline = (e: React.PointerEvent) => this.layoutDoc._timelineShow = !this.layoutDoc._timelineShow - - // ref for timeline - timelineRef = (timeline: HTMLDivElement) => this._timeline = timeline - - // starting the drag event creating a range mark - @action - onPointerDownTimeline = (e: React.PointerEvent): void => { - const rect = this._timeline?.getBoundingClientRect(); - if (rect && e.target !== this._audioRef.current && this.active()) { - const wasPlaying = this._playing; - if (this._playing) this.Pause(); - else if (!this._doubleTime) { - this._doubleTime = setTimeout(() => { - this._doubleTime = undefined; - this.player!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.duration; - }, 300); - } - - this._markerStart = this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); - VideoBox.SelectingRegion = this; - setupMoveUpEvents(this, e, - action(e => { - this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); - return false; - }), - action((e, movement) => { - if (this._markerEnd < this._markerStart) { - const tmp = this._markerStart; - this._markerStart = this._markerEnd; - this._markerEnd = tmp; - } - VideoBox.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd); - VideoBox.SelectingRegion = undefined; - }), - (e, doubleTap) => { - this.props.select(false); - e.shiftKey && this.createAnchor(this.player!.currentTime); - !wasPlaying && doubleTap && this.Play(); - } - , this.props.isSelected(true) || this._isChildActive); - } - } - - @action - createAnchor(anchorStartTime: number, anchorEndTime?: number) { - const anchor = Docs.Create.LabelDocument({ - title: ComputedField.MakeFunction(`"#" + formatToTime(self.anchorStartTime) + "-" + formatToTime(self.anchorEndTime)`) as any, - useLinkSmallAnchor: true, // bcz: note this also flags that the annotation is not on the video itself, just the timeline - hideLinkButton: true, - anchorStartTime, - anchorEndTime, - annotationOn: this.props.Document - }); - if (this.dataDoc[this.annotationKey + "-timeline"]) { - this.dataDoc[this.annotationKey + "-timeline"].push(anchor); - } else { - this.dataDoc[this.annotationKey + "-timeline"] = new List<Doc>([anchor]); - } - return anchor; - } - - @action - playOnClick = (anchorDoc: Doc, clientX: number) => { - const seekTimeInSeconds = this.anchorStart(anchorDoc); - const endTime = this.anchorEnd(anchorDoc); - if (this.layoutDoc.autoPlay) { - if (this._playing) this.Pause(); - else this.playFrom(seekTimeInSeconds, endTime); - } else { - if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) { - if (!this.layoutDoc.autoPlay && this._playing) { - this.Pause(); - } else { - this.Play(); - } - } else { - this.playFrom(seekTimeInSeconds, endTime); - } - } - return { select: true }; - } - - @action - clickAnchor = (anchorDoc: Doc, clientX: number) => { - const seekTimeInSeconds = this.anchorStart(anchorDoc); - const endTime = this.anchorEnd(anchorDoc); - if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) { - if (this._playing) this.Pause(); - else if (this.layoutDoc.autoPlay) this.Play(); - else if (!this.layoutDoc.autoPlay) { - const rect = this._timeline?.getBoundingClientRect(); - rect && this.Seek(this.toTimeline(clientX - rect.x, rect.width)); - } - } else { - if (this.layoutDoc.autoPlay) this.playFrom(seekTimeInSeconds, endTime); - else this.Seek(seekTimeInSeconds); - } - return { select: true }; - } - - toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration)); - // starting the drag event for anchor resizing - onPointerDown = (e: React.PointerEvent, m: Doc, left: boolean): void => { - this._currAnchor = m; - this._left = left; - this._timeline?.setPointerCapture(e.pointerId); - setupMoveUpEvents(this, e, - (e: PointerEvent) => { - const rect = (e.target as any).getBoundingClientRect(); - this.changeAnchor(this._currAnchor, this.toTimeline(e.clientX - rect.x, rect.width)); - return false; - }, - (e: PointerEvent) => { - const rect = (e.target as any).getBoundingClientRect(); - this.player!.currentTime = this.layoutDoc._currentTimecode = this.toTimeline(e.clientX - rect.x, rect.width); - this._timeline?.releasePointerCapture(e.pointerId); - }, - emptyFunction); - } - - // makes sure no anchors overlaps each other by setting the correct position and width - getLevel = (m: any, placed: { anchorStartTime: number, videoEnd: number, level: number }[]) => { - const timelineContentWidth = this.props.PanelWidth(); - const x1 = this.anchorStart(m); - const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.duration); - let max = 0; - const overlappedLevels = new Set(placed.map(p => { - const y1 = p.anchorStartTime; - const y2 = p.videoEnd; - if ((x1 >= y1 && x1 <= y2) || (x2 >= y1 && x2 <= y2) || - (y1 >= x1 && y1 <= x2) || (y2 >= x1 && y2 <= x2)) { - max = Math.max(max, p.level); - return p.level; - } - })); - let level = max + 1; - for (let j = max; j >= 0; j--) !overlappedLevels.has(j) && (level = j); - - placed.push({ anchorStartTime: x1, videoEnd: x2, level }); - return level; - } - playLink = (doc: Doc) => { const startTime = NumCast(doc.anchorStartTime, NumCast(doc._timecodeToShow)); const endTime = NumCast(doc.anchorEndTime, NumCast(doc._timecodeToHide, null)); @@ -658,145 +482,40 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD else this.Seek(startTime); } } - // renders the anchors as a document - renderInner = computedFn(function (this: VideoBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number, annotationKey: string) { - const anchor = observable({ view: undefined as any }); - return { - anchor, view: <DocumentView key="view" {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit} ref={action((r: DocumentView | null) => anchor.view = r)} - Document={mark} - DataDoc={undefined} - PanelWidth={() => width} - PanelHeight={() => height} - renderDepth={this.props.renderDepth + 1} - focus={() => this.playLink(mark)} - rootSelected={returnFalse} - LayoutTemplate={undefined} - LayoutTemplateString={LabelBox.LayoutString("data")} - ContainingCollectionDoc={this.props.Document} - removeDocument={(doc: Doc | Doc[]) => this.removeDocument(doc, annotationKey)} - ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().scale(this.scaling()).translate(-x, -y)} - parentActive={(out) => this.props.isSelected(out) || this._isChildActive} - whenActiveChanged={action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive))} - onClick={script} - onDoubleClick={doublescript} - ignoreAutoHeight={false} - bringToFront={emptyFunction} - scriptContext={this} /> - }; - }); - - renderAnchor = computedFn(function (this: VideoBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number, annotationKey: string) { - const inner = this.renderInner(mark, script, doublescript, x, y, width, height, annotationKey); - return <> - {inner.view} - {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? (null) : - <> - <div key="left" className="left-resizer" onPointerDown={e => this.onPointerDown(e, mark, true)} /> - <div key="right" className="resizer" onPointerDown={e => this.onPointerDown(e, mark, false)} /> - </>} - </>; - }); // returns the timeline @computed get renderTimeline() { - const timelineContentWidth = this.props.PanelWidth(); - const timelineContentHeight = this.props.PanelHeight() * (100 - this.heightPercent) / 100; - const overlaps: { anchorStartTime: number, videoEnd: number, level: number }[] = []; - const drawAnchors: { level: number, anchor: Doc }[] = this.anchorDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor })); - const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; - return !this.layoutDoc._timelineShow ? (null) : - <div className="audiobox-timeline" ref={this.timelineRef} style={{ height: `${100 - this.heightPercent}%` }} - onClick={e => { - if (this._isChildActive || this.props.isSelected()) { - e.stopPropagation(); e.preventDefault(); - } - }} - onPointerDown={e => { - if (this._isChildActive || this.props.isSelected()) { - e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e); - } - }}> - {drawAnchors.map(d => { - const m = d.anchor; - const start = this.anchorStart(m); - const end = this.anchorEnd(m, start + 10 / timelineContentWidth * this.duration); - const left = start / this.duration * timelineContentWidth; - const top = d.level / maxLevel * timelineContentHeight; - const timespan = end - start; - return this.layoutDoc.hideAnchors ? (null) : - <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}timeline`} key={m[Id]} - style={{ left, top, width: `${timespan / this.duration * 100}%`, height: `${1 / maxLevel * 100}%` }} - onClick={e => { this.playFrom(start, this.anchorEnd(m)); e.stopPropagation(); }} > - {this.renderAnchor(m, this.rangeClickScript, this.rangePlayScript, - left, - top + (this.props.PanelHeight() - timelineContentHeight), - timelineContentWidth * timespan / this.duration, - timelineContentHeight / maxLevel, this.annotationKey + (m.anchorStartTime !== undefined ? "-timeline" : ""))} - </div>; - })} - {this.selectionContainer} - <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} - style={{ left: `${NumCast(this.layoutDoc._currentTimecode) / this.duration * 100}%`, pointerEvents: "none" }} - /> - </div>; - } - - // updates the anchor with the new time - @action - changeAnchor = (anchor: Opt<Doc>, time: number) => { - if (anchor) { - const timelineOnly = Cast(anchor.anchorStartTime, "number", null) !== undefined; - if (timelineOnly) this._left ? anchor.anchorStartTime = time : anchor.anchorEndTime = time; - else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; - } - } - - // checks if the two anchors are the same with start and end time - isSame = (m1: any, m2: any) => { - return m1.anchorStartTime === m2.anchorStartTime && m1.anchorEndTime === m2.anchorEndTime && m1._timecodeToShow === m2._timecodeToShow && m1._timecodeToHide === m2._timecodeToHide; - } - - // returns the blue container when dragging - @computed get selectionContainer() { - return VideoBox.SelectingRegion !== this ? (null) : <div className="audiobox-container" style={{ - left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.duration * 100}%`, - width: `${Math.abs(this._markerStart - this._markerEnd) / this.duration * 100}%`, height: "100%", top: "0%" - }} />; - } - - static keyEventsWrapper = (e: KeyboardEvent) => { - VideoBox.Instance.keyEvents(e); - } - - // for creating key anchors with key events - @action - keyEvents = (e: KeyboardEvent) => { - if (e.target instanceof HTMLInputElement) return; - if (!this._playing) return; // can't create if video is not playing - switch (e.key) { - case "x": // currently set to x, but can be a different key - const currTime = this.player!.currentTime; - if (this._start) { - this._markerStart = this.player!.currentTime; - this._start = false; - this._visible = true; - } else { - this.createAnchor(this._markerStart, currTime); - this._start = true; - this._visible = false; - } - } + return <div style={{ width: "100%", height: `${100 - this.heightPercent}%`, position: "absolute" }}> + <StackedTimeline ref={this._stackedTimeline} + Document={this.props.Document} + dataDoc={this.dataDoc} + anchorProps={this.props} + renderDepth={this.props.renderDepth + 1} + annotationKey={this.annotationKey + "-timeline"} + 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>; } - rangeClickScript = () => VideoBox.RangeScript; - labelClickScript = () => VideoBox.LabelScript; - rangePlayScript = () => VideoBox.RangePlayScript; - labelPlayScript = () => VideoBox.LabelPlayScript; - contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content]; @computed get annotationLayer() { - return <div className="imageBox-annotationLayer" style={{ height: "100%" }} ref={this._annotationLayer} />; + return <div className="imageBox-annotationLayer" style={{ height: `${this.heightPercent}%` }} ref={this._annotationLayer} />; } marqueeDown = action((e: React.PointerEvent) => { |