diff options
-rw-r--r-- | src/client/views/AudioWaveform.tsx | 3 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.scss | 3 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 10 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.scss | 349 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 120 | ||||
-rw-r--r-- | src/client/views/nodes/ComparisonBox.tsx | 4 |
6 files changed, 163 insertions, 326 deletions
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<AudioWaveformProps> { 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<AudioWaveformProps> { 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 : ( - <div className="collectionStackedTimeline-waveform" style={{ width: `${this.zoomFactor * 100}%`, overflowX: "scroll" }}> + <div className="collectionStackedTimeline-waveform"> <AudioWaveform rawDuration={this.props.rawDuration} duration={this.clipDuration} @@ -580,7 +580,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)} - 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 : ( <div className={"collectionStackedTimeline-marker-timeline"} diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index a2fdd38e5..391507796 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -1,283 +1,144 @@ @import "../global/globalCssVariables.scss"; -.audiobox-container, -.audiobox-container-interactive { +.audiobox-container { width: 100%; height: 100%; position: inherit; display: flex; position: relative; cursor: default; +} + +.audiobox-recorder { + display: flex; + flex-direction: row; + overflow: hidden; + width: 100%; + height: 100%; + cursor: pointer; - .audiobox-buttons { + .audiobox-dictation { + width: 40px; + background: $medium-gray; + color: $dark-gray; display: flex; - width: 100%; + justify-content: center; 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; - } + &:hover { + color: $black; } } - .audiobox-control, - .audiobox-control-interactive { - top: 0; - max-height: 32px; + .audiobox-start-record { + color: $white; + background: $dark-gray; + display: flex; + align-items: center; + justify-content: center; + font-size: $body-text; width: 100%; - display: inline-block; - pointer-events: none; - } + height: 100%; + gap: 5px; - .audiobox-control-interactive { - pointer-events: all; + &:hover { + background: $black; + } } - .audiobox-record-interactive, - .audiobox-record { - pointer-events: all; - cursor: pointer; - width: 100%; - height: 100%; - position: relative; + .recording-controls { display: flex; flex-direction: row; align-items: center; justify-content: center; - gap: 10px; - color: white; - font-weight: bold; - background-color: $dark-gray; - } - - .audiobox-record { - pointer-events: none; - } - - .recording { - margin-top: auto; - margin-bottom: auto; + gap: 5px; width: 100%; height: 100%; - position: relative; - padding-right: 5px; - display: flex; - background-color: $medium-blue; + background: $dark-gray; + color: white; - .time { - position: relative; - width: 100%; + .record-timecode { font-size: $large-header; - text-align: center; } - .recording-buttons { - position: relative; - margin-top: auto; - margin-bottom: auto; - color: $dark-gray; - &:hover { - color: $black; - } - } - - .time, - .recording-buttons { + .record-button { + cursor: pointer; + width: 30px; + height: 30px; + border-radius: 50%; + background: $dark-gray; display: flex; align-items: center; - padding: 5px; - } - } - .audiobox-buttons { - display: flex; - width: 100%; - align-items: center; - height: 100%; + justify-content: center; - .audiobox-dictation { - position: relative; - width: 30px; - height: 100%; - align-items: center; - display: inherit; - background: $medium-gray; - left: 0px; - color: $dark-gray; + svg { + width: 15px; + } &:hover { - color: $black; - cursor: pointer; + background: $black; } } } +} - .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-file { + overflow: hidden; + display: flex; + flex-direction: column; + align-items: space-between; + background: $dark-gray; + width: 100%; + height: 100%; + color: $white; - .recording { - margin-top: auto; - margin-bottom: auto; - width: 100%; - height: 100%; - position: relative; - padding-right: 5px; + .audiobox-controls { display: flex; flex-direction: row; - justify-content: center; + justify-content: space-between; align-items: center; - gap: 7px; - background-color: $medium-blue; - padding: 10px; - - .time { - position: relative; - height: 100%; - width: 100%; - font-size: 16px; - text-align: center; - display: flex; - justify-content: center; - align-items: center; - font-weight: bold; - } - - .buttons { - cursor: pointer; - position: relative; - margin-top: auto; - margin-bottom: auto; - width: 25px; - width: 25px; - padding: 5px; - color: $dark-gray; - - &:hover { - color: $black; - } - } - } - - .audiobox-controls { width: 100%; - height: 100%; - position: relative; - display: flex; - background: $dark-gray; - - .audiobox-dictation { - position: absolute; - width: 40px; - height: 100%; - align-items: center; - display: inherit; - background: $medium-gray; - left: 0px; - } + height: 30px; - .audiobox-player { - margin-top: auto; - margin-bottom: auto; - width: 100%; - position: relative; - padding-right: 5px; + .controls-left { display: flex; - flex-direction: column; - justify-content: center; + flex-direction: row; + width: 100px; - .audiobox-buttons { - position: relative; - margin-top: auto; - margin-bottom: auto; - width: 30px; - height: 30px; + .audiobox-button { + margin: 2.5px; + cursor: pointer; + width: 25px; + height: 25px; border-radius: 50%; - background-color: $dark-gray; - color: $white; + background: $dark-gray; display: flex; align-items: center; justify-content: center; - left: 5px; - - &:hover { - background-color: $black; - } svg { - width: 100%; - position: absolute; - border-width: "thin"; - border-color: "white"; + width: 15px; } - } - - .audiobox-dictation { - position: relative; - margin-top: auto; - margin-bottom: auto; - width: 25px; - align-items: center; - display: inherit; - background: $medium-gray; - } - - .audiobox-timeline { - position: absolute; - top: 0; - background: white; - height: 100%; - width: 100%; - z-index: 1000; - overflow: hidden; - border-right: 5px solid black; - } - - .audioBox-total-time, - .audioBox-current-time { - position: absolute; - font-size: $small-text; - top: 100%; - color: $white; - } - .audioBox-current-time { - left: 42px; + &:hover { + background: $black; + } } + } - .audioBox-total-time { - right: 2px; - } + .controls-right { + display: flex; + flex-direction: row; - .toolbar-slider { - position: absolute; - top: 75px; - left: 70px; + svg { + width: 10px; } input[type="range"] { - width: calc(100% - 100px); - height: 16px; + width: 70px; -webkit-appearance: none; background: none; + margin: 5px; } input[type="range"]:focus { @@ -286,52 +147,46 @@ input[type="range"]::-webkit-slider-runnable-track { width: 100%; - height: 5px; + height: 6px; cursor: pointer; box-shadow: 0; - background: #dfdfdf; + background: $light-gray; border-radius: 3px; } input[type="range"]::-webkit-slider-thumb { box-shadow: 0; border: 0; - height: 7px; - width: 7px; + height: 10px; + width: 10px; border-radius: 10px; - background: #4476f7; + background: $medium-blue; cursor: pointer; -webkit-appearance: none; - margin: -1px; - } - - .audiobox-zoom { - bottom: 0; - left: 30px; - width: 70px; + margin: -2px; } } } -} -@media only screen and (max-device-width: 480px) { - .audiobox-dictation { - font-size: 5em; - display: flex; - width: 100; - justify-content: center; - flex-direction: column; - align-items: center; - } + .audiobox-playback { + width: 100%; + height: calc(100% - 50px); - .audiobox-container .audiobox-record, - .audiobox-container-interactive .audiobox-record { - font-size: 3em; + .audiobox-timeline { + height: 100%; + width: 100%; + background: $white; + } } - .audiobox-container .audiobox-controls .audiobox-player .audiobox-buttons, - .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation, - .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-buttons { - width: 70px; + .audiobox-timecodes { + display: flex; + flex-direction: row; + justify-content: space-between; + align-items: center; + width: 100%; + height: 20px; + padding: 3px; + font-size: $small-text; } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index f2001adcd..48e324971 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -43,8 +43,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp AudioBox._scrubTime = timeInMillisFrom1970; }); public static Enabled = false; - static playheadWidth = 40; // width of playhead - static heightPercent = 75; // height of timeline in percent of height of audioBox. + static topControlsHeight = 30; // width of playhead + static bottomControlsHeight = 20; // height of timeline in percent of height of audioBox. static zoomInterval = 0.1; @observable static _scrubTime = 0; @@ -338,19 +338,13 @@ export class AudioBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp timelineWhenChildContentsActiveChanged = (isActive: boolean) => 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<ViewBoxAnnotatableProp } onClipPointerDown = (e: React.PointerEvent) => { + 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<ViewBoxAnnotatableProp } @computed get recordingControls() { - return <div className="audiobox-buttons"> + return <div className="audiobox-recorder"> <div className="audiobox-dictation" onClick={this.onFile}> <FontAwesomeIcon - style={{ width: "30px" }} - icon="file-alt" - size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /> + size="2x" + icon="file-alt" /> </div> {[media_state.Recording, media_state.Playing].includes(this.mediaState) ? - <div className="recording" onClick={e => e.stopPropagation()}> - <div className="recording-buttons" onClick={this.Record}> + <div className="recording-controls" onClick={e => e.stopPropagation()}> + <div className="record-button" onClick={this.Record}> <FontAwesomeIcon - icon="stop" - size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /> + size="2x" + icon="stop" /> </div> - <div className="recording-buttons" - onClick={this._paused ? this.recordPlay : this.recordPause} - > + <div className="record-button" onClick={this._paused ? this.recordPlay : this.recordPause}> <FontAwesomeIcon - icon={this._paused ? "play" : "pause"} - size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /> + size="2x" + icon={this._paused ? "play" : "pause"} /> </div> - <div className="time"> + <div className="record-timecode"> {formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))} </div> </div> : - <div className={`audiobox-record${this.props.isContentActive() ? "-interactive" : ""}`}> + <div className="audiobox-start-record"> <FontAwesomeIcon icon="microphone" /> RECORD </div>} - </div>; + </div> } @computed get playbackControls() { - return <div className="audiobox-controls" - style={{ pointerEvents: this._isAnyChildContentActive || this.props.isContentActive() ? "all" : "none", }} - > - <div className="audiobox-dictation" /> - <div className="audiobox-player" - style={{ height: `${AudioBox.heightPercent}%` }} - > - <div className="audiobox-buttons" - title={this.mediaState === media_state.Paused ? "play" : "pause"} - onClick={this.mediaState === media_state.Paused ? this.Play : this.Pause} - > - {" "} - <FontAwesomeIcon - icon={this.mediaState === media_state.Paused ? "play" : "pause"} - size={"1x"} /> - </div> + return <div className="audiobox-file" style={{ pointerEvents: this._isAnyChildContentActive || this.props.isContentActive() ? "all" : "none", }}> + <div className="audiobox-controls"> + <div className="controls-left"> + <div className="audiobox-button" + title={this.mediaState === media_state.Paused ? "play" : "pause"} + onPointerDown={this.mediaState === media_state.Paused ? this.Play : this.Pause}> + <FontAwesomeIcon icon={this.mediaState === media_state.Paused ? "play" : "pause"} size={"1x"} /> + </div> - <div className="audiobox-buttons" - title={this.timeline?.IsTrimming !== TrimScope.None ? "finish" : "trim"} - onPointerDown={this.onClipPointerDown} - > - <FontAwesomeIcon - icon={this.timeline?.IsTrimming !== TrimScope.None ? "check" : "cut"} - size={"1x"} /> + <div className="audiobox-button" + title={this.timeline?.IsTrimming !== TrimScope.None ? "finish" : "trim"} + onPointerDown={this.onClipPointerDown}> + <FontAwesomeIcon icon={this.timeline?.IsTrimming !== TrimScope.None ? "check" : "cut"} size={"1x"} /> + </div> </div> + <div className="controls-right"> + <FontAwesomeIcon icon={["fas", "search"]} /> + <input type="range" step="0.1" min="1" max="5" value={this.timeline?._zoomFactor} + className="toolbar-slider" id="zoom-slider" + onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }} + onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.zoom(Number(e.target.value)); }} + /> + </div> + </div> - <div className="audiobox-timeline" - style={{ - left: AudioBox.playheadWidth, - width: `calc(100% - ${AudioBox.playheadWidth}px)`, - }} - > + <div className="audiobox-playback"> + <div className="audiobox-timeline"> {this.renderTimeline} </div> - {this.audio} - <div className="audioBox-current-time"> - {this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))} - </div> + </div> - {/* <input type="range" step="0.1" min="1" max="5" value={this.timeline?._zoomFactor} - className="toolbar-slider" id="zoom-slider" - onPointerDown={(e: React.PointerEvent) => { e.stopPropagation(); }} - onChange={(e: React.ChangeEvent<HTMLInputElement>) => { this.zoom(e.target.value); }} - /> */} + {this.audio} - <div className="audioBox-total-time"> + <div className="audiobox-timecodes"> + <div className="timecode-current"> + {this.timeline && formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this.timeline.clipStart)))} + </div> + <div className="timecode-duration"> {this.timeline && formatTime(Math.round(NumCast(this.timeline?.clipDuration)))} </div> </div> - </div>; + + + </div> } @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<ViewBoxAnnotatabl }; const displayDoc = (which: string) => { 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 ? <> <DocumentView ref={(r) => { |