diff options
Diffstat (limited to 'src/client/views/collections/CollectionStackedTimeline.tsx')
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 180 |
1 files changed, 99 insertions, 81 deletions
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 656f850b3..f9b123bb6 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -1,7 +1,13 @@ +/* eslint-disable react/jsx-props-no-spreading */ +/* eslint-disable jsx-a11y/alt-text */ +/* eslint-disable no-use-before-define */ +/* eslint-disable jsx-a11y/no-static-element-interactions */ +/* eslint-disable jsx-a11y/click-events-have-key-events */ import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; import * as React from 'react'; +import { returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../ClientUtils'; import { Doc, Opt } from '../../../fields/Doc'; import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; @@ -10,26 +16,26 @@ import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; import { Cast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; +import { emptyFunction, formatTime } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; -import { DocumentManager } from '../../util/DocumentManager'; +import { FollowLinkScript, IsFollowLinkScript } from '../../documents/DocUtils'; import { DragManager } from '../../util/DragManager'; -import { FollowLinkScript, IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower'; -import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; -import { CollectionSubView } from '../collections/CollectionSubView'; +import { VideoThumbnails } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; import { AudioWaveform } from '../nodes/audio/AudioWaveform'; -import { DocumentView, OpenWhere } from '../nodes/DocumentView'; -import { FocusFuncType, FocusViewOptions, StyleProviderFuncType } from '../nodes/FieldView'; +import { DocumentView } from '../nodes/DocumentView'; +import { FocusFuncType, StyleProviderFuncType } from '../nodes/FieldView'; +import { FocusViewOptions } from '../nodes/FocusViewOptions'; import { LabelBox } from '../nodes/LabelBox'; -import { VideoBox } from '../nodes/VideoBox'; +import { OpenWhere } from '../nodes/OpenWhere'; import { ObservableReactComponent } from '../ObservableReactComponent'; import './CollectionStackedTimeline.scss'; +import { CollectionSubView } from './CollectionSubView'; export type CollectionStackedTimelineProps = { Play: () => void; @@ -57,9 +63,14 @@ export enum TrimScope { @observer export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() { + // eslint-disable-next-line no-use-before-define public static SelectingRegions: Set<CollectionStackedTimeline> = new Set(); public static StopSelecting() { - this.SelectingRegions.forEach(action(region => (region._selectingRegion = false))); + this.SelectingRegions.forEach( + action(region => { + region._selectingRegion = false; + }) + ); this.SelectingRegions.clear(); } constructor(props: any) { @@ -73,7 +84,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack private _timeline: HTMLDivElement | null = null; // ref to actual timeline div private _timelineWrapper: HTMLDivElement | null = null; // ref to timeline wrapper div for zooming and scrolling private _markerStart: number = 0; - @observable public static CurrentlyPlaying: DocumentView[] = []; @observable _selectingRegion = false; @observable _markerEnd: number | undefined = undefined; @observable _trimming: number = TrimScope.None; @@ -130,7 +140,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack componentWillUnmount() { document.removeEventListener('keydown', this.keyEvents, true); if (this._selectingRegion) { - runInAction(() => (this._selectingRegion = false)); + runInAction(() => { + this._selectingRegion = false; + }); CollectionStackedTimeline.SelectingRegions.delete(this); } } @@ -166,7 +178,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> => new Promise<Opt<DocumentView>>(res => { if (doc.hidden) options.didMove = !(doc.hidden = false); - const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); + const findDoc = (finish: (dv: DocumentView) => void) => DocumentView.addViewRenderedCb(doc, dv => finish(dv)); findDoc(dv => res(dv)); }); @@ -174,9 +186,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this._props.endTag], val) ?? null); // converts screen pixel offset to time - toTimeline = (screen_delta: number, width: number) => { - return Math.max(this.clipStart, Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart)); - }; + // prettier-ignore + toTimeline = (screenDelta: number, width: number) => // + Math.max(this.clipStart, Math.min(this.clipEnd, (screenDelta / width) * this.clipDuration + this.clipStart)); @computed get rangeClick() { // prettier-ignore @@ -234,6 +246,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd)); e.stopPropagation(); break; + default: } } }; @@ -253,17 +266,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @action onPointerDownTimeline = (e: React.PointerEvent): void => { const rect = this._timeline?.getBoundingClientRect(); - const clientX = e.clientX; - const diff = rect ? clientX - rect?.x : null; - const shiftKey = e.shiftKey; + const { clientX, shiftKey } = e; if (rect && this._props.isContentActive()) { const wasPlaying = this._props.playing(); if (wasPlaying) this._props.Pause(); - var wasSelecting = this._markerEnd !== undefined; + let wasSelecting = this._markerEnd !== undefined; setupMoveUpEvents( this, e, - action(e => { + action(() => { if (!wasSelecting) { this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); wasSelecting = true; @@ -272,8 +283,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); return false; }), - action((e, movement, isClick) => { - this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); + action((upEvent, movement, isClick) => { + this._markerEnd = this.toTimeline(upEvent.clientX - rect.x, rect.width); if (this._markerEnd < this._markerStart) { const tmp = this._markerStart; this._markerStart = this._markerEnd; @@ -281,13 +292,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack } if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) { const anchor = CollectionStackedTimeline.createAnchor(this.Document, this.dataDoc, this._props.fieldKey, this._markerStart, this._markerEnd, undefined, true); - setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false)); + setTimeout(() => DocumentView.getDocumentView(anchor)?.select(false)); } (!isClick || !wasSelecting) && (this._markerEnd = undefined); this._timelineWrapper && (this._timelineWrapper.style.cursor = ''); }), - (e, doubleTap) => { - if (e.button !== 2) { + (clickEv, doubleTap) => { + if (clickEv.button !== 2) { this._props.select(false); !wasPlaying && doubleTap && this._props.Play(); } @@ -309,11 +320,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack onHover = (e: React.MouseEvent): void => { e.stopPropagation(); const rect = this._timeline?.getBoundingClientRect(); - const clientX = e.clientX; + const { clientX } = e; if (rect) { this._hoverTime = this.toTimeline(clientX - rect.x, rect.width); if (this.thumbnails) { - const nearest = Math.floor((this._hoverTime / this._props.rawDuration) * VideoBox.numThumbnails); + const nearest = Math.floor((this._hoverTime / this._props.rawDuration) * VideoThumbnails.DENSE); const imgField = this.thumbnails.length > 0 ? new ImageField(this.thumbnails[nearest]) : undefined; this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined; } @@ -327,14 +338,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack setupMoveUpEvents( this, e, - action((e, [], []) => { + action(moveEv => { if (rect && this._props.isContentActive()) { - this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength); + this._trimStart = Math.min(Math.max(this.trimStart + (moveEv.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength); } return false; }), emptyFunction, - action((e, doubleTap) => { + action((clickEv, doubleTap) => { doubleTap && (this._trimStart = this.clipStart); }) ); @@ -347,14 +358,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack setupMoveUpEvents( this, e, - action((e, [], []) => { + action(moveEv => { if (rect && this._props.isContentActive()) { - this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength); + this._trimEnd = Math.max(Math.min(this.trimEnd + (moveEv.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength); } return false; }), emptyFunction, - action((e, doubleTap) => { + action((clickEv, doubleTap) => { doubleTap && (this._trimEnd = this.clipEnd); }) ); @@ -383,7 +394,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // handles dragging and dropping markers in timeline @action - internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) { + internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData) { if (super.onInternalDrop(e, de)) { // determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view const localPt = this.ScreenToLocalBoxXf().transformPoint(de.x, de.y); @@ -403,7 +414,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack } onInternalDrop = (e: Event, de: DragManager.DropEvent) => { - if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData, 0); + if (de.complete.docDragData?.droppedDocuments.length) return this.internalDocDrop(e, de, de.complete.docDragData); return false; }; @@ -441,7 +452,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack } @action - playOnClick = (anchorDoc: Doc, clientX: number) => { + playOnClick = (anchorDoc: Doc /* , clientX: number */) => { const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); if (this.layoutDoc.autoPlayAnchors) { @@ -450,17 +461,15 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._props.playFrom(seekTimeInSeconds, endTime); this.scrollToTime(seekTimeInSeconds); } - } else { - if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) { - if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) { - this._props.Pause(); - } else { - this._props.Play(); - } + } else if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) { + if (!this.layoutDoc.autoPlayAnchors && this._props.playing()) { + this._props.Pause(); } else { - this._props.playFrom(seekTimeInSeconds, endTime); - this.scrollToTime(seekTimeInSeconds); + this._props.Play(); } + } else { + this._props.playFrom(seekTimeInSeconds, endTime); + this.scrollToTime(seekTimeInSeconds); } return { select: true }; }; @@ -468,7 +477,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @action clickAnchor = (anchorDoc: Doc, clientX: number) => { if (IsFollowLinkScript(anchorDoc.onClick)) { - LinkFollower.FollowLink(undefined, anchorDoc, false); + DocumentView.FollowLink(undefined, anchorDoc, false); } const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); @@ -479,19 +488,17 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const rect = this._timeline?.getBoundingClientRect(); rect && this._props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } + } else if (this.layoutDoc.autoPlayAnchors) { + this._props.playFrom(seekTimeInSeconds, endTime); } else { - if (this.layoutDoc.autoPlayAnchors) { - this._props.playFrom(seekTimeInSeconds, endTime); - } else { - this._props.setTime(seekTimeInSeconds); - } + this._props.setTime(seekTimeInSeconds); } return { select: true }; }; // makes sure no anchors overlaps each other by setting the correct position and width getLevel = (m: Doc, placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]) => { - const timelineContentWidth = this.timelineContentWidth; + const { timelineContentWidth } = this; const x1 = this.anchorStart(m); const x2 = this.anchorEnd(m, x1 + (10 / timelineContentWidth) * this.clipDuration); let max = 0; @@ -503,6 +510,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack max = Math.max(max, p.level); return p.level; } + return undefined; }) ); let level = max + 1; @@ -564,10 +572,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack onWheel={e => this.isContentActive() && e.stopPropagation()} onScroll={this.setScroll} onMouseMove={e => this.isContentActive() && this.onHover(e)} - ref={wrapper => (this._timelineWrapper = wrapper)}> + ref={wrapper => { + this._timelineWrapper = wrapper; + }}> <div className="collectionStackedTimeline" - ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)} + ref={(timeline: HTMLDivElement | null) => { + this._timeline = timeline; + }} onClick={e => this.isContentActive() && StopEvent(e)} onPointerDown={e => this.isContentActive() && this.onPointerDownTimeline(e)} style={{ width: this.timelineContentWidth }}> @@ -582,7 +594,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const height = this._props.PanelHeight() / maxLevel; return this.Document.hideAnchors ? null : ( <div - className={'collectionStackedTimeline-marker-timeline'} + className="collectionStackedTimeline-marker-timeline" key={d.anchor[Id]} style={{ left, @@ -592,6 +604,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack pointerEvents: 'none', }}> <StackedTimelineAnchor + // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} mark={d.anchor} containerViewPath={this._props.containerViewPath} @@ -646,7 +659,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack {this.IsTrimming !== TrimScope.None && ( <> - <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }}></div> + <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }} /> <div className="collectionStackedTimeline-trim-controls" @@ -654,8 +667,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack left: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%`, width: `${((this.trimEnd - this.trimStart) / this.clipDuration) * 100}%`, }}> - <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft}></div> - <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight}></div> + <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimLeft} /> + <div className="collectionStackedTimeline-trim-handle" onPointerDown={this.trimRight} /> </div> <div @@ -663,7 +676,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack style={{ left: `${((this.trimEnd - this.clipStart) / this.clipDuration) * 100}%`, width: `${((this.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`, - }}></div> + }} + /> </> )} </div> @@ -685,7 +699,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack interface StackedTimelineAnchorProps { mark: Doc; whenChildContentsActiveChanged: (isActive: boolean) => void; - addDocTab: (doc: Doc, where: OpenWhere) => boolean; + addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean; rangeClickScript: () => ScriptField; rangePlayScript: () => ScriptField; left: number; @@ -735,20 +749,20 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch this._disposer = reaction( () => this._props.currentTimecode(), time => { - const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null); - const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc); + // const dictationDoc = Cast(this._props.layoutDoc.data_dictation, Doc, null); + // const isDictation = dictationDoc && LinkManager.Links(this._props.mark).some(link => Cast(link.link_anchor_1, Doc, null)?.annotationOn === dictationDoc); if ( !LightboxView.LightboxDoc && // bcz: when should links be followed? we don't want to move away from the video to follow a link but we can open it in a sidebar/etc. But we don't know that upfront. // for now, we won't follow any links when the lightbox is oepn to avoid "losing" the video. - /*(isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc))*/ + /* (isDictation || !Doc.AreProtosEqual(LightboxView.LightboxDoc, this._props.layoutDoc)) */ !this._props.layoutDoc.dontAutoFollowLinks && - LinkManager.Links(this._props.mark).length && + Doc.Links(this._props.mark).length && time > NumCast(this._props.mark[this._props.startTag]) && time < NumCast(this._props.mark[this._props.endTag]) && this._lastTimecode < NumCast(this._props.mark[this._props.startTag]) - 1e-5 ) { - LinkFollower.FollowLink(undefined, this._props.mark, false); + DocumentView.FollowLink(undefined, this._props.mark, false); } this._lastTimecode = time; } @@ -763,34 +777,33 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch // starting the drag event for anchor resizing @action onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { - //this._props._timeline?.setPointerCapture(e.pointerId); - const newTime = (e: PointerEvent) => { - const rect = (e.target as any).getBoundingClientRect(); - return this._props.toTimeline(e.clientX - rect.x, rect.width); + const newTime = (timeDownEv: PointerEvent) => { + const rect = (timeDownEv.target as any).getBoundingClientRect(); + return this._props.toTimeline(timeDownEv.clientX - rect.x, rect.width); }; - const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => { + const changeAnchor = (time: number | undefined) => { const timelineOnly = Cast(anchor[this._props.startTag], 'number', null) !== undefined; if (timelineOnly) { - if (!left && time !== undefined && time <= NumCast(anchor[this._props.startTag])) time = undefined; - Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, time, true); - if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', time !== undefined ? undefined : '100%', true); + const timeMod = !left && time !== undefined && time <= NumCast(anchor[this._props.startTag]) ? undefined : time; + Doc.SetInPlace(anchor, left ? this._props.startTag : this._props.endTag, timeMod, true); + if (!left) Doc.SetInPlace(anchor, 'layout_borderRounding', timeMod !== undefined ? undefined : '100%', true); } else { anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time; } return false; }; this.noEvents = true; - var undo: UndoManager.Batch | undefined; + let undo: UndoManager.Batch | undefined; setupMoveUpEvents( this, e, - e => { + moveEv => { if (!undo) undo = UndoManager.StartBatch('drag anchor'); - this._props.setTime(newTime(e)); - return changeAnchor(anchor, left, newTime(e)); + this._props.setTime(newTime(moveEv)); + return changeAnchor(newTime(moveEv)); }, - action(e => { - this._props.setTime(newTime(e)); + action(upEv => { + this._props.setTime(newTime(upEv)); undo?.end(); this.noEvents = false; }), @@ -828,7 +841,9 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch {...this._props} NativeWidth={returnZero} NativeHeight={returnZero} - ref={action((r: DocumentView | null) => (anchor.view = r))} + ref={action((r: DocumentView | null) => { + anchor.view = r; + })} Document={mark} TemplateDataDocument={undefined} containerViewPath={this._props.containerViewPath} @@ -851,7 +866,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch onClickScript={script} onDoubleClickScript={this._props.layoutDoc.autoPlayAnchors ? undefined : doublescript} ignoreAutoHeight={false} - hideResizeHandles={true} + hideResizeHandles contextMenuItems={this.contextMenuItems} /> ), @@ -877,12 +892,15 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch ); } } +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function formatToTime(time: number): any { return formatTime(time); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function min(num1: number, num2: number): number { return Math.min(num1, num2); }); +// eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(function max(num1: number, num2: number): number { return Math.max(num1, num2); }); |