diff options
| author | bobzel <zzzman@gmail.com> | 2023-05-22 11:25:32 -0400 |
|---|---|---|
| committer | bobzel <zzzman@gmail.com> | 2023-05-22 11:25:32 -0400 |
| commit | bed3309e1fda6597b2a8fea10ad82cd3a0402051 (patch) | |
| tree | fe599bbdc5fca2c221e1e0f7a60995b7cd39f870 /src/client/views/collections/CollectionStackedTimeline.tsx | |
| parent | 887a4f7e0fc25fde87b20a5de2e7b0aee561cc78 (diff) | |
| parent | 3d26d5b2654841a9b92f3d66b28d1dc8e36cca6a (diff) | |
merged physics with master
Diffstat (limited to 'src/client/views/collections/CollectionStackedTimeline.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 156 |
1 files changed, 72 insertions, 84 deletions
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 28f08b6ce..b131d38d8 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -2,19 +2,20 @@ import React = require('react'); import { action, computed, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import { computedFn } from 'mobx-utils'; -import { Doc, DocListCast, Opt, StrListCast } from '../../../fields/Doc'; +import { Doc, Opt, StrListCast } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ComputedField, ScriptField } from '../../../fields/ScriptField'; -import { Cast, NumCast } from '../../../fields/Types'; +import { Cast, NumCast, ScriptCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, returnTrue, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; +import { emptyFunction, formatTime, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../Utils'; import { Docs } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from '../../util/DragManager'; -import { LinkFollower } from '../../util/LinkFollower'; +import { FollowLinkScript, IsFollowLinkScript, LinkFollower } from '../../util/LinkFollower'; +import { LinkManager } from '../../util/LinkManager'; import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; @@ -22,9 +23,8 @@ import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { AudioWaveform } from '../AudioWaveform'; import { CollectionSubView } from '../collections/CollectionSubView'; -import { Colors } from '../global/globalEnums'; import { LightboxView } from '../LightboxView'; -import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { DocFocusFunc, DocFocusOptions, DocumentView, DocumentViewProps, OpenWhere } from '../nodes/DocumentView'; import { LabelBox } from '../nodes/LabelBox'; import { VideoBox } from '../nodes/VideoBox'; import './CollectionStackedTimeline.scss'; @@ -41,7 +41,9 @@ export type CollectionStackedTimelineProps = { mediaPath: string; dictationKey: string; rawDuration: number; + dataFieldKey: string; fieldKey: string; + thumbnails?: () => string[]; }; // trimming state: shows full clip, current trim bounds, or not trimming @@ -54,7 +56,7 @@ export enum TrimScope { @observer export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() { @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; - @observable public static CurrentlyPlaying: Doc[]; + @observable public static CurrentlyPlaying: DocumentView[]; static RangeScript: ScriptField; static LabelScript: ScriptField; @@ -80,6 +82,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack get minTrimLength() { return Math.max(this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0, 0.5); } + @computed get thumbnails() { + return this.props.thumbnails?.(); + } @computed get trimStart() { return this.IsTrimming !== TrimScope.None ? this._trimStart : this.clipStart; @@ -102,7 +107,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack } @computed get currentTime() { - return NumCast(this.layoutDoc._currentTimecode); + return NumCast(this.layoutDoc._layout_currentTimecode); } @computed get zoomFactor() { @@ -114,7 +119,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // onClick play scripts CollectionStackedTimeline.RangeScript = CollectionStackedTimeline.RangeScript || - ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, { + ScriptField.MakeFunction(`setTimeout(() => scriptContext.clickAnchor(this, clientX))`, { + // setTimeout is a hack to run script in its own properly named undo group (instead of being part of the generic onClick) self: Doc.name, scriptContext: 'any', clientX: 'number', @@ -179,18 +185,21 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack if ( // 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) + this.props.isContentActive() ) { // if shift pressed scrub 1 second otherwise 1/10th const jump = e.shiftKey ? 1 : 0.1; switch (e.key) { case ' ': + this.props.playing() ? this.props.Pause() : this.props.Play(); + break; + case '^': if (!CollectionStackedTimeline.SelectingRegion) { this._markerStart = this._markerEnd = this.currentTime; CollectionStackedTimeline.SelectingRegion = this; } else { this._markerEnd = this.currentTime; - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true); + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true); this._markerEnd = undefined; CollectionStackedTimeline.SelectingRegion = undefined; } @@ -216,12 +225,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack }; getLinkData(l: Doc) { - let la1 = l.anchor1 as Doc; - let la2 = l.anchor2 as Doc; + let la1 = l.link_anchor_1 as Doc; + let la2 = l.link_anchor_2 as Doc; const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag])); if (Doc.AreProtosEqual(la1, this.dataDoc)) { - la1 = l.anchor2 as Doc; - la2 = l.anchor1 as Doc; + la1 = l.link_anchor_2 as Doc; + la2 = l.link_anchor_1 as Doc; } return { la1, la2, linkTime }; } @@ -257,7 +266,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this._markerEnd = tmp; } if (!isClick && Math.abs(movement[0]) > 15 && !this.IsTrimming) { - const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd, undefined, true); + const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this._markerStart, this._markerEnd, undefined, true); setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false)); } (!isClick || !wasSelecting) && (this._markerEnd = undefined); @@ -273,7 +282,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack undefined, () => { if (shiftKey) { - CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime, undefined, undefined, true); + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.currentTime, undefined, undefined, true); } else { !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); } @@ -289,10 +298,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack const clientX = e.clientX; if (rect) { this._hoverTime = this.toTimeline(clientX - rect.x, rect.width); - if (this.dataDoc.thumbnails) { + if (this.thumbnails) { const nearest = Math.floor((this._hoverTime / this.props.rawDuration) * VideoBox.numThumbnails); - const thumbnails = StrListCast(this.dataDoc.thumbnails); - const imgField = thumbnails?.length > 0 ? new ImageField(thumbnails[nearest]) : undefined; + 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; } } @@ -388,19 +396,20 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack // creates marker on timeline @undoBatch @action - static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) { + static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, anchorStartTime: Opt<number>, anchorEndTime: Opt<number>, docAnchor: Opt<Doc>, addAsAnnotation: boolean) { if (anchorStartTime === undefined) return rootDoc; + const startTag = '_timecodeToShow'; + const endTag = '_timecodeToHide'; const anchor = docAnchor ?? Docs.Create.LabelDocument({ title: ComputedField.MakeFunction(`self["${endTag}"] ? "#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"]) : "#" + formatToTime(self["${startTag}"])`) as any, _minFontSize: 12, _maxFontSize: 24, - _singleLine: false, _stayInCollection: true, - useLinkSmallAnchor: true, - hideLinkButton: true, - _isLinkButton: true, + backgroundColor: 'rgba(128, 128, 128, 0.5)', + layout_hideLinkButton: true, + onClick: FollowLinkScript(), annotationOn: rootDoc, _timelineLabel: true, borderRounding: anchorEndTime === undefined ? '100%' : undefined, @@ -419,7 +428,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @action playOnClick = (anchorDoc: Doc, clientX: number) => { - const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; + const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); if (this.layoutDoc.autoPlayAnchors) { if (this.props.playing()) this.props.Pause(); @@ -428,7 +437,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack this.scrollToTime(seekTimeInSeconds); } } else { - if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) { + if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) && endTime > NumCast(this.layoutDoc._layout_currentTimecode)) { if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) { this.props.Pause(); } else { @@ -444,12 +453,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @action clickAnchor = (anchorDoc: Doc, clientX: number) => { - if (anchorDoc.isLinkButton) { - LinkFollower.FollowLink(undefined, anchorDoc, this.props, false); + if (IsFollowLinkScript(anchorDoc.onClick)) { + LinkFollower.FollowLink(undefined, anchorDoc, false); } - const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25; + const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.05; const endTime = this.anchorEnd(anchorDoc); - if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) { + if (seekTimeInSeconds < NumCast(this.layoutDoc._layout_currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._layout_currentTimecode) - 1e-4) { if (this.props.playing()) this.props.Pause(); else if (this.layoutDoc.autoPlayAnchors) this.props.Play(); else if (!this.layoutDoc.autoPlayAnchors) { @@ -505,37 +514,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack currentTimecode = () => this.currentTime; - @computed get renderDictation() { - const dictation = Cast(this.dataDoc[this.props.dictationKey], Doc, null); - return !dictation ? null : ( - <div - style={{ - position: 'absolute', - height: '100%', - top: this.timelineContentHeight, - background: Colors.LIGHT_BLUE, - }}> - <DocumentView - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit} - Document={dictation} - PanelHeight={this.dictationHeight} - isAnnotationOverlay={true} - isDocumentActive={returnFalse} - select={emptyFunction} - NativeDimScaling={returnOne} - xMargin={25} - yMargin={10} - ScreenToLocalTransform={this.dictationScreenToLocalTransform} - whenChildContentsActiveChanged={emptyFunction} - removeDocument={returnFalse} - moveDocument={returnFalse} - addDocument={returnFalse} - CollectionView={undefined} - renderDepth={this.props.renderDepth + 1}></DocumentView> - </div> - ); - } - // renders selection region on timeline @computed get selectionContainer() { const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd; @@ -561,7 +539,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack anchor, })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; - return ( + return this.clipDuration === 0 ? null : ( <div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : undefined }}> <div className="collectionStackedTimeline-timelineContainer" @@ -594,10 +572,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack top, width: `${width}px`, height: `${height}px`, - }} - onClick={e => { - this.props.playFrom(start, this.anchorEnd(d.anchor)); - e.stopPropagation(); + pointerEvents: 'none', }}> <StackedTimelineAnchor {...this.props} @@ -624,6 +599,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack {!this.props.PanelHeight() ? null : ( <AudioWaveform rawDuration={this.props.rawDuration} + fieldKey={this.props.dataFieldKey} duration={this.clipDuration} mediaPath={this.props.mediaPath} layoutDoc={this.layoutDoc} @@ -634,7 +610,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack PanelWidth={this.timelineContentWidth} /> )} - {/* {this.renderDictation} */} <div className="collectionStackedTimeline-hover" @@ -690,6 +665,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack interface StackedTimelineAnchorProps { mark: Doc; + whenChildContentsActiveChanged: (isActive: boolean) => void; + addDocTab: (doc: Doc, where: OpenWhere) => boolean; rangeClickScript: () => ScriptField; rangePlayScript: () => ScriptField; left: number; @@ -738,19 +715,19 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> () => this.props.currentTimecode(), time => { const dictationDoc = Cast(this.props.layoutDoc['data-dictation'], Doc, null); - const isDictation = dictationDoc && DocListCast(this.props.mark.links).some(link => Cast(link.anchor1, Doc, null)?.annotationOn === dictationDoc); + 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))*/ !this.props.layoutDoc.dontAutoFollowLinks && - DocListCast(this.props.mark.links).length && + LinkManager.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, this.props as any as DocumentViewProps, false); + LinkFollower.FollowLink(undefined, this.props.mark, false); } this._lastTimecode = time; } @@ -761,9 +738,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> this._disposer?.(); } + @observable noEvents = false; // starting the drag event for anchor resizing + @action onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { - this.props._timeline?.setPointerCapture(e.pointerId); + //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); @@ -779,8 +758,8 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> } return false; }; + this.noEvents = true; var undo: UndoManager.Batch | undefined; - setupMoveUpEvents( this, e, @@ -789,11 +768,11 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> this.props.setTime(newTime(e)); return changeAnchor(anchor, left, newTime(e)); }, - e => { + action(e => { this.props.setTime(newTime(e)); - this.props._timeline?.releasePointerCapture(e.pointerId); undo?.end(); - }, + this.noEvents = false; + }), emptyFunction ); }; @@ -811,19 +790,23 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> // renders anchor LabelBox renderInner = computedFn(function (this: StackedTimelineAnchor, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), screenXf: () => Transform, width: () => number, height: () => number) { const anchor = observable({ view: undefined as any }); - const focusFunc = (doc: Doc, options: DocFocusOptions) => { + const focusFunc = (doc: Doc, options: DocFocusOptions): number | undefined => { this.props.playLink(mark); - this.props.focus(doc, options); + return undefined; }; return { anchor, view: ( <DocumentView key="view" - {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit} + {...this.props} + NativeWidth={returnZero} + NativeHeight={returnZero} ref={action((r: DocumentView | null) => (anchor.view = r))} Document={mark} DataDoc={undefined} + docViewPath={returnEmptyDoclist} + pointerEvents={this.noEvents ? returnNone : undefined} styleProvider={this.props.styleProvider} renderDepth={this.props.renderDepth + 1} LayoutTemplate={undefined} @@ -831,9 +814,14 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> isDocumentActive={this.props.isDocumentActive} PanelWidth={width} PanelHeight={height} - fitWidth={returnTrue} + layout_fitWidth={returnTrue} ScreenToLocalTransform={screenXf} + pinToPres={emptyFunction} focus={focusFunc} + isContentActive={returnFalse} + searchFilterDocs={returnEmptyDoclist} + docFilters={returnEmptyFilter} + docRangeFilters={returnEmptyFilter} rootSelected={returnFalse} onClick={script} onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript} @@ -854,15 +842,15 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> render() { const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height); return ( - <> + <div style={{ pointerEvents: this.noEvents ? 'none' : undefined }}> {inner.view} {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? null : ( <> - <div key="left" className="collectionStackedTimeline-left-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} /> - <div key="right" className="collectionStackedTimeline-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} /> + <div key="left" className="collectionStackedTimeline-left-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} /> + <div key="right" className="collectionStackedTimeline-resizer" style={{ pointerEvents: this.noEvents ? 'none' : undefined }} onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} /> </> )} - </> + </div> ); } } |
