diff options
Diffstat (limited to 'src/client/views/collections/CollectionStackedTimeline.tsx')
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 49 |
1 files changed, 41 insertions, 8 deletions
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index ebdea9aaf..3e85edac8 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -43,6 +43,8 @@ import { } from "../nodes/DocumentView"; import { LabelBox } from "../nodes/LabelBox"; import "./CollectionStackedTimeline.scss"; +import { VideoBox } from "../nodes/VideoBox"; +import { ImageField } from "../../../fields/URLField"; export type CollectionStackedTimelineProps = { Play: () => void; @@ -86,9 +88,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @observable _trimEnd: number = 0; // trim controls end pos @observable _zoomFactor: number = 1; - @observable _scroll: number = 0; + @observable _hoverTime: number = 0; + + @observable _thumbnail: string | undefined; + // ensures that clip doesn't get trimmed so small that controls cannot be adjusted anymore get minTrimLength() { return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5); } @@ -178,7 +183,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @action keyEvents = (e: KeyboardEvent) => { if ( - !(e.target instanceof HTMLInputElement) && + // need to include range inputs because after dragging video time slider it becomes target element + !(e.target instanceof HTMLInputElement && !(e.target.type === "range")) && this.props.isSelected(true) ) { // if shift pressed scrub 1 second otherwise 1/10th @@ -315,11 +321,28 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack } + @action + onHover = (e: React.MouseEvent): void => { + e.stopPropagation(); + const rect = this._timeline?.getBoundingClientRect(); + const clientX = e.clientX; + if (rect) { + this._hoverTime = this.toTimeline(clientX - rect.x, rect.width); + if (this.dataDoc.thumbnails) { + const nearest = Math.floor(this._hoverTime / this.props.rawDuration * VideoBox.numThumbnails); + const thumbnails = Cast(this.dataDoc.thumbnails, listSpec("string"), []); + const imgField = thumbnails && thumbnails.length > 0 ? new ImageField(thumbnails[nearest]) : new ImageField(""); + const src = imgField && imgField.url.href ? imgField.url.href.replace(".png", "_s.png") : ""; + this._thumbnail = src ? src : undefined; + } + } + } + + // for dragging trim start handle @action trimLeft = (e: React.PointerEvent): void => { const rect = this._timeline?.getBoundingClientRect(); - const clientX = e.movementX; setupMoveUpEvents( this, e, @@ -346,7 +369,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @action trimRight = (e: React.PointerEvent): void => { const rect = this._timeline?.getBoundingClientRect(); - const clientX = e.movementX; setupMoveUpEvents( this, e, @@ -436,7 +458,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack if (anchorStartTime === undefined) return rootDoc; const anchor = docAnchor ?? Docs.Create.LabelDocument({ title: ComputedField.MakeFunction( - `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])` + `self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])` ) as any, _minFontSize: 12, _maxFontSize: 24, @@ -556,7 +578,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100; @computed get timelineContentHeight() { return this.props.PanelHeight() * this.dictationHeightPercent / 100; } - @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor - 4; } // subtract size of container border + @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor; } // subtract size of container border dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight); @@ -632,6 +654,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack style={{ width: this.props.PanelWidth() }} onWheel={e => e.stopPropagation()} onScroll={this.setScroll} + onMouseMove={(e) => this.isContentActive() && this.onHover(e)} ref={wrapper => this._timelineWrapper = wrapper}> <div className="collectionStackedTimeline" @@ -702,6 +725,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack /> {/* {this.renderDictation} */} + { /* check time to prevent weird div overflow */ this._hoverTime < this.clipDuration && <div + className="collectionStackedTimeline-hover" + style={{ + left: `${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%`, + }} + />} + <div className="collectionStackedTimeline-current" style={{ @@ -744,6 +774,10 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack )} </div> </div> + <div className="timeline-hoverUI" style={{ left: `calc(${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%` }}> + <div className="hoverTime">{formatTime(this._hoverTime)}</div> + {this._thumbnail && <img className="videoBox-thumbnail" src={this._thumbnail} />} + </div> </div >); } } @@ -799,7 +833,6 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> return `#${formatTime(start)}-${formatTime(end)}`; } - componentDidMount() { this._disposer = reaction( () => this.props.currentTimecode(), @@ -890,7 +923,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> // context menu contextMenuItems = () => { - const resetTitle = { script: ScriptField.MakeFunction(`self.title = "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"])`)!, icon: "folder-plus", label: "Reset Title" }; + const resetTitle = { script: ScriptField.MakeFunction(`self.title = self["${this.props.endTag}"] ? "#" + formatToTime(self["${this.props.startTag}"]) + "-" + formatToTime(self["${this.props.endTag}"]) : "#" + formatToTime(self["${this.props.startTag}"])`)!, icon: "folder-plus", label: "Reset Title" }; return [resetTitle]; } |