aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionStackedTimeline.tsx
diff options
context:
space:
mode:
authorljungster <parkerljung@gmail.com>2022-08-09 11:52:07 -0500
committerljungster <parkerljung@gmail.com>2022-08-09 11:52:07 -0500
commitda3cb00f809a482a9fdf732f6a656fbc467cce27 (patch)
tree9eb1fd278bc71d080d71bbfb7e3aec482d35f439 /src/client/views/collections/CollectionStackedTimeline.tsx
parent1638527259a072dfc2ab286bd27bbb1751e8434e (diff)
parent26670c8b9eb6e2fd981c3a0997bff5556b60504b (diff)
Merge branch 'parker' of https://github.com/brown-dash/Dash-Web into parker
Diffstat (limited to 'src/client/views/collections/CollectionStackedTimeline.tsx')
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx942
1 files changed, 468 insertions, 474 deletions
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index e09e9aa35..48e3abbc7 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,53 +1,35 @@
-import React = require("react");
-import {
- action,
- computed,
- IReactionDisposer,
- observable,
- reaction,
- runInAction
-} from "mobx";
-import { observer } from "mobx-react";
-import { computedFn } from "mobx-utils";
-import { Doc, DocListCast } 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 {
- emptyFunction,
- formatTime,
- OmitKeys,
- returnFalse,
- returnOne,
- setupMoveUpEvents,
- StopEvent
-} from "../../../Utils";
-import { Docs } from "../../documents/Documents";
-import { DocumentManager } from "../../util/DocumentManager";
-import { DragManager } from "../../util/DragManager";
-import { LinkManager } from "../../util/LinkManager";
-import { ScriptingGlobals } from "../../util/ScriptingGlobals";
-import { SelectionManager } from "../../util/SelectionManager";
-import { SnappingManager } from "../../util/SnappingManager";
-import { Transform } from "../../util/Transform";
-import { undoBatch } from "../../util/UndoManager";
-import { AudioWaveform } from "../AudioWaveform";
-import { CollectionSubView } from "../collections/CollectionSubView";
-import { Colors } from "../global/globalEnums";
-import { LightboxView } from "../LightboxView";
-import {
- DocAfterFocusFunc,
- DocFocusFunc,
- DocumentView,
- DocumentViewProps
-} from "../nodes/DocumentView";
-import { LabelBox } from "../nodes/LabelBox";
-import "./CollectionStackedTimeline.scss";
+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, 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 { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, returnTrue, 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 { ScriptingGlobals } from '../../util/ScriptingGlobals';
+import { SelectionManager } from '../../util/SelectionManager';
+import { SnappingManager } from '../../util/SnappingManager';
+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 { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps } from '../nodes/DocumentView';
+import { LabelBox } from '../nodes/LabelBox';
+import './CollectionStackedTimeline.scss';
+import { VideoBox } from '../nodes/VideoBox';
+import { ImageField } from '../../../fields/URLField';
export type CollectionStackedTimelineProps = {
- duration: number;
Play: () => void;
Pause: () => void;
playLink: (linkDoc: Doc) => void;
@@ -58,60 +40,73 @@ export type CollectionStackedTimelineProps = {
endTag: string;
mediaPath: string;
dictationKey: string;
- trimming: boolean;
- trimStart: number;
- trimEnd: number;
- trimDuration: number;
- setStartTrim: (newStart: number) => void;
- setEndTrim: (newEnd: number) => void;
+ rawDuration: number;
+ fieldKey: string;
};
+// trimming state: shows full clip, current trim bounds, or not trimming
+export enum TrimScope {
+ All = 2,
+ Clip = 1,
+ None = 0,
+}
+
@observer
export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
- @observable static SelectingRegion: CollectionStackedTimeline | undefined =
- undefined;
+ @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
+ @observable public static CurrentlyPlaying: Doc[];
+
static RangeScript: ScriptField;
static LabelScript: ScriptField;
static RangePlayScript: ScriptField;
static LabelPlayScript: ScriptField;
- private _timeline: HTMLDivElement | null = null;
+ 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 _markerEnd: number = 0;
+ @observable _markerEnd: number | undefined;
+ @observable _trimming: number = TrimScope.None;
+ @observable _trimStart: number = 0; // trim controls start pos
+ @observable _trimEnd: number = 0; // trim controls end pos
- get minLength() {
- const rect = this._timeline?.getBoundingClientRect();
- if (rect) {
- return 0.05 * this.duration;
- }
- return 0;
- }
+ @observable _zoomFactor: number = 1;
+ @observable _scroll: number = 0;
+
+ @observable _hoverTime: number = 0;
- get trimStart() {
- return this.props.trimStart;
+ @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);
}
- get trimEnd() {
- return this.props.trimEnd;
+ @computed get trimStart() {
+ return this.IsTrimming !== TrimScope.None ? this._trimStart : this.clipStart;
+ }
+ @computed get trimDuration() {
+ return this.trimEnd - this.trimStart;
+ }
+ @computed get trimEnd() {
+ return this.IsTrimming !== TrimScope.None ? this._trimEnd : this.clipEnd;
}
- get duration() {
- return this.props.duration;
+ @computed get clipStart() {
+ return this.IsTrimming === TrimScope.All ? 0 : NumCast(this.layoutDoc.clipStart);
+ }
+ @computed get clipDuration() {
+ return this.clipEnd - this.clipStart;
+ }
+ @computed get clipEnd() {
+ return this.IsTrimming === TrimScope.All ? this.props.rawDuration : NumCast(this.layoutDoc.clipEnd, this.props.rawDuration);
}
@computed get currentTime() {
return NumCast(this.layoutDoc._currentTimecode);
}
- @computed get selectionContainer() {
- return CollectionStackedTimeline.SelectingRegion !== this ? null : (
- <div
- className="collectionStackedTimeline-selector"
- style={{
- left: `${((Math.min(this._markerStart, this._markerEnd) - this.trimStart) / this.props.trimDuration) * 100}%`,
- width: `${(Math.abs(this._markerStart - this._markerEnd) / this.props.trimDuration) * 100}%`,
- }}
- />
- );
+
+ @computed get zoomFactor() {
+ return this._zoomFactor;
}
constructor(props: any) {
@@ -121,82 +116,109 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
CollectionStackedTimeline.RangeScript ||
ScriptField.MakeFunction(`scriptContext.clickAnchor(this, clientX)`, {
self: Doc.name,
- scriptContext: "any",
- clientX: "number",
+ scriptContext: 'any',
+ clientX: 'number',
})!;
CollectionStackedTimeline.RangePlayScript =
CollectionStackedTimeline.RangePlayScript ||
ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, {
self: Doc.name,
- scriptContext: "any",
- clientX: "number",
+ scriptContext: 'any',
+ clientX: 'number',
})!;
}
componentDidMount() {
- document.addEventListener("keydown", this.keyEvents, true);
+ document.addEventListener('keydown', this.keyEvents, true);
}
+
+ @action
componentWillUnmount() {
- document.removeEventListener("keydown", this.keyEvents, true);
+ document.removeEventListener('keydown', this.keyEvents, true);
if (CollectionStackedTimeline.SelectingRegion === this) {
- runInAction(
- () => (CollectionStackedTimeline.SelectingRegion = undefined)
- );
+ CollectionStackedTimeline.SelectingRegion = undefined;
}
}
- anchorStart = (anchor: Doc) =>
- NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]))
- anchorEnd = (anchor: Doc, val: any = null) => {
- const endVal = NumCast(anchor[this.props.endTag], val);
- return NumCast(
- anchor._timecodeToHide,
- endVal === undefined ? null : endVal
- );
+ public get IsTrimming() {
+ return this._trimming;
}
- toTimeline = (screen_delta: number, width: number) => {
- return Math.max(
- this.trimStart,
- Math.min(this.trimEnd, (screen_delta / width) * this.props.trimDuration + this.trimStart));
+
+ @action
+ public StartTrimming(scope: TrimScope) {
+ this._trimStart = this.clipStart;
+ this._trimEnd = this.clipEnd;
+ this._trimming = scope;
+ }
+ @action
+ public StopTrimming() {
+ this.layoutDoc.clipStart = this.trimStart;
+ this.layoutDoc.clipEnd = this.trimEnd;
+ this._trimming = TrimScope.None;
+ }
+
+ @action
+ public setZoom(zoom: number) {
+ this._zoomFactor = zoom;
}
+ anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag]));
+ 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));
+ };
+
rangeClickScript = () => CollectionStackedTimeline.RangeScript;
rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
- // for creating key anchors with key events
+ // handles key events for for creating key anchors, scrubbing, exiting trim
@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
+ const jump = e.shiftKey ? 1 : 0.1;
switch (e.key) {
- case " ":
+ case ' ':
if (!CollectionStackedTimeline.SelectingRegion) {
this._markerStart = this._markerEnd = this.currentTime;
CollectionStackedTimeline.SelectingRegion = this;
} else {
- CollectionStackedTimeline.createAnchor(
- this.rootDoc,
- this.dataDoc,
- this.props.fieldKey,
- this.props.startTag,
- this.props.endTag,
- this.currentTime
- );
+ this._markerEnd = this.currentTime;
+ CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this._markerStart, this._markerEnd);
+ this._markerEnd = undefined;
CollectionStackedTimeline.SelectingRegion = undefined;
}
+ e.stopPropagation();
+ break;
+ case 'Escape':
+ // abandons current trim
+ this._trimStart = this.clipStart;
+ this._trimStart = this.clipEnd;
+ this._trimming = TrimScope.None;
+ e.stopPropagation();
+ break;
+ case 'ArrowLeft':
+ this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
+ e.stopPropagation();
+ break;
+ case 'ArrowRight':
+ this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime + jump), this.clipEnd));
+ e.stopPropagation();
+ break;
}
}
- }
+ };
getLinkData(l: Doc) {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- const linkTime = NumCast(
- la2[this.props.startTag],
- NumCast(la1[this.props.startTag])
- );
+ 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;
@@ -204,28 +226,24 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
return { la1, la2, linkTime };
}
- // starting the drag event for anchor resizing
+ // handles dragging selection to create markers
@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;
if (rect && this.props.isContentActive()) {
const wasPlaying = this.props.playing();
if (wasPlaying) this.props.Pause();
- const wasSelecting = CollectionStackedTimeline.SelectingRegion === this;
+ var wasSelecting = this._markerEnd !== undefined;
setupMoveUpEvents(
this,
e,
- action((e) => {
- if (
- !wasSelecting &&
- CollectionStackedTimeline.SelectingRegion !== this
- ) {
- this._markerStart = this._markerEnd = this.toTimeline(
- clientX - rect.x,
- rect.width
- );
- CollectionStackedTimeline.SelectingRegion = this;
+ action(e => {
+ if (!wasSelecting) {
+ this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
+ wasSelecting = true;
}
this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width);
return false;
@@ -237,117 +255,125 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerStart = this._markerEnd;
this._markerEnd = tmp;
}
- if (
- !isClick &&
- CollectionStackedTimeline.SelectingRegion === this &&
- Math.abs(movement[0]) > 15 &&
- !this.props.trimming
- ) {
- const anchor = CollectionStackedTimeline.createAnchor(
- this.rootDoc,
- this.dataDoc,
- this.props.fieldKey,
- this.props.startTag,
- this.props.endTag,
- this._markerStart,
- this._markerEnd
- );
+ 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);
setTimeout(() => DocumentManager.Instance.getDocumentView(anchor)?.select(false));
}
- (!isClick || !wasSelecting) &&
- (CollectionStackedTimeline.SelectingRegion = undefined);
+ (!isClick || !wasSelecting) && (this._markerEnd = undefined);
}),
(e, doubleTap) => {
- this.props.select(false);
- e.shiftKey &&
- CollectionStackedTimeline.createAnchor(
- this.rootDoc,
- this.dataDoc,
- this.props.fieldKey,
- this.props.startTag,
- this.props.endTag,
- this.currentTime
- );
- !wasPlaying && doubleTap && this.props.Play();
+ if (e.button !== 2) {
+ this.props.select(false);
+ !wasPlaying && doubleTap && this.props.Play();
+ }
},
this.props.isSelected(true) || this.props.isContentActive(),
undefined,
() => {
- !wasPlaying &&
- (this.props.trimming && this.duration ?
- this.props.setTime(((clientX - rect.x) / rect.width) * this.duration)
- :
- this.props.setTime(((clientX - rect.x) / rect.width) * this.props.trimDuration + this.trimStart)
- );
+ if (shiftKey) {
+ CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime);
+ } else {
+ !wasPlaying && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ }
}
);
}
+ };
- }
+ @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 = StrListCast(this.dataDoc.thumbnails);
+ const imgField = thumbnails?.length > 0 ? new ImageField(thumbnails[nearest]) : undefined;
+ this._thumbnail = imgField?.url?.href ? imgField.url.href.replace('.png', '_m.png') : undefined;
+ }
+ }
+ };
+ // for dragging trim start handle
@action
trimLeft = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();
- const clientX = e.movementX;
setupMoveUpEvents(
this,
e,
action((e, [], []) => {
if (rect && this.props.isContentActive()) {
- this.props.setStartTrim(Math.min(
- Math.max(
- this.trimStart + (e.movementX / rect.width) * this.duration,
- 0
- ),
- this.trimEnd - this.minLength
- ));
+ this._trimStart = Math.min(Math.max(this.trimStart + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
}
return false;
}),
emptyFunction,
action((e, doubleTap) => {
- if (doubleTap) {
- this.props.setStartTrim(0);
- }
+ doubleTap && (this._trimStart = this.clipStart);
})
);
- }
+ };
+ // for dragging trim end handle
@action
trimRight = (e: React.PointerEvent): void => {
const rect = this._timeline?.getBoundingClientRect();
- const clientX = e.movementX;
setupMoveUpEvents(
this,
e,
action((e, [], []) => {
if (rect && this.props.isContentActive()) {
- this.props.setEndTrim(Math.max(
- Math.min(
- this.trimEnd + (e.movementX / rect.width) * this.duration,
- this.duration
- ),
- this.trimStart + this.minLength
- ));
+ this._trimEnd = Math.max(Math.min(this.trimEnd + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
}
return false;
}),
emptyFunction,
action((e, doubleTap) => {
- if (doubleTap) {
- this.props.setEndTrim(this.duration);
- }
+ doubleTap && (this._trimEnd = this.clipEnd);
})
);
- }
+ };
+
+ // for rendering scrolling when timeline zoomed
+ @action
+ setScroll = (e: React.UIEvent) => {
+ e.stopPropagation();
+ this._scroll = this._timelineWrapper!.scrollLeft;
+ };
+ // smooth scrolls to time like when following links overflowed due to zoom
+ @action
+ scrollToTime = (time: number) => {
+ if (this._timelineWrapper) {
+ if (time > this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) {
+ this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth());
+ smoothScrollHorizontal(200, this._timelineWrapper, this._scroll);
+ } else if (time < this.toTimeline(this._scroll, this.timelineContentWidth)) {
+ this._scroll = (time / this.timelineContentWidth) * this.clipDuration;
+ smoothScrollHorizontal(200, this._timelineWrapper, this._scroll);
+ }
+ }
+ };
+
+ // handles dragging and dropping markers in timeline
@action
internalDocDrop(e: Event, de: DragManager.DropEvent, docDragData: DragManager.DocumentDragData, xp: number) {
- if (!de.embedKey && this.props.layerProvider?.(this.props.Document) !== false && this.props.Document._isGroup) return false;
+ if (!de.embedKey && this.props.Document._isGroup) return false;
if (!super.onInternalDrop(e, de)) return false;
-
// 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.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ const x = localPt[0] - docDragData.offset[0];
+ const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth);
+ docDragData.droppedDocuments.forEach(drop => {
+ const anchorEnd = this.anchorEnd(drop);
+ if (anchorEnd !== undefined) {
+ Doc.SetInPlace(drop, drop._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
+ }
+ Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false);
+ });
return true;
}
@@ -355,34 +381,31 @@ 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);
return false;
- }
+ };
+ // creates marker on timeline
@undoBatch
@action
- static createAnchor(
- rootDoc: Doc,
- dataDoc: Doc,
- fieldKey: string,
- startTag: string,
- endTag: string,
- anchorStartTime?: number,
- anchorEndTime?: number,
- docAnchor?: Doc
- ) {
+ static createAnchor(rootDoc: Doc, dataDoc: Doc, fieldKey: string, startTag: string, endTag: string, anchorStartTime?: number, anchorEndTime?: number, docAnchor?: Doc) {
if (anchorStartTime === undefined) return rootDoc;
- const anchor = docAnchor ?? Docs.Create.LabelDocument({
- title: ComputedField.MakeFunction(
- `"#" + formatToTime(self["${startTag}"]) + "-" + formatToTime(self["${endTag}"])`
- ) as any,
- _stayInCollection: true,
- useLinkSmallAnchor: true,
- hideLinkButton: true,
- annotationOn: rootDoc,
- _timelineLabel: true,
- });
+ 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,
+ annotationOn: rootDoc,
+ _timelineLabel: true,
+ borderRounding: anchorEndTime === undefined ? '100%' : undefined,
+ });
Doc.GetProto(anchor)[startTag] = anchorStartTime;
Doc.GetProto(anchor)[endTag] = anchorEndTime;
- if (Cast(dataDoc[fieldKey], listSpec(Doc), null) !== undefined) {
+ if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) {
Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor);
} else {
dataDoc[fieldKey] = new List<Doc>([anchor]);
@@ -396,12 +419,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
const endTime = this.anchorEnd(anchorDoc);
if (this.layoutDoc.autoPlayAnchors) {
if (this.props.playing()) this.props.Pause();
- else this.props.playFrom(seekTimeInSeconds, endTime);
+ else {
+ this.props.playFrom(seekTimeInSeconds, endTime);
+ this.scrollToTime(seekTimeInSeconds);
+ }
} else {
- if (
- seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) &&
- endTime > NumCast(this.layoutDoc._currentTimecode)
- ) {
+ if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) {
if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
this.props.Pause();
} else {
@@ -409,62 +432,47 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
} else {
this.props.playFrom(seekTimeInSeconds, endTime);
+ this.scrollToTime(seekTimeInSeconds);
}
}
return { select: true };
- }
+ };
@action
clickAnchor = (anchorDoc: Doc, clientX: number) => {
if (anchorDoc.isLinkButton) {
- LinkManager.FollowLink(undefined, anchorDoc, this.props, false);
+ LinkFollower.FollowLink(undefined, anchorDoc, this.props, false);
}
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
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._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) {
if (this.props.playing()) this.props.Pause();
else if (this.layoutDoc.autoPlayAnchors) this.props.Play();
else if (!this.layoutDoc.autoPlayAnchors) {
const rect = this._timeline?.getBoundingClientRect();
- rect &&
- this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
+ rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width));
}
} else {
if (this.layoutDoc.autoPlayAnchors) {
this.props.playFrom(seekTimeInSeconds, endTime);
- }
- else {
+ } else {
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.props.PanelWidth();
+ getLevel = (m: Doc, placed: { anchorStartTime: number; anchorEndTime: number; level: number }[]) => {
+ const timelineContentWidth = this.timelineContentWidth;
const x1 = this.anchorStart(m);
- const x2 = this.anchorEnd(
- m,
- x1 + (10 / timelineContentWidth) * this.duration
- );
+ const x2 = this.anchorEnd(m, x1 + (10 / timelineContentWidth) * this.clipDuration);
let max = 0;
const overlappedLevels = new Set(
- placed.map((p) => {
+ placed.map(p => {
const y1 = p.anchorStartTime;
const y2 = p.anchorEndTime;
- if (
- (x1 >= y1 && x1 <= y2) ||
- (x2 >= y1 && x2 <= y2) ||
- (y1 >= x1 && y1 <= x2) ||
- (y2 >= x1 && y2 <= x2)
- ) {
+ 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;
}
@@ -475,40 +483,42 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
placed.push({ anchorStartTime: x1, anchorEndTime: x2, level });
return level;
- }
+ };
dictationHeightPercent = 50;
- dictationHeight = () =>
- (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100
- timelineContentHeight = () =>
- (this.props.PanelHeight() * this.dictationHeightPercent) / 100
- dictationScreenToLocalTransform = () =>
- this.props
- .ScreenToLocalTransform()
- .translate(0, -this.timelineContentHeight())
+ 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;
+ } // subtract size of container border
+
+ dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight);
+
+ isContentActive = () => this.props.isSelected() || this.props.isContentActive();
+
+ 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(),
+ position: 'absolute',
+ height: '100%',
+ top: this.timelineContentHeight,
background: Colors.LIGHT_BLUE,
- }}
- >
+ }}>
<DocumentView
- {...OmitKeys(this.props, [
- "NativeWidth",
- "NativeHeight",
- "setContentView",
- ]).omit}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight', 'setContentView']).omit}
Document={dictation}
PanelHeight={this.dictationHeight}
isAnnotationOverlay={true}
isDocumentActive={returnFalse}
select={emptyFunction}
- scaling={returnOne}
+ NativeDimScaling={returnOne}
xMargin={25}
yMargin={10}
ScreenToLocalTransform={this.dictationScreenToLocalTransform}
@@ -517,149 +527,161 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
moveDocument={returnFalse}
addDocument={returnFalse}
CollectionView={undefined}
- renderDepth={this.props.renderDepth + 1}
- ></DocumentView>
+ renderDepth={this.props.renderDepth + 1}></DocumentView>
</div>
);
}
- @computed get renderAudioWaveform() {
- return !this.props.mediaPath ? null : (
- <div className="collectionStackedTimeline-waveform">
- <AudioWaveform
- duration={this.duration}
- mediaPath={this.props.mediaPath}
- layoutDoc={this.layoutDoc}
- PanelHeight={this.timelineContentHeight}
- trimming={this.props.trimming}
- />
- </div>
+
+ // renders selection region on timeline
+ @computed get selectionContainer() {
+ const markerEnd = CollectionStackedTimeline.SelectingRegion === this ? this.currentTime : this._markerEnd;
+ return markerEnd === undefined ? null : (
+ <div
+ className="collectionStackedTimeline-selector"
+ style={{
+ left: `${((Math.min(this._markerStart, markerEnd) - this.trimStart) / this.trimDuration) * 100}%`,
+ width: `${(Math.abs(this._markerStart - markerEnd) / this.trimDuration) * 100}%`,
+ }}
+ />
);
}
- currentTimecode = () => this.currentTime;
render() {
- const timelineContentWidth = this.props.PanelWidth();
const overlaps: {
anchorStartTime: number;
anchorEndTime: number;
level: number;
}[] = [];
- const drawAnchors = this.childDocs.map((anchor) => ({
+ const drawAnchors = this.childDocs.map(anchor => ({
level: this.getLevel(anchor, overlaps),
anchor,
}));
const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
- const isActive =
- this.props.isContentActive() || this.props.isSelected(false);
- return (<div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined }}>
- <div
- className="collectionStackedTimeline"
- ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
- onClick={(e) => isActive && StopEvent(e)}
- onPointerDown={(e) => isActive && this.onPointerDownTimeline(e)}
- >
- {drawAnchors.map((d) => {
-
- const start = this.anchorStart(d.anchor);
- const end = this.anchorEnd(
- d.anchor,
- start + (10 / timelineContentWidth) * this.duration
- );
- const left = this.props.trimming ?
- (start / this.duration) * timelineContentWidth
- : (start - this.trimStart) / this.props.trimDuration * timelineContentWidth;
- const top = (d.level / maxLevel) * this.timelineContentHeight();
- const timespan = end - start;
- const width = (timespan / this.props.trimDuration) * timelineContentWidth;
- const height = this.timelineContentHeight() / maxLevel;
- return this.props.Document.hideAnchors ? null : (
- <div
- className={"collectionStackedTimeline-marker-timeline"}
- key={d.anchor[Id]}
- style={{
- left,
- top,
- width: `${width}px`,
- height: `${height}px`,
- }}
- onClick={(e) => {
- this.props.playFrom(start, this.anchorEnd(d.anchor));
- e.stopPropagation();
- }}
- >
- <StackedTimelineAnchor
- {...this.props}
- mark={d.anchor}
- rangeClickScript={this.rangeClickScript}
- rangePlayScript={this.rangePlayScript}
- left={left}
- top={top}
- width={width}
- height={height}
- toTimeline={this.toTimeline}
- layoutDoc={this.layoutDoc}
- currentTimecode={this.currentTimecode}
- _timeline={this._timeline}
- stackedTimeline={this}
- trimStart={this.trimStart}
- trimEnd={this.trimEnd}
- />
- </div>
- );
- })}
- {!this.props.trimming && this.selectionContainer}
- {this.renderAudioWaveform}
- {this.renderDictation}
-
+ return (
+ <div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : undefined }}>
<div
- className="collectionStackedTimeline-current"
- style={{
- left: this.props.trimming
- ? `${(this.currentTime / this.duration) * 100}%`
- : `${(this.currentTime - this.trimStart) / (this.trimEnd - this.trimStart) * 100}%`,
- }}
- />
-
- {this.props.trimming && (
- <>
- <div
- className="collectionStackedTimeline-trim-shade"
- style={{ width: `${(this.trimStart / this.duration) * 100}%` }}
- ></div>
+ className="collectionStackedTimeline-timelineContainer"
+ 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"
+ ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)}
+ onClick={e => this.isContentActive() && StopEvent(e)}
+ onPointerDown={e => this.isContentActive() && this.onPointerDownTimeline(e)}
+ style={{ width: this.timelineContentWidth }}>
+ {drawAnchors.map(d => {
+ const start = this.anchorStart(d.anchor);
+ const end = this.anchorEnd(d.anchor, start + (10 / this.timelineContentWidth) * this.clipDuration);
+ if (end < this.clipStart || start > this.clipEnd) return null;
+ const left = Math.max(((start - this.clipStart) / this.clipDuration) * this.timelineContentWidth, 0);
+ const top = (d.level / maxLevel) * this.props.PanelHeight();
+ const timespan = Math.max(0, Math.min(end - this.clipStart, this.clipEnd)) - Math.max(0, start - this.clipStart);
+ const width = (timespan / this.clipDuration) * this.timelineContentWidth;
+ const height = this.props.PanelHeight() / maxLevel;
+ return this.props.Document.hideAnchors ? null : (
+ <div
+ className={'collectionStackedTimeline-marker-timeline'}
+ key={d.anchor[Id]}
+ style={{
+ left,
+ top,
+ width: `${width}px`,
+ height: `${height}px`,
+ }}
+ onClick={e => {
+ this.props.playFrom(start, this.anchorEnd(d.anchor));
+ e.stopPropagation();
+ }}>
+ <StackedTimelineAnchor
+ {...this.props}
+ mark={d.anchor}
+ rangeClickScript={this.rangeClickScript}
+ rangePlayScript={this.rangePlayScript}
+ left={left - this._scroll}
+ top={top}
+ width={width}
+ height={height}
+ toTimeline={this.toTimeline}
+ layoutDoc={this.layoutDoc}
+ // isDocumentActive={this.props.childDocumentsActive ? this.props.isDocumentActive : this.isContentActive}
+ currentTimecode={this.currentTimecode}
+ _timeline={this._timeline}
+ stackedTimeline={this}
+ trimStart={this.trimStart}
+ trimEnd={this.trimEnd}
+ />
+ </div>
+ );
+ })}
+ {!this.IsTrimming && this.selectionContainer}
+ <AudioWaveform
+ rawDuration={this.props.rawDuration}
+ duration={this.clipDuration}
+ mediaPath={this.props.mediaPath}
+ layoutDoc={this.layoutDoc}
+ clipStart={this.clipStart}
+ clipEnd={this.clipEnd}
+ zoomFactor={this.zoomFactor}
+ PanelHeight={this.timelineContentHeight}
+ PanelWidth={this.timelineContentWidth}
+ />
+ {/* {this.renderDictation} */}
<div
- className="collectionStackedTimeline-trim-controls"
+ className="collectionStackedTimeline-hover"
style={{
- left: `${(this.trimStart / this.duration) * 100}%`,
- width: `${((this.trimEnd - this.trimStart) / this.duration) * 100
- }%`,
+ left: `${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%`,
}}
- >
- <div
- className="collectionStackedTimeline-trim-handle"
- onPointerDown={this.trimLeft}
- ></div>
- <div
- className="collectionStackedTimeline-trim-handle"
- onPointerDown={this.trimRight}
- ></div>
- </div>
+ />
<div
- className="collectionStackedTimeline-trim-shade"
+ className="collectionStackedTimeline-current"
style={{
- left: `${(this.trimEnd / this.duration) * 100}%`,
- width: `${((this.duration - this.trimEnd) / this.duration) * 100
- }%`,
+ left: `${((this.currentTime - this.clipStart) / this.clipDuration) * 100}%`,
}}
- ></div>
- </>
- )}
+ />
+
+ {this.IsTrimming !== TrimScope.None && (
+ <>
+ <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }}></div>
+
+ <div
+ className="collectionStackedTimeline-trim-controls"
+ style={{
+ 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>
+
+ <div
+ className="collectionStackedTimeline-trim-shade"
+ style={{
+ left: `${((this.trimEnd - this.clipStart) / this.clipDuration) * 100}%`,
+ width: `${((this.clipEnd - this.trimEnd) / this.clipDuration) * 100}%`,
+ }}></div>
+ </>
+ )}
+ </div>
+ </div>
+ <div className="timeline-hoverUI" style={{ left: `calc(${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%` }}>
+ <div className="hoverTime">{formatTime(this._hoverTime - this.clipStart)}</div>
+ {this._thumbnail && <img className="videoBox-thumbnail" src={this._thumbnail} />}
+ </div>
</div>
- </div>);
+ );
}
}
+/**
+ * StackedTimelineAnchor
+ * creates the anchors to display markers, links, and embedded documents on timeline
+ */
+
interface StackedTimelineAnchorProps {
mark: Doc;
rangeClickScript: () => ScriptField;
@@ -675,6 +697,7 @@ interface StackedTimelineAnchorProps {
endTag: string;
renderDepth: number;
layoutDoc: Doc;
+ isDocumentActive?: () => boolean;
ScreenToLocalTransform: () => Transform;
_timeline: HTMLDivElement | null;
focus: DocFocusFunc;
@@ -684,54 +707,53 @@ interface StackedTimelineAnchorProps {
trimStart: number;
trimEnd: number;
}
+
@observer
class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> {
_lastTimecode: number;
_disposer: IReactionDisposer | undefined;
+
constructor(props: any) {
super(props);
this._lastTimecode = this.props.currentTimecode();
}
+
+ // updates marker document title to reflect correct timecodes
+ computeTitle = () => {
+ if (this.props.mark.type !== DocumentType.LABEL) return undefined;
+ const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
+ const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
+ return `#${formatTime(start)}-${formatTime(end)}`;
+ };
+
componentDidMount() {
this._disposer = reaction(
() => 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
- );
+ 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);
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 &&
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
) {
- LinkManager.FollowLink(
- undefined,
- this.props.mark,
- this.props as any as DocumentViewProps,
- false,
- true
- );
+ LinkFollower.FollowLink(undefined, this.props.mark, this.props as any as DocumentViewProps, false, true);
}
this._lastTimecode = time;
}
);
}
+
componentWillUnmount() {
this._disposer?.();
}
+
// starting the drag event for anchor resizing
onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
this.props._timeline?.setPointerCapture(e.pointerId);
@@ -739,61 +761,50 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
const rect = (e.target as any).getBoundingClientRect();
return this.props.toTimeline(e.clientX - rect.x, rect.width);
};
- const changeAnchor = (anchor: Doc, left: boolean, time: number) => {
- const timelineOnly =
- Cast(anchor[this.props.startTag], "number", null) !== undefined;
+ const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
+ const timelineOnly = Cast(anchor[this.props.startTag], 'number', null) !== undefined;
if (timelineOnly) {
- Doc.SetInPlace(
- anchor,
- left ? this.props.startTag : this.props.endTag,
- time,
- true
- );
- }
- else {
- left
- ? (anchor._timecodeToShow = time)
- : (anchor._timecodeToHide = time);
+ 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, 'borderRounding', time !== undefined ? undefined : '100%', true);
+ } else {
+ anchor[left ? '_timecodeToShow' : '_timecodeToHide'] = time;
}
return false;
};
+ var undo: UndoManager.Batch | undefined;
+
setupMoveUpEvents(
this,
e,
- (e) => changeAnchor(anchor, left, newTime(e)),
- (e) => {
+ e => {
+ if (!undo) undo = UndoManager.StartBatch('drag anchor');
+ this.props.setTime(newTime(e));
+ return changeAnchor(anchor, left, newTime(e));
+ },
+ e => {
this.props.setTime(newTime(e));
this.props._timeline?.releasePointerCapture(e.pointerId);
+ undo?.end();
},
emptyFunction
);
- }
-
- @action
- computeTitle = () => {
- const start = Math.max(NumCast(this.props.mark[this.props.startTag]), this.props.trimStart) - this.props.trimStart;
- const end = Math.min(NumCast(this.props.mark[this.props.endTag]), this.props.trimEnd) - this.props.trimStart;
- return `#${formatTime(start)}-${formatTime(end)}`;
- }
+ };
+
+ // context menu
+ contextMenuItems = () => {
+ 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];
+ };
- renderInner = computedFn(function (
- this: StackedTimelineAnchor,
- mark: Doc,
- script: undefined | (() => ScriptField),
- doublescript: undefined | (() => ScriptField),
- x: number,
- y: number,
- width: number,
- height: number
- ) {
+ // 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,
- willZoom?: boolean,
- scale?: number,
- afterFocus?: DocAfterFocusFunc,
- docTransform?: Transform
- ) => {
+ const focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => {
this.props.playLink(mark);
this.props.focus(doc, { willZoom, scale, afterFocus, docTransform });
};
@@ -802,62 +813,45 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
view: (
<DocumentView
key="view"
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
+ {...OmitKeys(this.props, ['NativeWidth', 'NativeHeight']).omit}
ref={action((r: DocumentView | null) => (anchor.view = r))}
Document={mark}
DataDoc={undefined}
renderDepth={this.props.renderDepth + 1}
LayoutTemplate={undefined}
- LayoutTemplateString={LabelBox.LayoutStringWithTitle(LabelBox, "data", this.computeTitle())}
- isDocumentActive={returnFalse}
- PanelWidth={() => width}
- PanelHeight={() => height}
- ScreenToLocalTransform={() =>
- this.props.ScreenToLocalTransform().translate(-x, -y)
- }
+ LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())}
+ isDocumentActive={this.props.isDocumentActive}
+ PanelWidth={width}
+ PanelHeight={height}
+ fitWidth={returnTrue}
+ ScreenToLocalTransform={screenXf}
focus={focusFunc}
rootSelected={returnFalse}
onClick={script}
- onDoubleClick={
- this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript
- }
+ onDoubleClick={this.props.layoutDoc.autoPlayAnchors ? undefined : doublescript}
ignoreAutoHeight={false}
hideResizeHandles={true}
bringToFront={emptyFunction}
+ contextMenuItems={this.contextMenuItems}
scriptContext={this.props.stackedTimeline}
/>
),
};
});
+ anchorScreenToLocalXf = () => this.props.ScreenToLocalTransform().translate(-this.props.left, -this.props.top);
+ width = () => this.props.width;
+ height = () => this.props.height;
+
render() {
- const inner = this.renderInner(
- this.props.mark,
- this.props.rangeClickScript,
- this.props.rangePlayScript,
- this.props.left,
- this.props.top,
- this.props.width,
- this.props.height
- );
+ const inner = this.renderInner(this.props.mark, this.props.rangeClickScript, this.props.rangePlayScript, this.anchorScreenToLocalXf, this.width, this.height);
return (
<>
{inner.view}
- {!inner.anchor.view ||
- !SelectionManager.IsSelected(inner.anchor.view) ? null : (
+ {!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" onPointerDown={e => this.onAnchorDown(e, this.props.mark, true)} />
+ <div key="right" className="collectionStackedTimeline-resizer" onPointerDown={e => this.onAnchorDown(e, this.props.mark, false)} />
</>
)}
</>
@@ -872,4 +866,4 @@ ScriptingGlobals.add(function min(num1: number, num2: number): number {
});
ScriptingGlobals.add(function max(num1: number, num2: number): number {
return Math.max(num1, num2);
-}); \ No newline at end of file
+});