aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections/CollectionStackedTimeline.tsx
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2022-07-08 00:17:26 -0400
committerbobzel <zzzman@gmail.com>2022-07-08 00:17:26 -0400
commit146f8622d5bac2edc6b09f57c173bd057dfbcfad (patch)
treef871089c438a476543ca96bac163c0532b9557c7 /src/client/views/collections/CollectionStackedTimeline.tsx
parentb7e66da6b23cdb41c127000dfe13843d35f7d0cc (diff)
restructured currentUserUtils to avoid having import cycles.
Diffstat (limited to 'src/client/views/collections/CollectionStackedTimeline.tsx')
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx716
1 files changed, 279 insertions, 437 deletions
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index b00017453..dcf3f7c51 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,50 +1,33 @@
-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 } 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 { 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, 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";
+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 } 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 = {
Play: () => void;
@@ -68,7 +51,6 @@ export enum TrimScope {
None = 0,
}
-
@observer
export class CollectionStackedTimeline extends CollectionSubView<CollectionStackedTimelineProps>() {
@observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined;
@@ -94,21 +76,38 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
@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); }
-
- @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; }
+ // 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);
+ }
- @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 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;
+ }
- @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); }
+ @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 zoomFactor() { return this._zoomFactor; }
+ @computed get currentTime() {
+ return NumCast(this.layoutDoc._currentTimecode);
+ }
+ @computed get zoomFactor() {
+ return this._zoomFactor;
+ }
constructor(props: any) {
super(props);
@@ -117,32 +116,33 @@ 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) {
CollectionStackedTimeline.SelectingRegion = undefined;
}
}
-
- public get IsTrimming() { return this._trimming; }
+ public get IsTrimming() {
+ return this._trimming;
+ }
@action
public StartTrimming(scope: TrimScope) {
@@ -162,81 +162,63 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
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));
- }
-
+ return Math.max(this.clipStart, Math.min(this.clipEnd, (screen_delta / width) * this.clipDuration + this.clipStart));
+ };
rangeClickScript = () => CollectionStackedTimeline.RangeScript;
rangePlayScript = () => CollectionStackedTimeline.RangePlayScript;
-
// handles key events for for creating key anchors, scrubbing, exiting trim
@action
keyEvents = (e: KeyboardEvent) => {
if (
// need to include range inputs because after dragging video time slider it becomes target element
- !(e.target instanceof HTMLInputElement && !(e.target.type === "range")) &&
+ !(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 {
this._markerEnd = this.currentTime;
- CollectionStackedTimeline.createAnchor(
- this.rootDoc,
- this.dataDoc,
- this.props.fieldKey,
- this.props.startTag,
- this.props.endTag,
- this._markerStart,
- this._markerEnd
- );
+ 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":
+ case 'Escape':
// abandons current trim
this._trimStart = this.clipStart;
this._trimStart = this.clipEnd;
this._trimming = TrimScope.None;
e.stopPropagation();
break;
- case "ArrowLeft":
+ case 'ArrowLeft':
this.props.setTime(Math.min(Math.max(this.clipStart, this.currentTime - jump), this.clipEnd));
e.stopPropagation();
break;
- case "ArrowRight":
+ 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;
@@ -244,7 +226,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
return { la1, la2, linkTime };
}
-
// handles dragging selection to create markers
@action
onPointerDownTimeline = (e: React.PointerEvent): void => {
@@ -259,7 +240,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
setupMoveUpEvents(
this,
e,
- action((e) => {
+ action(e => {
if (!wasSelecting) {
this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width);
wasSelecting = true;
@@ -274,24 +255,11 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
this._markerStart = this._markerEnd;
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
- );
+ 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) &&
- (this._markerEnd = undefined);
+ (!isClick || !wasSelecting) && (this._markerEnd = undefined);
}),
(e, doubleTap) => {
if (e.button !== 2) {
@@ -303,23 +271,14 @@ 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
- );
+ 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 => {
@@ -329,15 +288,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
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") : "";
+ 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
@@ -348,13 +306,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
e,
action((e, [], []) => {
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 + (e.movementX / rect.width) * this.clipDuration, this.clipStart), this.trimEnd - this.minTrimLength);
}
return false;
}),
@@ -363,7 +315,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
doubleTap && (this._trimStart = this.clipStart);
})
);
- }
+ };
// for dragging trim end handle
@action
@@ -374,13 +326,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
e,
action((e, [], []) => {
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 + (e.movementX / rect.width) * this.clipDuration, this.clipEnd), this.trimStart + this.minTrimLength);
}
return false;
}),
@@ -389,15 +335,14 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
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
@@ -406,14 +351,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
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;
+ } 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
@@ -428,9 +371,9 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
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._timecodeToHide === undefined ? this.props.endTag : 'timecodeToHide', timelinePt + anchorEnd - this.anchorStart(drop), false);
}
- Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : "timecodeToShow", timelinePt, false);
+ Doc.SetInPlace(drop, drop._timecodeToShow === undefined ? this.props.startTag : 'timecodeToShow', timelinePt, false);
});
return true;
@@ -439,38 +382,28 @@ 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(
- `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
- });
+ 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)) {
@@ -481,7 +414,6 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
return anchor;
}
-
@action
playOnClick = (anchorDoc: Doc, clientX: number) => {
const seekTimeInSeconds = this.anchorStart(anchorDoc) - 0.25;
@@ -493,10 +425,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._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) {
if (!this.layoutDoc.autoPlayAnchors && this.props.playing()) {
this.props.Pause();
} else {
@@ -508,59 +437,43 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
}
}
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 }[]
- ) => {
+ 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.clipDuration
- );
+ 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;
}
@@ -571,14 +484,17 @@ 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;
- @computed get timelineContentHeight() { return this.props.PanelHeight() * this.dictationHeightPercent / 100; }
- @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor; } // subtract size of container border
+ @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);
@@ -586,24 +502,18 @@ 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%",
+ 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}
@@ -618,8 +528,7 @@ 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>
);
}
@@ -644,145 +553,131 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack
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;
- return (<div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? "all" : undefined }}>
- <div className="timeline-container"
- style={{ width: this.props.PanelWidth() }}
- onWheel={e => e.stopPropagation()}
- onScroll={this.setScroll}
- onMouseMove={(e) => this.isContentActive() && this.onHover(e)}
- ref={wrapper => this._timelineWrapper = wrapper}>
+ return (
+ <div ref={this.createDashEventsTarget} style={{ pointerEvents: SnappingManager.GetIsDragging() ? 'all' : undefined }}>
<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} */}
-
+ className="timeline-container"
+ 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-hover"
- style={{
- left: `${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%`,
- }}
- />
+ 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-hover"
+ style={{
+ left: `${((this._hoverTime - this.clipStart) / this.clipDuration) * 100}%`,
+ }}
+ />
+
+ <div
+ className="collectionStackedTimeline-current"
+ style={{
+ left: `${((this.currentTime - this.clipStart) / this.clipDuration) * 100}%`,
+ }}
+ />
+
+ {this.IsTrimming !== TrimScope.None && (
+ <>
+ <div className="collectionStackedTimeline-trim-shade" style={{ width: `${((this.trimStart - this.clipStart) / this.clipDuration) * 100}%` }}></div>
- <div
- className="collectionStackedTimeline-current"
- style={{
- left: `${((this.currentTime - this.clipStart) / this.clipDuration) * 100}%`,
- }}
- />
-
- {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>
+ 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-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>
- </>
- )}
+ 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 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 >);
+ );
}
}
-
/**
* StackedTimelineAnchor
* creates the anchors to display markers, links, and embedded documents on timeline
@@ -814,7 +709,6 @@ interface StackedTimelineAnchorProps {
trimEnd: number;
}
-
@observer
class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps> {
_lastTimecode: number;
@@ -831,23 +725,14 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
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.
@@ -859,13 +744,7 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
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;
}
@@ -876,7 +755,6 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
this._disposer?.();
}
-
// starting the drag event for anchor resizing
onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => {
this.props._timeline?.setPointerCapture(e.pointerId);
@@ -885,19 +763,13 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
return this.props.toTimeline(e.clientX - rect.x, rect.width);
};
const changeAnchor = (anchor: Doc, left: boolean, time: number | undefined) => {
- const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== 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, "borderRounding", time !== undefined ? undefined : "100%", true);
- }
- else {
- anchor[left ? "_timecodeToShow" : "_timecodeToHide"] = time;
+ 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;
};
@@ -906,46 +778,34 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
setupMoveUpEvents(
this,
e,
- (e) => {
- if (!undo) undo = UndoManager.StartBatch("drag anchor");
+ e => {
+ if (!undo) undo = UndoManager.StartBatch('drag anchor');
this.props.setTime(newTime(e));
return changeAnchor(anchor, left, newTime(e));
},
- (e) => {
+ e => {
this.props.setTime(newTime(e));
this.props._timeline?.releasePointerCapture(e.pointerId);
undo?.end();
},
emptyFunction
);
- }
-
+ };
// 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" };
+ 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];
- }
-
+ };
// renders anchor LabelBox
- renderInner = computedFn(function (
- this: StackedTimelineAnchor,
- mark: Doc,
- script: undefined | (() => ScriptField),
- doublescript: undefined | (() => ScriptField),
- screenXf: () => Transform,
- width: () => number,
- height: () => number
- ) {
+ 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 });
};
@@ -954,13 +814,13 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
view: (
<DocumentView
key="view"
- {...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit}
- ref={action((r: DocumentView | null) => anchor.view = r)}
+ {...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("data", this.computeTitle())}
+ LayoutTemplateString={LabelBox.LayoutStringWithTitle('data', this.computeTitle())}
isDocumentActive={this.props.isDocumentActive}
PanelWidth={width}
PanelHeight={height}
@@ -985,32 +845,14 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
height = () => this.props.height;
render() {
- const inner = this.renderInner(
- this.props.mark,
- this.props.rangeClickScript,
- this.props.rangePlayScript,
- this.anchorScreenToLocalXf,
- this.width,
- this.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)} />
</>
)}
</>
@@ -1025,4 +867,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
+});