From 967b8d98207e83c27799494601c0db8ba1fa077e Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 26 Sep 2021 10:15:04 -0400 Subject: made embedded documents appear with their title on timeline. added reset title context menu item --- src/client/views/collections/CollectionStackedTimeline.scss | 1 + 1 file changed, 1 insertion(+) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 59c21210a..091337aac 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -60,6 +60,7 @@ top: 2.5%; height: 95%; border-radius: 4px; + background: $light-gray; &:hover { opacity: 1; } -- cgit v1.2.3-70-g09d2 From e7d41738198931dd54dc4ce0b74ee2654ba10bbd Mon Sep 17 00:00:00 2001 From: mehekj Date: Thu, 7 Oct 2021 16:45:08 -0400 Subject: minor bug fixes and added escape key to abort trim --- .../collections/CollectionStackedTimeline.scss | 1 - .../collections/CollectionStackedTimeline.tsx | 44 ++++++++++++++-------- src/client/views/nodes/VideoBox.tsx | 6 +-- 3 files changed, 32 insertions(+), 19 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 091337aac..7a957ae5c 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -5,7 +5,6 @@ width: 100%; height: 100%; z-index: 1000; - overflow: hidden; top: 0px; .collectionStackedTimeline-trim-shade { diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 0a0581f3c..3ec7f27d3 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -87,6 +87,8 @@ export class CollectionStackedTimeline extends CollectionSubView< @observable _trimStart: number = 0; @observable _trimEnd: number = 0; + @observable _zoomFactor: number = 1; + get minTrimLength() { return this._timeline?.getBoundingClientRect() ? 0.05 * this.clipDuration : 0; } @computed get trimStart() { return this.IsTrimming !== TrimScope.None ? this._trimStart : this.clipStart; } @computed get trimDuration() { return this.trimEnd - this.trimStart; } @@ -133,6 +135,7 @@ export class CollectionStackedTimeline extends CollectionSubView< @action public StartTrimming(scope: TrimScope) { + console.log(this.minTrimLength); this._trimStart = this.clipStart; this._trimEnd = this.clipEnd; this._trimming = scope; @@ -144,6 +147,11 @@ export class CollectionStackedTimeline extends CollectionSubView< this._trimming = TrimScope.None; } + @action + public setZoom(change: number) { + this._zoomFactor = Math.max(1, this._zoomFactor + change); + } + 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); toTimeline = (screen_delta: number, width: number) => { @@ -181,6 +189,11 @@ export class CollectionStackedTimeline extends CollectionSubView< this._markerEnd = undefined; CollectionStackedTimeline.SelectingRegion = undefined; } + break; + case "Escape": + this._trimStart = this.clipStart; + this._trimStart = this.clipEnd; + this._trimming = TrimScope.None; } } } @@ -285,7 +298,7 @@ export class CollectionStackedTimeline extends CollectionSubView< this._trimStart = Math.min( Math.max( this.trimStart + (e.movementX / rect.width) * this.clipDuration, - 0 + this.clipStart ), this.trimEnd - this.minTrimLength ); @@ -311,7 +324,7 @@ export class CollectionStackedTimeline extends CollectionSubView< this._trimEnd = Math.max( Math.min( this.trimEnd + (e.movementX / rect.width) * this.clipDuration, - this.clipStart + this.clipDuration + this.clipEnd ), this.trimStart + this.minTrimLength ); @@ -560,6 +573,7 @@ export class CollectionStackedTimeline extends CollectionSubView< ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)} onClick={(e) => this.isContentActive() && StopEvent(e)} onPointerDown={(e) => this.isContentActive() && this.onPointerDownTimeline(e)} + style={{ width: `${this._zoomFactor * 100}%` }} > {drawAnchors.map((d) => { const start = this.anchorStart(d.anchor); @@ -854,19 +868,19 @@ class StackedTimelineAnchor extends React.Component {inner.view} {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? null : ( - <> -
this.onAnchorDown(e, this.props.mark, true)} - /> -
this.onAnchorDown(e, this.props.mark, false)} - /> - - )} + <> +
this.onAnchorDown(e, this.props.mark, true)} + /> +
this.onAnchorDown(e, this.props.mark, false)} + /> + + )} ); } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index b1f049700..23c90de8a 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -419,12 +419,12 @@ export class VideoBox extends ViewBoxAnnotatableComponent { this.Pause(); this.setPlayheadTime(Math.max(Math.min(this.timeline?.trimEnd || 0, this.player!.currentTime), this.timeline?.trimStart || 0)); - this._stackedTimeline.current?.StopTrimming(); + this.timeline?.StopTrimming(); }); startTrim = (scope: TrimScope) => { this.Pause(); - this._stackedTimeline.current?.StartTrimming(scope); + this.timeline?.StartTrimming(scope); } onClipPointerDown = (e: React.PointerEvent) => { @@ -441,7 +441,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { const startTime = Math.max(0, (this._stackedTimeline.current?.anchorStart(doc) || 0)); - const endTime = this._stackedTimeline.current?.anchorEnd(doc); + const endTime = this.timeline?.anchorEnd(doc); if (startTime !== undefined) { if (!this.layoutDoc.dontAutoPlayFollowedLinks) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime); else this.Seek(startTime); -- cgit v1.2.3-70-g09d2 From 6cec290f98103827727905874c5a9c5ced0bcca8 Mon Sep 17 00:00:00 2001 From: mehekj Date: Thu, 14 Oct 2021 16:21:03 -0400 Subject: pre-redesign changes (zoom but no scroll) --- src/client/views/AudioWaveform.tsx | 26 +-- .../collections/CollectionStackedTimeline.scss | 167 ++++++++++--------- .../collections/CollectionStackedTimeline.tsx | 46 +++--- src/client/views/nodes/AudioBox.scss | 178 +++++++++++++-------- src/client/views/nodes/AudioBox.tsx | 14 ++ 5 files changed, 256 insertions(+), 175 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index 270b3869c..7b9b1aa81 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -18,6 +18,7 @@ export interface AudioWaveformProps { layoutDoc: Doc; clipStart: number; clipEnd: number; + zoomFactor: number; PanelHeight: () => number; } @@ -28,28 +29,30 @@ export class AudioWaveform extends React.Component { @computed get waveHeight() { return Math.max(50, this.props.PanelHeight()); } @computed get clipStart() { return this.props.clipStart; } @computed get clipEnd() { return this.props.clipEnd; } - @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd)], listSpec("number"), []); } + @computed get zoomFactor() { return this.props.zoomFactor; } + @computed get audioBuckets() { return Cast(this.props.layoutDoc[this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor)], listSpec("number"), []); } - audioBucketField = (start: number, end: number) => "audioBuckets/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_"); + audioBucketField = (start: number, end: number, zoomFactor: number) => "audioBuckets/" + "/" + start.toFixed(2).replace(".", "_") + "/" + end.toFixed(2).replace(".", "_") + "/" + (zoomFactor * 10); componentWillUnmount() { this._disposer?.(); } componentDidMount() { - this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd) }), - ({ clipStart, clipEnd, fieldKey }) => { + console.log("new waveform"); + this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }), + ({ clipStart, clipEnd, fieldKey, zoomFactor }) => { if (!this.props.layoutDoc[fieldKey]) { // setting these values here serves as a "lock" to prevent multiple attempts to create the waveform at nerly the same time. - const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration)], listSpec("number")); + const waveform = Cast(this.props.layoutDoc[this.audioBucketField(0, this.props.rawDuration, 1)], listSpec("number")); this.props.layoutDoc[fieldKey] = waveform && new List(waveform.slice(clipStart / this.props.rawDuration * waveform.length, clipEnd / this.props.rawDuration * waveform.length)); - setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd)); + setTimeout(() => this.createWaveformBuckets(fieldKey, clipStart, clipEnd, zoomFactor)); } }, { fireImmediately: true }); } // decodes the audio file into peaks for generating the waveform - createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number) => { + createWaveformBuckets = async (fieldKey: string, clipStart: number, clipEnd: number, zoomFactor: number) => { axios({ url: this.props.mediaPath, responseType: "arraybuffer" }).then( (response) => { const context = new window.AudioContext(); @@ -60,12 +63,15 @@ export class AudioWaveform extends React.Component { const startInd = clipStart / this.props.rawDuration; const endInd = clipEnd / this.props.rawDuration; const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length)); + const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor); + + console.log(numBuckets); const bucketDataSize = Math.floor( - decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS + decodedAudioData.length / numBuckets ); const brange = Array.from(Array(bucketDataSize)); - const bucketList = numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map( + const bucketList = numberRange(numBuckets).map( (i: number) => brange.reduce( (p, x, j) => @@ -87,7 +93,7 @@ export class AudioWaveform extends React.Component { NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); @@ -216,7 +220,9 @@ export class CollectionStackedTimeline extends CollectionSubView< @action onPointerDownTimeline = (e: React.PointerEvent): void => { const rect = this._timeline?.getBoundingClientRect(); + const scrollLeft = this._timeline?.scrollLeft; 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(); @@ -529,7 +535,7 @@ export class CollectionStackedTimeline extends CollectionSubView< } @computed get renderAudioWaveform() { return !this.props.mediaPath ? null : ( -
+
); @@ -556,7 +563,7 @@ export class CollectionStackedTimeline extends CollectionSubView< } render() { - const timelineContentWidth = this.props.PanelWidth(); + const timelineContentWidth = this.props.PanelWidth() * this.zoomFactor; const overlaps: { anchorStartTime: number; anchorEndTime: number; @@ -573,8 +580,9 @@ export class CollectionStackedTimeline extends CollectionSubView< ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)} onClick={(e) => this.isContentActive() && StopEvent(e)} onPointerDown={(e) => this.isContentActive() && this.onPointerDownTimeline(e)} - style={{ width: `${this._zoomFactor * 100}%` }} + onPointerEnter={(e) => { console.log("scroll"); e.preventDefault(); e.stopPropagation(); }} > + {drawAnchors.map((d) => { const start = this.anchorStart(d.anchor); const end = this.anchorEnd( @@ -868,19 +876,19 @@ class StackedTimelineAnchor extends React.Component {inner.view} {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? null : ( - <> -
this.onAnchorDown(e, this.props.mark, true)} - /> -
this.onAnchorDown(e, this.props.mark, false)} - /> - - )} + <> +
this.onAnchorDown(e, this.props.mark, true)} + /> +
this.onAnchorDown(e, this.props.mark, false)} + /> + + )} ); } diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index b33c7f506..a2fdd38e5 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -1,51 +1,50 @@ @import "../global/globalCssVariables.scss"; - .audiobox-container, .audiobox-container-interactive { - width: 100%; - height: 100%; - position: inherit; - display: flex; - position: relative; - cursor: default; - - .audiobox-buttons { - display: flex; width: 100%; - align-items: center; + height: 100%; + position: inherit; + display: flex; + position: relative; + cursor: default; - .audiobox-dictation { - position: relative; - width: 30px; - height: 100%; - align-items: center; - display: inherit; - background: $medium-gray; - left: 0px; - color: $dark-gray; - &:hover { - color: $black; - cursor: pointer; - } + .audiobox-buttons { + display: flex; + width: 100%; + align-items: center; + + .audiobox-dictation { + position: relative; + width: 30px; + height: 100%; + align-items: center; + display: inherit; + background: $medium-gray; + left: 0px; + color: $dark-gray; + &:hover { + color: $black; + cursor: pointer; + } + } } - } - .audiobox-control, - .audiobox-control-interactive { - top: 0; - max-height: 32px; - width: 100%; - display: inline-block; - pointer-events: none; - } + .audiobox-control, + .audiobox-control-interactive { + top: 0; + max-height: 32px; + width: 100%; + display: inline-block; + pointer-events: none; + } - .audiobox-control-interactive { - pointer-events: all; - } + .audiobox-control-interactive { + pointer-events: all; + } - .audiobox-record-interactive, - .audiobox-record { + .audiobox-record-interactive, + .audiobox-record { pointer-events: all; cursor: pointer; width: 100%; @@ -59,45 +58,46 @@ color: white; font-weight: bold; background-color: $dark-gray; - } + } - .audiobox-record { + .audiobox-record { pointer-events: none; - } + } - .recording { - margin-top: auto; - margin-bottom: auto; - width: 100%; - height: 100%; - position: relative; - padding-right: 5px; - display: flex; - background-color: $medium-blue; + .recording { + margin-top: auto; + margin-bottom: auto; + width: 100%; + height: 100%; + position: relative; + padding-right: 5px; + display: flex; + background-color: $medium-blue; - .time { - position: relative; - width: 100%; - font-size: $large-header; - text-align: center; - } + .time { + position: relative; + width: 100%; + font-size: $large-header; + text-align: center; + } - .recording-buttons { - position: relative; - margin-top: auto; - margin-bottom: auto; - color: $dark-gray; - &:hover { - color: $black; - } - } + .recording-buttons { + position: relative; + margin-top: auto; + margin-bottom: auto; + color: $dark-gray; + &:hover { + color: $black; + } + } - .time, .recording-buttons { - display: flex; - align-items: center; - padding: 5px; + .time, + .recording-buttons { + display: flex; + align-items: center; + padding: 5px; + } } - } .audiobox-buttons { display: flex; width: 100%; @@ -267,6 +267,44 @@ right: 2px; } + .toolbar-slider { + position: absolute; + top: 75px; + left: 70px; + } + + input[type="range"] { + width: calc(100% - 100px); + height: 16px; + -webkit-appearance: none; + background: none; + } + + input[type="range"]:focus { + outline: none; + } + + input[type="range"]::-webkit-slider-runnable-track { + width: 100%; + height: 5px; + cursor: pointer; + box-shadow: 0; + background: #dfdfdf; + border-radius: 3px; + } + + input[type="range"]::-webkit-slider-thumb { + box-shadow: 0; + border: 0; + height: 7px; + width: 7px; + border-radius: 10px; + background: #4476f7; + cursor: pointer; + -webkit-appearance: none; + margin: -1px; + } + .audiobox-zoom { bottom: 0; left: 30px; diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 2eb34d27a..f2001adcd 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -45,6 +45,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { + this.timeline?.setZoom(zoom); + } + setupTimelineDrop = (r: HTMLDivElement | null) => { if (r && this.timeline) { this._dropDisposer?.(); @@ -437,6 +442,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent
+
+
{this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))}
+ + {/* { e.stopPropagation(); }} + onChange={(e: React.ChangeEvent) => { this.zoom(e.target.value); }} + /> */} +
{this.timeline && formatTime(Math.round(NumCast(this.timeline?.clipDuration)))}
-- cgit v1.2.3-70-g09d2 From 3d76222b8cd41423bcdd7a91e4aee2826f329d9f Mon Sep 17 00:00:00 2001 From: mehekj Date: Sat, 23 Oct 2021 21:36:30 -0400 Subject: audiobox UI update --- src/client/views/AudioWaveform.tsx | 3 - .../collections/CollectionStackedTimeline.scss | 3 +- .../collections/CollectionStackedTimeline.tsx | 10 +- src/client/views/nodes/AudioBox.scss | 349 ++++++--------------- src/client/views/nodes/AudioBox.tsx | 120 ++++--- src/client/views/nodes/ComparisonBox.tsx | 4 +- 6 files changed, 163 insertions(+), 326 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index 7b9b1aa81..ca1dd6f36 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -38,7 +38,6 @@ export class AudioWaveform extends React.Component { this._disposer?.(); } componentDidMount() { - console.log("new waveform"); this._disposer = reaction(() => ({ clipStart: this.clipStart, clipEnd: this.clipEnd, fieldKey: this.audioBucketField(this.clipStart, this.clipEnd, this.zoomFactor), zoomFactor: this.props.zoomFactor }), ({ clipStart, clipEnd, fieldKey, zoomFactor }) => { if (!this.props.layoutDoc[fieldKey]) { @@ -65,8 +64,6 @@ export class AudioWaveform extends React.Component { const decodedAudioData = rawDecodedAudioData.slice(Math.floor(startInd * rawDecodedAudioData.length), Math.floor(endInd * rawDecodedAudioData.length)); const numBuckets = Math.floor(AudioWaveform.NUMBER_OF_BUCKETS * zoomFactor); - console.log(numBuckets); - const bucketDataSize = Math.floor( decodedAudioData.length / numBuckets ); diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 0ec5f9aef..34679e9e3 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -4,9 +4,8 @@ position: absolute; width: 100%; height: 100%; + background: $off-white; z-index: 1000; - top: 0px; - // overflow-x: scroll; ::-webkit-scrollbar { position: relative; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 5c02611bb..c79d21418 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -153,7 +153,6 @@ export class CollectionStackedTimeline extends CollectionSubView< @action public setZoom(zoom: number) { this._zoomFactor = zoom; - // console.log(this._timeline?.scrollWidth); } anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); @@ -534,8 +533,9 @@ export class CollectionStackedTimeline extends CollectionSubView< ); } @computed get renderAudioWaveform() { + console.log(this.props.mediaPath) return !this.props.mediaPath ? null : ( -
+
(this._timeline = timeline)} onClick={(e) => this.isContentActive() && StopEvent(e)} onPointerDown={(e) => this.isContentActive() && this.onPointerDownTimeline(e)} - onPointerEnter={(e) => { console.log("scroll"); e.preventDefault(); e.stopPropagation(); }} + style={{ height: this.props.PanelHeight(), width: this.props.PanelWidth() }} > {drawAnchors.map((d) => { @@ -591,10 +591,10 @@ export class CollectionStackedTimeline extends CollectionSubView< ); if (end < this.clipStart || start > this.clipEnd) return (null); const left = Math.max((start - this.clipStart) / this.clipDuration * timelineContentWidth, 0); - const top = (d.level / maxLevel) * this.timelineContentHeight(); + const top = (d.level / maxLevel) * this.props.PanelHeight(); const timespan = Math.max(0, end - this.clipStart) - Math.max(0, start - this.clipStart); const width = (timespan / this.clipDuration) * timelineContentWidth; - const height = this.timelineContentHeight() / maxLevel; + const height = this.props.PanelHeight() / maxLevel; return this.props.Document.hideAnchors ? null : (
this.props.whenChildContentsActiveChanged(this._isAnyChildContentActive = isActive) timelineScreenToLocal = () => - this.props - .ScreenToLocalTransform() - .translate( - -AudioBox.playheadWidth, - (-(100 - AudioBox.heightPercent) / 200) * this.props.PanelHeight() - ) + this.props.ScreenToLocalTransform().translate(0, -AudioBox.bottomControlsHeight) setPlayheadTime = (time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time; playing = () => this.mediaState === media_state.Playing; isActiveChild = () => this._isAnyChildContentActive; - timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth; - timelineHeight = () => (this.props.PanelHeight() * (AudioBox.heightPercent / 100)) *// panelHeight * heightPercent is player height - (AudioBox.heightPercent / 100) // * heightPercent is timeline height (as per css inline) + timelineWidth = () => this.props.PanelWidth(); + timelineHeight = () => (this.props.PanelHeight() - (AudioBox.topControlsHeight + AudioBox.bottomControlsHeight)) @undoBatch finishTrim = () => { // hides trim controls and displays new clip @@ -365,6 +359,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { + e.stopPropagation(); this.timeline && setupMoveUpEvents(this, e, returnFalse, returnFalse, action((e: PointerEvent, doubleTap?: boolean) => { if (doubleTap) { this.startTrim(TrimScope.All); @@ -392,90 +387,81 @@ export class AudioBox extends ViewBoxAnnotatableComponent + return
+ size="2x" + icon="file-alt" />
{[media_state.Recording, media_state.Playing].includes(this.mediaState) ? -
e.stopPropagation()}> -
+
e.stopPropagation()}> +
+ size="2x" + icon="stop" />
-
+
+ size="2x" + icon={this._paused ? "play" : "pause"} />
-
+
{formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}
: -
+
RECORD
} -
; +
} @computed get playbackControls() { - return
-
-
-
- {" "} - -
+ return
+
+
+
+ +
-
- +
+ +
+
+ + { e.stopPropagation(); }} + onChange={(e: React.ChangeEvent) => { this.zoom(Number(e.target.value)); }} + /> +
+
-
+
+
{this.renderTimeline}
- {this.audio} -
- {this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))} -
+
- {/* { e.stopPropagation(); }} - onChange={(e: React.ChangeEvent) => { this.zoom(e.target.value); }} - /> */} + {this.audio} -
+
+
+ {this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))} +
+
{this.timeline && formatTime(Math.round(NumCast(this.timeline?.clipDuration)))}
-
; + + +
} @computed get renderTimeline() { diff --git a/src/client/views/nodes/ComparisonBox.tsx b/src/client/views/nodes/ComparisonBox.tsx index d80fb44cf..d47e8340c 100644 --- a/src/client/views/nodes/ComparisonBox.tsx +++ b/src/client/views/nodes/ComparisonBox.tsx @@ -89,8 +89,8 @@ export class ComparisonBox extends ViewBoxAnnotatableComponent { var whichDoc = Cast(this.dataDoc[which], Doc, null); - if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null); - const targetDoc = Cast(whichDoc.annotationOn, Doc, null) ?? whichDoc; + // if (whichDoc?.type === DocumentType.MARKER) whichDoc = Cast(whichDoc.annotationOn, Doc, null); + const targetDoc = Cast(whichDoc?.annotationOn, Doc, null) ?? whichDoc; return whichDoc ? <> { -- cgit v1.2.3-70-g09d2 From 35157eb87d6d3e23f2392d70c62df9519b682745 Mon Sep 17 00:00:00 2001 From: mehekj Date: Sat, 23 Oct 2021 22:12:15 -0400 Subject: implemented basic audio timeline zoom with scroll --- .../collections/CollectionStackedTimeline.scss | 9 +- .../collections/CollectionStackedTimeline.tsx | 194 +++++++++++---------- src/client/views/nodes/AudioBox.scss | 5 + 3 files changed, 110 insertions(+), 98 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 34679e9e3..843c5dcb5 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -1,11 +1,16 @@ @import "../global/globalCssVariables.scss"; +.timeline-container { + height: calc(100% - 50px); + overflow-x: scroll; + border: none; +} + .collectionStackedTimeline { position: absolute; - width: 100%; - height: 100%; background: $off-white; z-index: 1000; + height: 100%; ::-webkit-scrollbar { position: relative; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index c79d21418..82a6b2a66 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -575,108 +575,110 @@ export class CollectionStackedTimeline extends CollectionSubView< })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; return (
-
(this._timeline = timeline)} - onClick={(e) => this.isContentActive() && StopEvent(e)} - onPointerDown={(e) => this.isContentActive() && this.onPointerDownTimeline(e)} - style={{ height: this.props.PanelHeight(), width: this.props.PanelWidth() }} - > - - {drawAnchors.map((d) => { - const start = this.anchorStart(d.anchor); - const end = this.anchorEnd( - d.anchor, - start + (10 / timelineContentWidth) * this.clipDuration - ); - if (end < this.clipStart || start > this.clipEnd) return (null); - const left = Math.max((start - this.clipStart) / this.clipDuration * timelineContentWidth, 0); - const top = (d.level / maxLevel) * this.props.PanelHeight(); - const timespan = Math.max(0, end - this.clipStart) - Math.max(0, start - this.clipStart); - const width = (timespan / this.clipDuration) * timelineContentWidth; - const height = this.props.PanelHeight() / maxLevel; - return this.props.Document.hideAnchors ? null : ( -
{ - this.props.playFrom(start, this.anchorEnd(d.anchor)); - e.stopPropagation(); - }} - > - -
- ); - })} - {!this.IsTrimming && this.selectionContainer} - {this.renderAudioWaveform} - {this.renderDictation} - +
- - {this.IsTrimming !== TrimScope.None && ( - <> -
- -
+ className="collectionStackedTimeline" + ref={(timeline: HTMLDivElement | null) => (this._timeline = timeline)} + onClick={(e) => this.isContentActive() && StopEvent(e)} + onPointerDown={(e) => this.isContentActive() && this.onPointerDownTimeline(e)} + style={{ width: timelineContentWidth }}> + + {drawAnchors.map((d) => { + const start = this.anchorStart(d.anchor); + const end = this.anchorEnd( + d.anchor, + start + (10 / timelineContentWidth) * this.clipDuration + ); + if (end < this.clipStart || start > this.clipEnd) return (null); + const left = Math.max((start - this.clipStart) / this.clipDuration * timelineContentWidth, 0); + const top = (d.level / maxLevel) * this.props.PanelHeight(); + const timespan = Math.max(0, end - this.clipStart) - Math.max(0, start - this.clipStart); + const width = (timespan / this.clipDuration) * timelineContentWidth; + const height = this.props.PanelHeight() / maxLevel; + return this.props.Document.hideAnchors ? null : (
+ 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(); + }} + > + +
+ ); + })} + {!this.IsTrimming && this.selectionContainer} + {this.renderAudioWaveform} + {this.renderDictation} + +
+ + {this.IsTrimming !== TrimScope.None && ( + <>
-
-
- - )} +
+
+
+
+ +
+ + )} +
-
); +
); } } diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 391507796..d466c6c3b 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -177,6 +177,11 @@ width: 100%; background: $white; } + + .audiobox-timeline > div { + width: 100%; + height: 100%; + } } .audiobox-timecodes { -- cgit v1.2.3-70-g09d2 From 7f08f0fec32a28e1dc19f00021f99352f55a045c Mon Sep 17 00:00:00 2001 From: mehekj Date: Thu, 28 Oct 2021 15:12:34 -0400 Subject: fixed marker document decorations on scroll in zoomed timeline --- src/client/views/AudioWaveform.tsx | 2 +- src/client/views/MainView.tsx | 2 +- .../collections/CollectionStackedTimeline.scss | 32 +++--- .../collections/CollectionStackedTimeline.tsx | 16 ++- src/client/views/nodes/AudioBox.scss | 123 ++++++++++++--------- src/client/views/nodes/AudioBox.tsx | 43 ++++++- 6 files changed, 138 insertions(+), 80 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index ca1dd6f36..58384792e 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -90,7 +90,7 @@ export class AudioWaveform extends React.Component { { const rect = this._timeline?.getBoundingClientRect(); - const scrollLeft = this._timeline?.scrollLeft; const clientX = e.clientX; const diff = rect ? clientX - rect?.x : null; const shiftKey = e.shiftKey; @@ -343,6 +344,11 @@ export class CollectionStackedTimeline extends CollectionSubView< ); } + @action + setScroll = (e: React.MouseEvent) => { + this._scroll = e.currentTarget.scrollLeft; + } + @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; @@ -533,7 +539,6 @@ export class CollectionStackedTimeline extends CollectionSubView< ); } @computed get renderAudioWaveform() { - console.log(this.props.mediaPath) return !this.props.mediaPath ? null : (
Math.max(m, o.level), 0) + 2; return (
+ style={{ width: this.props.PanelWidth() }} + onScroll={this.setScroll}>
(this._timeline = timeline)} @@ -616,7 +622,7 @@ export class CollectionStackedTimeline extends CollectionSubView< mark={d.anchor} rangeClickScript={this.rangeClickScript} rangePlayScript={this.rangePlayScript} - left={left} + left={left - this._scroll} top={top} width={width} height={height} diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index d466c6c3b..b3df7e79f 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -92,6 +92,62 @@ height: 100%; color: $white; + .audiobox-button { + margin: 2.5px; + cursor: pointer; + width: 25px; + height: 25px; + border-radius: 50%; + background: $dark-gray; + display: flex; + align-items: center; + justify-content: center; + + svg { + width: 15px; + } + + &:hover { + background: $black; + } + } + + svg { + width: 10px; + } + + input[type="range"] { + width: 70px; + -webkit-appearance: none; + background: none; + margin: 5px; + } + + input[type="range"]:focus { + outline: none; + } + + input[type="range"]::-webkit-slider-runnable-track { + width: 100%; + height: 6px; + cursor: pointer; + box-shadow: 0; + background: $light-gray; + border-radius: 3px; + } + + input[type="range"]::-webkit-slider-thumb { + box-shadow: 0; + border: 0; + height: 10px; + width: 10px; + border-radius: 10px; + background: $medium-blue; + cursor: pointer; + -webkit-appearance: none; + margin: -2px; + } + .audiobox-controls { display: flex; flex-direction: row; @@ -104,66 +160,20 @@ display: flex; flex-direction: row; width: 100px; - - .audiobox-button { - margin: 2.5px; - cursor: pointer; - width: 25px; - height: 25px; - border-radius: 50%; - background: $dark-gray; - display: flex; - align-items: center; - justify-content: center; - - svg { - width: 15px; - } - - &:hover { - background: $black; - } - } } .controls-right { display: flex; flex-direction: row; - svg { - width: 10px; - } - - input[type="range"] { - width: 70px; - -webkit-appearance: none; - background: none; - margin: 5px; - } - - input[type="range"]:focus { - outline: none; - } - - input[type="range"]::-webkit-slider-runnable-track { - width: 100%; - height: 6px; - cursor: pointer; - box-shadow: 0; - background: $light-gray; - border-radius: 3px; - } + .audiobox-button { + width: 15px; + height: 15px; + margin: 0; - input[type="range"]::-webkit-slider-thumb { - box-shadow: 0; - border: 0; - height: 10px; - width: 10px; - border-radius: 10px; - background: $medium-blue; - cursor: pointer; - -webkit-appearance: none; - margin: -2px; + svg { + width: 10px; + } } } } @@ -171,6 +181,7 @@ .audiobox-playback { width: 100%; height: calc(100% - 50px); + background: $white; .audiobox-timeline { height: 100%; @@ -193,5 +204,11 @@ height: 20px; padding: 3px; font-size: $small-text; + + .bottom-controls-middle { + display: flex; + flex-direction: row; + align-items: center; + } } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 48e324971..3a3eb78e1 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -21,6 +21,7 @@ import { ContextMenuProps } from "../ContextMenuItem"; import { ViewBoxAnnotatableComponent, ViewBoxAnnotatableProps } from "../DocComponent"; import "./AudioBox.scss"; import { FieldView, FieldViewProps } from "./FieldView"; +import { timeStamp } from "console"; declare class MediaRecorder { constructor(e: any); // whatever MediaRecorder has @@ -60,6 +61,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - if (fullPlay) this.setPlayheadTime(this.timeline!.trimStart); this.Pause(); + if (fullPlay) this.setPlayheadTime(this.timeline!.trimStart); }, (end - start) * 1000); } else { @@ -260,6 +263,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { this._ele?.pause(); this.mediaState = media_state.Paused; + // clearTimeout(this._play); // stops clip from jumping back to beginning } // creates a text document for dictation @@ -374,6 +378,23 @@ export class AudioBox extends ViewBoxAnnotatableComponent { + if (this._ele) { + this._volume = volume; + this._ele.volume = volume; + } + } + + @action + toggleMute = (e: React.PointerEvent) => { + e.stopPropagation(); + if (this._ele) { + this._muted = !this._muted; + this._ele.muted = this._muted; + } + } + setupTimelineDrop = (r: HTMLDivElement | null) => { if (r && this.timeline) { this._dropDisposer?.(); @@ -434,11 +455,15 @@ export class AudioBox extends ViewBoxAnnotatableComponent
- - + +
+ { e.stopPropagation(); }} - onChange={(e: React.ChangeEvent) => { this.zoom(Number(e.target.value)); }} + onChange={(e: React.ChangeEvent) => { this.setVolume(Number(e.target.value)) }} />
@@ -455,6 +480,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent {this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))}
+
+ + { e.stopPropagation(); }} + onChange={(e: React.ChangeEvent) => { this.zoom(Number(e.target.value)); }} + /> +
{this.timeline && formatTime(Math.round(NumCast(this.timeline?.clipDuration)))}
-- cgit v1.2.3-70-g09d2 From bcae1802bc5277811476ce968a337813a7841fb6 Mon Sep 17 00:00:00 2001 From: mehekj Date: Thu, 4 Nov 2021 16:09:13 -0400 Subject: smooth scroll in timeline while playing audio --- src/Utils.ts | 20 ++++ src/client/views/AudioWaveform.tsx | 118 +++++++++++++++++++-- .../collections/CollectionStackedTimeline.scss | 17 +-- .../collections/CollectionStackedTimeline.tsx | 59 ++++++++--- src/client/views/nodes/AudioBox.tsx | 4 +- 5 files changed, 178 insertions(+), 40 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/Utils.ts b/src/Utils.ts index bfb29fe8d..9faea8d60 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -514,6 +514,26 @@ export function smoothScroll(duration: number, element: HTMLElement | HTMLElemen }; animateScroll(); } + +export function smoothScrollHorizontal(duration: number, element: HTMLElement | HTMLElement[], to: number) { + const elements = (element instanceof HTMLElement ? [element] : element); + const starts = elements.map(element => element.scrollLeft); + const startDate = new Date().getTime(); + + const animateScroll = () => { + const currentDate = new Date().getTime(); + const currentTime = currentDate - startDate; + elements.map((element, i) => element.scrollLeft = easeInOutQuad(currentTime, starts[i], to - starts[i], duration)); + + if (currentTime < duration) { + requestAnimationFrame(animateScroll); + } else { + elements.forEach(element => element.scrollLeft = to); + } + }; + animateScroll(); +} + export function addStyleSheet(styleType: string = "text/css") { const style = document.createElement("style"); style.type = styleType; diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx index 4d9c039c4..8a3c3c319 100644 --- a/src/client/views/AudioWaveform.tsx +++ b/src/client/views/AudioWaveform.tsx @@ -10,6 +10,7 @@ import { Cast, NumCast } from "../../fields/Types"; import { numberRange } from "../../Utils"; import "./AudioWaveform.scss"; import { Colors } from "./global/globalEnums"; +import Color = require("color"); export interface AudioWaveformProps { duration: number; // length of media clip @@ -19,15 +20,15 @@ export interface AudioWaveformProps { clipStart: number; clipEnd: number; zoomFactor: number; - PanelHeight: () => number; - PanelWidth: () => number; + PanelHeight: number; + PanelWidth: number; } @observer export class AudioWaveform extends React.Component { public static NUMBER_OF_BUCKETS = 100; _disposer: IReactionDisposer | undefined; - @computed get waveHeight() { return Math.max(50, this.props.PanelHeight()); } + @computed get waveHeight() { return Math.max(50, this.props.PanelHeight); } @computed get clipStart() { return this.props.clipStart; } @computed get clipEnd() { return this.props.clipEnd; } @computed get zoomFactor() { return this.props.zoomFactor; } @@ -88,10 +89,17 @@ export class AudioWaveform extends React.Component { render() { return (
+ {/* this.props.PanelWidth} + height={this.props.PanelHeight} + peaks={this.audioBuckets} + color={Colors.MEDIUM_BLUE} + /> */} { } -// export interface WaveformProps { -// barWidth: number; -// width: () => number; -// height: () => number; -// peaks: number[]; -// color: string; -// } +export interface WaveformProps { + barWidth: number; + width: () => number; + height: () => number; + peaks: number[]; + color: string; +} +// @observer // export class Waveform extends React.Component { +// private _canvas: HTMLCanvasElement | null = null; + +// get width() { return this.props.width(); } +// get height() { return this.props.height(); } +// get peaks() { return this.props.peaks; } + +// componentDidMount() { +// this.drawBars(); +// } + +// drawBars() { +// const waveCanvasCtx = this._canvas?.getContext("2d"); + +// if (waveCanvasCtx) { +// const pixelRatio = window.devicePixelRatio; +// console.log(pixelRatio); + +// const displayWidth = Math.round(this.width); +// const displayHeight = Math.round(this.height); +// waveCanvasCtx.canvas.width = this.width; +// waveCanvasCtx.canvas.height = this.height; +// waveCanvasCtx.canvas.style.width = `${displayWidth}px`; +// waveCanvasCtx.canvas.style.height = `${displayHeight}px`; + +// waveCanvasCtx.clearRect(0, 0, this.width, this.height); + +// const hasMinVals = [].some.call(this.peaks, (val) => val < 0); +// let filteredPeaks = this.peaks; +// if (hasMinVals) { +// // If the first value is negative, add 1 to the filtered indices +// let indexOffset = 0; +// if (this.peaks[0] < 0) { +// indexOffset = 1; +// } +// filteredPeaks = [].filter.call( +// this.peaks, +// (_, index) => (index + indexOffset) % 2 == 0 +// ); +// } + +// const $ = 0.5; +// const height = this.height; +// const offsetY = 0; +// const halfH = this.height / 2; +// const length = filteredPeaks.length; +// const bar = this.props.barWidth; +// const gap = 2; +// const step = bar + gap; + +// let absmax = 1; +// absmax = this.absMax(filteredPeaks); + +// const scale = length / this.width; + +// waveCanvasCtx.fillStyle = this.props.color; + +// for (let i = 0; i < this.width; i += step) { +// let h = Math.round(filteredPeaks[Math.floor(i * scale)] / absmax * halfH) +// if (h === 0) { +// h = 1 +// } +// waveCanvasCtx.fillRect(i + $, halfH - h + offsetY, bar + $, h * 2) +// } +// } +// } + +// absMax = (values: number[]) => { +// let max = -Infinity; +// for (const i in values) { +// const num = Math.abs(values[i]); +// if (num > max) { +// max = num; +// } +// } + +// return max; +// } + +// render() { +// return this.props.peaks ? ( +// { +// this._canvas = instance; +// }} +// /> +// ) : null +// } // } \ No newline at end of file diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 19913350b..fce105a44 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -3,25 +3,13 @@ .timeline-container { height: calc(100% - 50px); overflow-x: auto; + overflow-y: hidden; border: none; background-color: $white; border: 2px solid $dark-gray; border-width: 0 2px 0 2px; } -::-webkit-scrollbar { - position: relative; - -webkit-appearance: none; - height: 5px; -} - -::-webkit-scrollbar-thumb { - position: relative; - -webkit-appearance: none; - height: 5px; - background-color: $medium-gray; -} - .collectionStackedTimeline { position: absolute; background: $off-white; @@ -33,6 +21,7 @@ height: 100%; background-color: $dark-gray; opacity: 0.3; + top: 0; } .collectionStackedTimeline-trim-controls { @@ -43,6 +32,8 @@ display: flex; justify-content: space-between; max-width: 100%; + top: 0; + left: 0; .collectionStackedTimeline-trim-handle { background-color: $medium-blue; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index b63835b00..ced8a68e8 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -23,6 +23,8 @@ import { setupMoveUpEvents, StopEvent, returnTrue, + smoothScroll, + smoothScrollHorizontal, } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { LinkManager } from "../../util/LinkManager"; @@ -81,6 +83,7 @@ export class CollectionStackedTimeline extends CollectionSubView< static LabelPlayScript: ScriptField; private _timeline: HTMLDivElement | null = null; + private _timelineWrapper: HTMLDivElement | null = null; private _markerStart: number = 0; @observable _markerEnd: number | undefined; @observable _trimming: number = TrimScope.None; @@ -345,8 +348,23 @@ export class CollectionStackedTimeline extends CollectionSubView< } @action - setScroll = (e: React.MouseEvent) => { - this._scroll = e.currentTarget.scrollLeft; + setScroll = (e: React.UIEvent) => { + e.stopPropagation(); + this._scroll = this._timelineWrapper!.scrollLeft; + } + + @action + scrollToTime = (time: number) => { + console.log("rightmost visible time: " + this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)); + if (this._timelineWrapper && time > this.toTimeline(this._scroll + this.props.PanelWidth(), this.timelineContentWidth)) { + console.log("scrolled"); + this._scroll = Math.min(this._scroll + this.props.PanelWidth(), this.timelineContentWidth - this.props.PanelWidth()); + smoothScrollHorizontal(100, this._timelineWrapper, this._scroll); + } + else if (this._timelineWrapper && time < this.toTimeline(this._scroll, this.timelineContentWidth)) { + this._scroll = time / this.timelineContentWidth * this.clipDuration; + smoothScrollHorizontal(100, this._timelineWrapper, this._scroll); + } } @action @@ -357,7 +375,7 @@ export class CollectionStackedTimeline extends CollectionSubView< // 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()); + const timelinePt = this.toTimeline(x + this._scroll, this.timelineContentWidth); docDragData.droppedDocuments.forEach(drop => { const anchorEnd = this.anchorEnd(drop); if (anchorEnd !== undefined) { @@ -466,7 +484,7 @@ export class CollectionStackedTimeline extends CollectionSubView< m: Doc, placed: { anchorStartTime: number; anchorEndTime: number; level: number }[] ) => { - const timelineContentWidth = this.timelineContentWidth(); + const timelineContentWidth = this.timelineContentWidth; const x1 = this.anchorStart(m); const x2 = this.anchorEnd( m, @@ -497,9 +515,9 @@ export class CollectionStackedTimeline extends CollectionSubView< dictationHeightPercent = 50; dictationHeight = () => (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100; - timelineContentHeight = () => (this.props.PanelHeight() * this.dictationHeightPercent) / 100; - timelineContentWidth = () => (this.props.PanelWidth() * this.zoomFactor - 4); // subtract size of container border - dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight()); + @computed get timelineContentHeight() { return this.props.PanelHeight() * this.dictationHeightPercent / 100; } + @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor - 4 }; // subtract size of container border + dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight); isContentActive = () => this.props.isSelected() || this.props.isContentActive(); currentTimecode = () => this.currentTime; @@ -510,7 +528,7 @@ export class CollectionStackedTimeline extends CollectionSubView< style={{ position: "absolute", height: "100%", - top: this.timelineContentHeight(), + top: this.timelineContentHeight, background: Colors.LIGHT_BLUE, }} > @@ -570,7 +588,6 @@ export class CollectionStackedTimeline extends CollectionSubView< } render() { - const timelineContentWidth = this.timelineContentWidth(); const overlaps: { anchorStartTime: number; anchorEndTime: number; @@ -584,25 +601,26 @@ export class CollectionStackedTimeline extends CollectionSubView< return (
+ onScroll={this.setScroll} + ref={(wrapper: HTMLDivElement | null) => (this._timelineWrapper = wrapper)}>
(this._timeline = timeline)} onClick={(e) => this.isContentActive() && StopEvent(e)} onPointerDown={(e) => this.isContentActive() && this.onPointerDownTimeline(e)} - style={{ width: timelineContentWidth }}> + style={{ width: this.timelineContentWidth }}> {drawAnchors.map((d) => { const start = this.anchorStart(d.anchor); const end = this.anchorEnd( d.anchor, - start + (10 / timelineContentWidth) * this.clipDuration + start + (10 / this.timelineContentWidth) * this.clipDuration ); if (end < this.clipStart || start > this.clipEnd) return (null); - const left = Math.max((start - this.clipStart) / this.clipDuration * timelineContentWidth, 0); + 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, end - this.clipStart) - Math.max(0, start - this.clipStart); - const width = (timespan / this.clipDuration) * timelineContentWidth; + const width = (timespan / this.clipDuration) * this.timelineContentWidth; const height = this.props.PanelHeight() / maxLevel; return this.props.Document.hideAnchors ? null : (
{this.renderDictation}
Date: Mon, 8 Nov 2021 11:07:05 -0500 Subject: fixed vertical height of timeline for video. fixed initial display of timeline by not assigning clipEnd in DidMount --- .../collections/CollectionStackedTimeline.scss | 2 +- .../collections/CollectionStackedTimeline.tsx | 33 ++++++++++++---------- src/client/views/collections/TabDocView.tsx | 4 +-- src/client/views/nodes/AudioBox.scss | 5 ++-- src/client/views/nodes/AudioBox.tsx | 7 +++-- src/client/views/nodes/VideoBox.tsx | 9 +++--- 6 files changed, 33 insertions(+), 27 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index fce105a44..36aa36978 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -1,7 +1,7 @@ @import "../global/globalCssVariables.scss"; .timeline-container { - height: calc(100% - 50px); + height: 100%; overflow-x: auto; overflow-y: hidden; border: none; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index ced8a68e8..f5c3676e8 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -127,8 +127,11 @@ export class CollectionStackedTimeline extends CollectionSubView< } componentDidMount() { - this.layoutDoc.clipStart = 0; - this.layoutDoc.clipEnd = this.props.rawDuration; + // bcz: setting these shouldn't be necessary since they are the default values of this.clipStart and this.clipEnd. + // also, setting anything on the Document in DidMount or the constructor is not good form since it means that + // someone who has viewing but not edit permissions would not be able to do the assignment. + // this.layoutDoc.clipStart = 0; + // this.layoutDoc.clipEnd = this.props.rawDuration; document.addEventListener("keydown", this.keyEvents, true); } @@ -915,19 +918,19 @@ class StackedTimelineAnchor extends React.Component {inner.view} {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? null : ( - <> -
this.onAnchorDown(e, this.props.mark, true)} - /> -
this.onAnchorDown(e, this.props.mark, false)} - /> - - )} + <> +
this.onAnchorDown(e, this.props.mark, true)} + /> +
this.onAnchorDown(e, this.props.mark, false)} + /> + + )} ); } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 9f24d60d8..6e1d9b067 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -220,8 +220,8 @@ export class TabDocView extends React.Component { if (!pinProps?.audioRange && duration !== undefined) { pinDoc.mediaStart = "manual"; pinDoc.mediaStop = "manual"; - pinDoc.presStartTime = doc.clipStart; - pinDoc.presEndTime = doc.clipEnd; + pinDoc.presStartTime = NumCast(doc.clipStart); + pinDoc.presEndTime = NumCast(doc.clipEnd, duration); } //save position if (pinProps?.setPosition || pinDoc.isInkMask) { diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 458d607a5..681a6b022 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -180,13 +180,14 @@ .audiobox-playback { width: 100%; - height: calc(100% - 50px); + height: 100%; background: $white; .audiobox-timeline { - height: 100%; + height: calc(100% - 50px); width: 100%; background: $white; + position: absolute; } .audiobox-timeline > div { diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index b51908e20..62958a80b 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -64,8 +64,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent
- {this.timeline && formatTime(Math.round(NumCast(this.timeline?.clipDuration)))} + {this.timeline && formatTime(Math.round(this.timeline.clipDuration))}
@@ -537,7 +538,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent (this._ele?.duration && this._ele?.duration !== Infinity) && - (this.dataDoc[this.fieldKey + "-duration"] = this._ele?.duration) + (this.dataDoc[this.fieldKey + "-duration"] = this.rawDuration = this._ele.duration) )} > diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index c22dbab5c..ec6519abd 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -79,7 +79,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + videoLoad = action(() => { const aspect = this.player!.videoWidth / this.player!.videoHeight; Doc.SetNativeWidth(this.dataDoc, this.player!.videoWidth); Doc.SetNativeHeight(this.dataDoc, this.player!.videoHeight); this.layoutDoc._height = (this.layoutDoc._width || 0) / aspect; if (Number.isFinite(this.player!.duration)) { - this.dataDoc[this.fieldKey + "-duration"] = this.player!.duration; + this.rawDuration = this.player!.duration; } - } + }) @action updateTimecode = () => { -- cgit v1.2.3-70-g09d2 From 44a607d7bf6c30d05698dfa9d4be47c5b8356c21 Mon Sep 17 00:00:00 2001 From: mehekj Date: Mon, 29 Nov 2021 15:02:02 -0500 Subject: fixed playback loop to beginning and added click to go to currently playing --- .../collections/CollectionStackedTimeline.scss | 4 +++ .../collectionLinear/CollectionLinearView.scss | 17 ++++++------ .../collectionLinear/CollectionLinearView.tsx | 17 ++++++++++-- src/client/views/nodes/AudioBox.tsx | 32 +++++++++++++--------- src/client/views/nodes/VideoBox.tsx | 28 ++++++++++++------- 5 files changed, 64 insertions(+), 34 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 36aa36978..e8b6817b4 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -10,6 +10,10 @@ border-width: 0 2px 0 2px; } +::-webkit-scrollbar { + height: 5px; +} + .collectionStackedTimeline { position: absolute; background: $off-white; diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss index 8fe804466..d86e2d81f 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss @@ -13,7 +13,7 @@ background-color: $medium-blue-alt; } - >input:not(:checked)~&.true { + > input:not(:checked) ~ &.true { background-color: transparent; } @@ -27,7 +27,7 @@ overflow: visible !important; } - >span { + > span { background: $dark-gray; color: $white; border-radius: 18px; @@ -54,6 +54,7 @@ padding-right: 20px; vertical-align: middle; font-size: 12.5px; + pointer-events: all; } .bottomPopup-descriptions { @@ -82,7 +83,7 @@ color: black; } - >label { + > label { pointer-events: all; cursor: pointer; background-color: $medium-blue; @@ -100,20 +101,20 @@ justify-content: center; transition: 0.2s; - &:hover{ + &:hover { filter: brightness(0.85); } } - >input { + > input { display: none; } - >input:not(:checked)~.collectionLinearView-content { + > input:not(:checked) ~ .collectionLinearView-content { display: none; } - >input:checked~label { + > input:checked ~ label { transform: rotate(45deg); transition: transform 0.5s; cursor: pointer; @@ -147,4 +148,4 @@ } } } -} \ No newline at end of file +} diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 35b7fb5e9..3a06df746 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -3,12 +3,13 @@ import { Tooltip } from '@material-ui/core'; import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, HeightSym, Opt, WidthSym } from '../../../../fields/Doc'; +import { Doc, HeightSym, Opt, WidthSym, DataSym } from '../../../../fields/Doc'; import { documentSchema } from '../../../../fields/documentSchemas'; import { Id } from '../../../../fields/FieldSymbols'; import { makeInterface } from '../../../../fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnTrue, Utils } from '../../../../Utils'; +import { emptyFunction, returnEmptyDoclist, returnTrue, returnFalse, OmitKeys, Utils } from '../../../../Utils'; +import { DocUtils } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { Colors, Shadows } from '../../global/globalEnums'; @@ -19,6 +20,7 @@ import { LinkDescriptionPopup } from '../../nodes/LinkDescriptionPopup'; import { StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import { CollectionViewType } from '../CollectionView'; +import { DocumentManager } from "../../../util/DocumentManager"; import "./CollectionLinearView.scss"; @@ -122,6 +124,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { let dref: Opt; const docXf = () => this.getTransform(dref); // const scalable = pair.layout.onClick || pair.layout.onDragStart; + doc.title == "audio recording 1" && console.log(doc); return hidden ? (null) :
dref = r || undefined} style={{ pointerEvents: "all", @@ -225,9 +228,17 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { add button to close out audio from currently playing */} {AudioBox.CurrentlyPlaying && AudioBox.CurrentlyPlaying.length != 0 && StrCast(this.layoutDoc.title) === "docked buttons" ? - Currently listening to: {AudioBox.CurrentlyPlaying.map((clip) => clip.dataDoc.title + ", ")} + Currently listening to: {AudioBox.CurrentlyPlaying.map((clip) => + { + DocumentManager.Instance.jumpToDocument(clip, true); + }}>{clip.title}, + )} : null} + + {/* {AudioBox.CurrentlyPlaying && AudioBox.CurrentlyPlaying.length != 0 && StrCast(this.layoutDoc.title) === "docked buttons" ? + this.getDisplayDoc(AudioBox.CurrentlyPlaying[0]) : null} */} +
; } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index acd025fbd..67c8902f9 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -38,7 +38,7 @@ enum media_state { } @observer export class AudioBox extends ViewBoxAnnotatableComponent(AudioDocument) { - @observable public static CurrentlyPlaying: AudioBox[]; + @observable public static CurrentlyPlaying: Doc[]; public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); } public static SetScrubTime = action((timeInMillisFrom1970: number) => { @@ -63,6 +63,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.playFrom(seekTimeInSeconds, endTime), 500); @@ -175,14 +175,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - this.Pause(false); - if (fullPlay) this.setPlayheadTime(this.timeline!.trimStart); + if (fullPlay) this._finished = true; // removes from currently playing if playback has reached end of range marker else this.removeCurrentlyPlaying(); + this.Pause(); }, (end - start) * 1000); } else { - this.Pause(false); + this.Pause(); } } } @@ -190,7 +190,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - AudioBox.CurrentlyPlaying.splice(AudioBox.CurrentlyPlaying.indexOf(this), 1); + AudioBox.CurrentlyPlaying.splice(AudioBox.CurrentlyPlaying.indexOf(this.Document), 1); } // update the recording time @@ -277,20 +277,26 @@ export class AudioBox extends ViewBoxAnnotatableComponent { + e?.stopPropagation?.(); + if (this.timeline && this._ele) { const eleTime = this._ele.currentTime; - const start = eleTime >= this.timeline.trimEnd || eleTime <= this.timeline.trimStart ? this.timeline.trimStart : eleTime; + let start = eleTime >= this.timeline.trimEnd || eleTime <= this.timeline.trimStart ? this.timeline.trimStart : eleTime; + if (this._finished) { + this._finished = false; + start = this.timeline.trimStart; + } this.playFrom(start, this.timeline.trimEnd, true); - e?.stopPropagation?.(); } } // pause play back @action - Pause = (timeoutClear: boolean = true) => { + Pause = () => { this._ele?.pause(); this.mediaState = media_state.Paused; - if (timeoutClear) clearTimeout(this._play); // prevents jump back to beginning when manually paused + if (!this._finished) clearTimeout(this._play); + AudioBox.CurrentlyPlaying.splice(AudioBox.CurrentlyPlaying.indexOf(this.Document), 1); } // creates a text document for dictation @@ -318,7 +324,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { e?.addEventListener("timeupdate", this.timecodeChanged); - e?.addEventListener("ended", () => this.Pause(false)); + e?.addEventListener("ended", () => { this._finished = true; this.Pause() }); this._ele = e; } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index ec6519abd..fbefa02bc 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -76,6 +76,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { this._playing = true; const eleTime = this.player?.currentTime || 0; - const start = eleTime >= (this.timeline?.trimEnd || 0) ? this.timeline?.trimStart || 0 : eleTime; - try { - this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime); - update && this.player && this.playFrom(start, undefined, true); - update && this._audioPlayer?.play(); - update && this._youtubePlayer?.playVideo(); - this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); - } catch (e) { - console.log("Video Play Exception:", e); + if (this.timeline) { + let start = eleTime >= this.timeline.trimEnd || eleTime <= this.timeline.trimStart ? this.timeline.trimStart : eleTime; + if (this._finished) { + this._finished = false; + start = this.timeline.trimStart; + } + try { + this._audioPlayer && this.player && (this._audioPlayer.currentTime = this.player?.currentTime); + update && this.player && this.playFrom(start, undefined, true); + update && this._audioPlayer?.play(); + update && this._youtubePlayer?.playVideo(); + this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); + } catch (e) { + console.log("Video Play Exception:", e); + } } this.updateTimecode(); } @@ -157,6 +164,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { @@ -407,7 +415,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - if (fullPlay) this.setPlayheadTime(this.timeline?.trimStart || 0); + if (fullPlay) this._finished = true; this.Pause(); }, this._playRegionDuration * 1000); } else { -- cgit v1.2.3-70-g09d2 From 9501e39728851d7cd66faa51c619e17b0265f56e Mon Sep 17 00:00:00 2001 From: mehekj Date: Wed, 8 Jun 2022 15:51:28 -0400 Subject: thumbnail preview of video when hovering over timeline --- src/.DS_Store | Bin 8196 -> 10244 bytes .../collections/CollectionStackedTimeline.scss | 30 +++++++++++++- .../collections/CollectionStackedTimeline.tsx | 36 +++++++++++++++-- src/client/views/nodes/AudioBox.tsx | 2 + src/client/views/nodes/VideoBox.tsx | 43 ++++++++++++++++++++- 5 files changed, 104 insertions(+), 7 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/.DS_Store b/src/.DS_Store index 4bf9cdac7..4751acf44 100644 Binary files a/src/.DS_Store and b/src/.DS_Store differ diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index e8b6817b4..580cbccda 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -8,6 +8,16 @@ background-color: $white; border: 2px solid $dark-gray; border-width: 0 2px 0 2px; + + &:hover { + .collectionStackedTimeline-hover { + display: block; + } + } +} + +.timeline-container:hover + .videoBox-thumbnail { + display: block; } ::-webkit-scrollbar { @@ -61,15 +71,23 @@ border-width: 1px; } - .collectionStackedTimeline-current { + .collectionStackedTimeline-current, .collectionStackedTimeline-hover { width: 1px; height: 100%; - background-color: $pink; position: absolute; top: 0px; pointer-events: none; } + .collectionStackedTimeline-current { + background-color: $pink; + } + + .collectionStackedTimeline-hover { + display: none; + background-color: $medium-blue; + } + .collectionStackedTimeline-marker-timeline { position: absolute; top: 2.5%; @@ -108,3 +126,11 @@ pointer-events: none; } } + +.videoBox-thumbnail { + position: absolute; + z-index: 10000; + transform: translate(-50%, 100%); + height: 100%; + display: none; +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index ca0b9d3d3..2b78f5764 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -43,6 +43,8 @@ import { } from "../nodes/DocumentView"; import { LabelBox } from "../nodes/LabelBox"; import "./CollectionStackedTimeline.scss"; +import { VideoBox } from "../nodes/VideoBox"; +import { ImageField } from "../../../fields/URLField"; export type CollectionStackedTimelineProps = { Play: () => void; @@ -86,9 +88,12 @@ export class CollectionStackedTimeline extends CollectionSubView { + e.stopPropagation(); + const rect = this._timeline?.getBoundingClientRect(); + const clientX = e.clientX; + if (rect) { + this._hoverTime = Math.min(this.toTimeline(clientX - rect.x, rect.width), this.clipEnd); + if (this.dataDoc.thumbnails) { + const nearest = Math.floor(this._hoverTime / this.props.rawDuration * VideoBox.numThumbnails); + const thumbnails = Cast(this.dataDoc.thumbnails, listSpec("string"), []); + const src = new ImageField(thumbnails[nearest]).url.href.replace(".png", "_s.png"); + this._thumbnail = src; + console.log(src); + } + } + } + + // for dragging trim start handle @action trimLeft = (e: React.PointerEvent): void => { const rect = this._timeline?.getBoundingClientRect(); - const clientX = e.movementX; setupMoveUpEvents( this, e, @@ -346,7 +368,6 @@ export class CollectionStackedTimeline extends CollectionSubView { const rect = this._timeline?.getBoundingClientRect(); - const clientX = e.movementX; setupMoveUpEvents( this, e, @@ -631,6 +652,7 @@ export class CollectionStackedTimeline extends CollectionSubView e.stopPropagation()} onScroll={this.setScroll} + onMouseMove={(e) => this.isContentActive() && this.onHover(e)} ref={wrapper => this._timelineWrapper = wrapper}>
{/* {this.renderDictation} */} +
+
+ {this._thumbnail && }
); } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 787ad939d..59c37753a 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -591,6 +591,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent
} + {this.miniPlayer &&
/
} +
{this.timeline && formatTime(Math.round(this.timeline.clipDuration))}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 236c0d5fd..172b0b16f 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -28,6 +28,8 @@ import { AnchorMenu } from "../pdf/AnchorMenu"; import { StyleProp } from "../StyleProvider"; import { FieldView, FieldViewProps } from './FieldView'; import "./VideoBox.scss"; +import { Image } from "wikijs"; +import { List } from "../../../fields/List"; const path = require('path'); @@ -85,6 +87,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent ref @@ -370,6 +373,42 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + this.layoutDoc.cloneO + const video = document.createElement('video'); + const thumbnails: string[] = []; + video.onloadedmetadata = () => { + video.currentTime = 0; + }; + video.onseeked = () => { + const canvas = document.createElement('canvas'); + canvas.height = video.videoHeight; + canvas.width = video.videoWidth; + const ctx = canvas.getContext('2d'); + ctx?.drawImage(video, 0, 0, canvas.width, canvas.height); + const imgUrl = canvas.toDataURL(); + const retitled = StrCast(this.rootDoc.title).replace(/[ -\.:]/g, ""); + const encodedFilename = encodeURIComponent("thumbnail" + retitled + "_" + video.currentTime.toString().replace(/\./, "_")); + const filename = basename(encodedFilename); + VideoBox.convertDataUri(imgUrl, filename).then((returnedFilename: string) => { + returnedFilename && thumbnails.push(returnedFilename); + const newTime = video.currentTime + video.duration / VideoBox.numThumbnails; + if (newTime < video.duration) { + video.currentTime = newTime; + console.log(thumbnails.length); + } + else { + this.dataDoc.thumbnails = new List(thumbnails); + } + }); + }; + + const field = Cast(this.dataDoc[this.fieldKey], VideoField); + field && (video.src = field.url.href); + } + + // sets video element ref @action setVideoRef = (vref: HTMLVideoElement | null) => { @@ -381,6 +420,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent NumCast(this.layoutDoc._currentTimecode), time => !this._playing && (vref.currentTime = time), { fireImmediately: true }); + + !this.dataDoc.thumbnails && this.getVideoThumbnails(); } } @@ -395,11 +436,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent this._controlsVisible = false), 3000) - console.log("added"); } else { document.removeEventListener('pointermove', this.controlsFade); - console.log("removed"); } }); } -- cgit v1.2.3-70-g09d2 From bf3f88f5236a6ebda4ca3b6dc246f98ff404a360 Mon Sep 17 00:00:00 2001 From: mehekj Date: Wed, 15 Jun 2022 13:11:16 -0400 Subject: ui fixes and thumbnails upload in parallel --- src/client/views/AudioWaveform.scss | 2 +- .../collections/CollectionStackedTimeline.scss | 13 +++++-- .../collections/CollectionStackedTimeline.tsx | 19 +++++----- src/client/views/nodes/AudioBox.tsx | 6 ++-- src/client/views/nodes/VideoBox.tsx | 40 +++++++--------------- 5 files changed, 38 insertions(+), 42 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/AudioWaveform.scss b/src/client/views/AudioWaveform.scss index e20434a25..6cbd1759a 100644 --- a/src/client/views/AudioWaveform.scss +++ b/src/client/views/AudioWaveform.scss @@ -1,7 +1,7 @@ .audioWaveform { position: relative; width: 100%; - height: 100%; + height: 200%; overflow: hidden; z-index: -1000; bottom: 0; diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 580cbccda..f9cf5cd4e 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -6,7 +6,6 @@ overflow-y: hidden; border: none; background-color: $white; - border: 2px solid $dark-gray; border-width: 0 2px 0 2px; &:hover { @@ -16,7 +15,7 @@ } } -.timeline-container:hover + .videoBox-thumbnail { +.timeline-container:hover + .timeline-hoverUI { display: block; } @@ -29,6 +28,7 @@ background: $off-white; z-index: 1000; height: 100%; + overflow: hidden; .collectionStackedTimeline-trim-shade { position: absolute; @@ -127,10 +127,17 @@ } } -.videoBox-thumbnail { +.timeline-hoverUI { position: absolute; z-index: 10000; transform: translate(-50%, 100%); height: 100%; display: none; + + .hoverTime { + color: $dark-gray; + text-align: center; + transform: translate(0, -17px); + font-weight: bold; + } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 2b78f5764..850aa5dbe 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -326,13 +326,13 @@ export class CollectionStackedTimeline extends CollectionSubView 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; } } } @@ -576,7 +576,7 @@ export class CollectionStackedTimeline extends CollectionSubView (this.props.PanelHeight() * (100 - this.dictationHeightPercent)) / 100; @computed get timelineContentHeight() { return this.props.PanelHeight() * this.dictationHeightPercent / 100; } - @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor - 4; } // subtract size of container border + @computed get timelineContentWidth() { return this.props.PanelWidth() * this.zoomFactor; } // subtract size of container border dictationScreenToLocalTransform = () => this.props.ScreenToLocalTransform().translate(0, -this.timelineContentHeight); @@ -723,12 +723,12 @@ export class CollectionStackedTimeline extends CollectionSubView {/* {this.renderDictation} */} -
+ />}
- {this._thumbnail && } +
+ {this._thumbnail && } +
{formatTime(this._hoverTime)}
+
); } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 59c37753a..1d06f368f 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -581,7 +581,9 @@ export class AudioBox extends ViewBoxAnnotatableComponent {this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))}
- {!this.miniPlayer && + {this.miniPlayer ? +
/
+ :
} - {this.miniPlayer &&
/
} -
{this.timeline && formatTime(Math.round(this.timeline.clipDuration))}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 80ff19519..ef3b0d105 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -32,18 +32,6 @@ import { Image } from "wikijs"; import { List } from "../../../fields/List"; const path = require('path'); - -//TODO mj: one option for doing thumbnail previews of video in timeline? -/** - * 1. set vref in videobox - * 2. when vref is set immediately process video to extract ~50 thumbnails - * (^^ would make more sense to do this when video is initially uploaded) - * 3. upload each file to server using convertDataURI and save list of URLs as field on doc - * 4. in CST onHover, set hover time - * 5. use hover time to figure out index of nearest thumbnail - * 6. get URL of image and use source to paint canvas accordingly - */ - /** * VideoBox * Main component: VideoBox.tsx @@ -375,12 +363,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - this.layoutDoc.cloneO const video = document.createElement('video'); - const thumbnails: string[] = []; + const thumbnailPromises: Promise[] = []; + video.onloadedmetadata = () => { video.currentTime = 0; }; + video.onseeked = () => { const canvas = document.createElement('canvas'); canvas.height = video.videoHeight; @@ -391,18 +380,15 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - returnedFilename && thumbnails.push(returnedFilename); - const newTime = video.currentTime + video.duration / VideoBox.numThumbnails; - if (newTime < video.duration) { - video.currentTime = newTime; - console.log(thumbnails.length); - } - else { - this.dataDoc.thumbnails = new List(thumbnails); - } - }); - }; + thumbnailPromises.push(VideoBox.convertDataUri(imgUrl, filename)); + const newTime = video.currentTime + video.duration / (VideoBox.numThumbnails - 1); + if (newTime < video.duration) { + video.currentTime = newTime; + } + else { + Promise.all(thumbnailPromises).then(thumbnails => { this.dataDoc.thumbnails = new List(thumbnails); }); + } + } const field = Cast(this.dataDoc[this.fieldKey], VideoField); field && (video.src = field.url.href); @@ -421,7 +407,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent NumCast(this.layoutDoc._currentTimecode), time => !this._playing && (vref.currentTime = time), { fireImmediately: true }); - !this.dataDoc.thumbnails && this.getVideoThumbnails(); + (!this.dataDoc.thumbnails || this.dataDoc.thumbnails.length != VideoBox.numThumbnails) && this.getVideoThumbnails(); } } -- cgit v1.2.3-70-g09d2 From 7eedde332010c8896be636f0b5c6a7b2c8043e48 Mon Sep 17 00:00:00 2001 From: mehekj Date: Wed, 15 Jun 2022 13:22:37 -0400 Subject: made hover time more visible --- src/client/views/collections/CollectionStackedTimeline.scss | 8 +++++--- src/client/views/collections/CollectionStackedTimeline.tsx | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index f9cf5cd4e..bb98e1c99 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -16,7 +16,8 @@ } .timeline-container:hover + .timeline-hoverUI { - display: block; + display: flex; + justify-content: center; } ::-webkit-scrollbar { @@ -135,9 +136,10 @@ display: none; .hoverTime { + position: absolute; color: $dark-gray; - text-align: center; - transform: translate(0, -17px); + transform: translate(0, -100%); + font-weight: bold; } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 850aa5dbe..8bdcfeea9 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -773,8 +773,8 @@ export class CollectionStackedTimeline extends CollectionSubView
- {this._thumbnail && }
{formatTime(this._hoverTime)}
+ {this._thumbnail && }
); } -- cgit v1.2.3-70-g09d2 From 87742396ffc315c41c7ae8bfdbb916940bdd2db4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 20 Jul 2022 09:31:31 -0400 Subject: fixed hiding display of timeline for videoBox --- src/client/views/collections/CollectionStackedTimeline.scss | 11 ++++++----- src/client/views/collections/CollectionStackedTimeline.tsx | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) (limited to 'src/client/views/collections/CollectionStackedTimeline.scss') diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index bb98e1c99..6611477e5 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -1,6 +1,6 @@ -@import "../global/globalCssVariables.scss"; +@import '../global/globalCssVariables.scss'; -.timeline-container { +.collectionStackedTimeline-timelineContainer { height: 100%; overflow-x: auto; overflow-y: hidden; @@ -15,7 +15,7 @@ } } -.timeline-container:hover + .timeline-hoverUI { +.collectionStackedTimeline-timelineContainer:hover + .timeline-hoverUI { display: flex; justify-content: center; } @@ -72,7 +72,8 @@ border-width: 1px; } - .collectionStackedTimeline-current, .collectionStackedTimeline-hover { + .collectionStackedTimeline-current, + .collectionStackedTimeline-hover { width: 1px; height: 100%; position: absolute; @@ -142,4 +143,4 @@ font-weight: bold; } -} \ No newline at end of file +} diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index dcf3f7c51..f1d842386 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -561,7 +561,7 @@ export class CollectionStackedTimeline extends CollectionSubView
e.stopPropagation()} onScroll={this.setScroll} -- cgit v1.2.3-70-g09d2