diff options
author | bobzel <zzzman@gmail.com> | 2021-01-28 18:06:05 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2021-01-28 18:06:05 -0500 |
commit | 32361454f6582155bc84c154dc9315aa794b359e (patch) | |
tree | 41fa0d329d9fcfff927f157d1e38579f8987245f /src | |
parent | ecacd10cd3f70013d7112f5742eb4168e898949e (diff) |
cleaned up video/audio/stackedTimeline css. restored SpaceKey trigger to start/stop creating an anchor.
Diffstat (limited to 'src')
-rw-r--r-- | src/Utils.ts | 12 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.scss | 407 | ||||
-rw-r--r-- | src/client/views/collections/CollectionStackedTimeline.tsx | 213 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.scss | 224 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 43 | ||||
-rw-r--r-- | src/client/views/nodes/ImageBox.tsx | 2 | ||||
-rw-r--r-- | src/client/views/nodes/VideoBox.scss | 132 | ||||
-rw-r--r-- | src/client/views/nodes/VideoBox.tsx | 11 |
8 files changed, 190 insertions, 854 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 3cf695a30..dcfb579ca 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -600,11 +600,16 @@ export function hasDescendantTarget(x: number, y: number, target: HTMLDivElement return entered; } +export function StopEvent(e: React.PointerEvent | React.MouseEvent) { + e.stopPropagation(); + e.preventDefault(); +} + export function setupMoveUpEvents( target: object, e: React.PointerEvent, moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean, - upEvent: (e: PointerEvent, movement: number[]) => any, + upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any, clickEvent: (e: PointerEvent, doubleTap?: boolean) => any, stopPropagation: boolean = true, stopMovePropagation: boolean = true @@ -632,8 +637,9 @@ export function setupMoveUpEvents( const _upEvent = (e: PointerEvent): void => { (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < 300); (target as any)._lastTap = Date.now(); - upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY]); - if (Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4) { + const isClick = Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4; + upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY], isClick); + if (isClick) { if ((target as any)._doubleTime && (target as any)._doubleTap) { clearTimeout((target as any)._doubleTime); (target as any)._doubleTime = undefined; diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 1bb5bc720..9d1b46f27 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -1,373 +1,62 @@ -.audiobox-container, -.audiobox-container-interactive { +.collectionStackedTimeline { + position: absolute; width: 100%; height: 100%; - position: inherit; - display: flex; - position: relative; - cursor: default; - - .audiobox-inner { - width:100%; - height: 100%; - } - - .audiobox-buttons { - display: flex; - width: 100%; - align-items: center; - height: 100%; - - .audiobox-dictation { - position: relative; - width: 30px; - height: 100%; - align-items: center; - display: inherit; - background: dimgray; - left: 0px; - } - - .audiobox-dictation:hover { - color: white; - cursor: pointer; - } - } - - .audiobox-handle { - width: 20px; - height: 100%; - display: inline-block; - } - - .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-record { - pointer-events: all; - width: 100%; + border: gray solid 1px; + border-radius: 3px; + z-index: 1000; + overflow: hidden; + top: 0px; + + .collectionStackedTimeline-selector { + position: absolute; + width: 10px; + top: 2.5%; + height: 95%; + background: lightblue; + border-radius: 5px; + opacity: 0.3; + z-index: 500; + border-style: solid; + border-color: darkblue; + border-width: 1px; + } + + .collectionStackedTimeline-current { + width: 1px; height: 100%; - position: relative; + background-color: red; + position: absolute; + top: 0px; pointer-events: none; } - .audiobox-record-interactive { - pointer-events: all; - width: 100%; - height: 100%; - position: relative; - - - } - - .recording { - margin-top: auto; - margin-bottom: auto; - width: 100%; - height: 100%; - position: relative; - padding-right: 5px; - display: flex; - background-color: red; - - .time { - position: relative; - height: 100%; - width: 100%; - font-size: 20; - text-align: center; - top: 5; - } - - .buttons { - position: relative; - margin-top: auto; - margin-bottom: auto; - width: 25px; - padding: 5px; - } - - .buttons:hover { - background-color: crimson; + .collectionStackedTimeline-marker-timeline { + position: absolute; + top: 2.5%; + height: 95%; + border-radius: 4px; + opacity: 0.3; + &:hover { + opacity: 1; } - } - - .audiobox-controls { - width: 100%; - height: 100%; - position: relative; - display: flex; - padding-left: 2px; - background: black; - .audiobox-dictation { + .collectionStackedTimeline-left-resizer, + .collectionStackedTimeline-resizer { + background: dimgrey; position: absolute; - width: 30px; + top: 0; height: 100%; - align-items: center; - display: inherit; - background: dimgray; - left: 0px; + width: 10px; + pointer-events: all; + cursor: ew-resize; + z-index: 100; } - - .audiobox-player { - margin-top: auto; - margin-bottom: auto; - width: 100%; - position: relative; - padding-right: 5px; - display: flex; - - .audiobox-playhead { - position: relative; - margin-top: auto; - margin-bottom: auto; - margin-right: 2px; - height: 25px; - padding: 2px; - border-radius: 50%; - background-color: black; - color: white; - } - - .audiobox-playhead:hover { - // background-color: black; - // border-radius: 5px; - background-color: grey; - color: lightgrey; - } - - .audiobox-dictation { - position: relative; - margin-top: auto; - margin-bottom: auto; - width: 25px; - padding: 2px; - align-items: center; - display: inherit; - background: dimgray; - } - - .audiobox-timeline { - position: absolute; - width: 100%; - border: gray solid 1px; - border-radius: 3px; - z-index: 1000; - overflow: hidden; - - .audiobox-container { - position: absolute; - width: 10px; - top: 2.5%; - height: 0px; - background: lightblue; - border-radius: 5px; - // box-shadow: black 2px 2px 1px; - opacity: 0.3; - z-index: 500; - border-style: solid; - border-color: darkblue; - border-width: 1px; - } - - .audiobox-current { - width: 1px; - height: 100%; - background-color: red; - position: absolute; - top: 0px; - } - - .waveform { - position: relative; - width: 100%; - height: 100%; - overflow: hidden; - z-index: -1000; - bottom: 0; - pointer-events: none; - div { - height: 100% !important; - width: 100% !important; - } - canvas { - height: 100% !important; - width: 100% !important; - } - } - - .audiobox-linker, - .audiobox-linker-mini { - position: absolute; - width: 15px; - min-height: 10px; - height: 15px; - margin-left: -2.55px; - background: gray; - border-radius: 100%; - opacity: 0.9; - box-shadow: black 2px 2px 1px; - - .linkAnchorBox-cont { - position: relative !important; - height: 100% !important; - width: 100% !important; - left: unset !important; - top: unset !important; - } - } - - .audiobox-linker-mini { - width: 8px; - min-height: 8px; - height: 8px; - box-shadow: black 1px 1px 1px; - margin-left: -1; - margin-top: -2; - - .linkAnchorBox-cont { - position: relative !important; - height: 100% !important; - width: 100% !important; - left: unset !important; - top: unset !important; - } - } - - .audiobox-linker:hover, - .audiobox-linker-mini:hover { - opacity: 1; - } - - .audiobox-marker-container, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 10px; - top: 2.5%; - background: gray; - border-radius: 50%; - box-shadow: black 2px 2px 1px; - overflow: visible; - cursor: pointer; - - .audiobox-marker { - position: relative; - height: 100%; - // height: calc(100% - 15px); - width: 100%; - //margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - } - - .audiobox-marker-timeline, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 90%; - top: 2.5%; - border-radius: 5px; - - .left-resizer { - background: dimgrey; - } - .resizer { - background: dimgrey; - } - - .audiobox-marker { - position: relative; - height: calc(100% - 15px); - margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - - .resizer { - position: absolute; - top: 0; - right: 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - - .click { - position: relative; - height: 100%; - width: 100%; - z-index: 100; - } - - .left-resizer { - position: absolute; - left: 0; - top : 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - } - - .audiobox-marker-timeline:hover, - .audiobox-marker-minicontainer:hover { - opacity: 0.8; - } - - .audiobox-marker-minicontainer { - width: 5px; - border-radius: 1px; - - .audiobox-marker { - position: relative; - height: calc(100% - 8px); - margin-top: 8px; - } - } - } + .collectionStackedTimeline-resizer { + right: 0; + } + .collectionStackedTimeline-left-resizer { + left: 0; } - } -} - -@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-container .audiobox-record, - .audiobox-container-interactive .audiobox-record { - font-size: 3em; - } - - .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead, - .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation, - .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-playhead { - width: 70px; } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 1d7e40e96..11a74c4bc 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -8,15 +8,15 @@ import { List } from "../../../fields/List"; import { listSpec, makeInterface } from "../../../fields/Schema"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; import { Cast, NumCast } from "../../../fields/Types"; -import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents } from "../../../Utils"; +import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents, StopEvent } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { Scripting } from "../../util/Scripting"; import { SelectionManager } from "../../util/SelectionManager"; +import { undoBatch } from "../../util/UndoManager"; import { CollectionSubView } from "../collections/CollectionSubView"; import { DocumentView } from "../nodes/DocumentView"; import { LabelBox } from "../nodes/LabelBox"; import "./CollectionStackedTimeline.scss"; -import { undoBatch } from "../../util/UndoManager"; type PanZoomDocument = makeInterface<[]>; const PanZoomDocument = makeInterface(); @@ -35,28 +35,26 @@ export type CollectionStackedTimelineProps = { @observer export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument, CollectionStackedTimelineProps>(PanZoomDocument) { + @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; static RangeScript: ScriptField; static LabelScript: ScriptField; static RangePlayScript: ScriptField; static LabelPlayScript: ScriptField; - _disposers: { [name: string]: IReactionDisposer } = {}; - _doubleTime: NodeJS.Timeout | undefined; // bcz: Hack! this must be called _doubleTime since setupMoveDragEvents will use that field name - _ele: HTMLAudioElement | null = null; - _start: number = 0; - _left: boolean = false; - _dragging = false; - _play: any = null; - _audioRef = React.createRef<HTMLDivElement>(); - _timeline: Opt<HTMLDivElement>; - _markerStart: number = 0; - _currAnchor: Opt<Doc>; - - @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; + private _doubleTime: NodeJS.Timeout | undefined; // bcz: Hack! this must be called _doubleTime since setupMoveDragEvents will use that field name + private _timeline: HTMLDivElement | null = null; + private _markerStart: number = 0; @observable _markerEnd: number = 0; - @observable _position: number = 0; + + get duration() { return this.props.duration; } @computed get anchorDocs() { return this.childDocs; } - @computed get currentTime() { return NumCast(this.props.Document._currentTimecode); } + @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); } + @computed get selectionContainer() { + return CollectionStackedTimeline.SelectingRegion !== this ? (null) : <div className="collectionStackedTimeline-selector" style={{ + left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.duration * 100}%`, + width: `${Math.abs(this._markerStart - this._markerEnd) / this.duration * 100}%` + }} />; + } constructor(props: any) { super(props); @@ -67,28 +65,37 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument CollectionStackedTimeline.LabelPlayScript = CollectionStackedTimeline.LabelPlayScript || ScriptField.MakeFunction(`scriptContext.playOnClick(this, clientX)`, { self: Doc.name, scriptContext: "any", clientX: "number" })!; } - // for creating key anchors with key events + componentDidMount() { document.addEventListener("keydown", this.keyEvents, true); } @action - keyEvents = (e: KeyboardEvent) => { - if (e.target instanceof HTMLInputElement) return; - if (!this.props.playing()) return; // can't create if video is not playing - switch (e.key) { - case "x": // currently set to x, but can be a different key - const currTime = this.currentTime; - if (this._start) { - this._markerStart = currTime; - // this._start = false; - // this._visible = true; - } else { - this.createAnchor(this._markerStart, currTime); - // this._start = true; - // this._visible = false; - } - } + componentWillUnmount() { + document.removeEventListener("keydown", this.keyEvents, true); + if (CollectionStackedTimeline.SelectingRegion === this) CollectionStackedTimeline.SelectingRegion = undefined; } 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)); + toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration)); + rangeClickScript = () => CollectionStackedTimeline.RangeScript; + labelClickScript = () => CollectionStackedTimeline.LabelScript; + rangePlayScript = () => CollectionStackedTimeline.RangePlayScript; + labelPlayScript = () => CollectionStackedTimeline.LabelPlayScript; + + // for creating key anchors with key events + @action + keyEvents = (e: KeyboardEvent) => { + if (!(e.target instanceof HTMLInputElement) && this.props.isSelected(true)) { + switch (e.key) { + case " ": + if (!CollectionStackedTimeline.SelectingRegion) { + this._markerStart = this._markerEnd = this.currentTime; + CollectionStackedTimeline.SelectingRegion = this; + } else { + this.createAnchor(this._markerStart, this.currentTime); + CollectionStackedTimeline.SelectingRegion = undefined; + } + } + } + } getLinkData(l: Doc) { let la1 = l.anchor1 as Doc; @@ -101,69 +108,48 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument return { la1, la2, linkTime }; } - // ref for timeline - timelineRef = (timeline: HTMLDivElement) => { - this._timeline = timeline; - } - - // updates the anchor with the new time - @action - changeAnchor = (anchor: Opt<Doc>, time: number) => { - if (anchor) { - const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; - if (timelineOnly) Doc.SetInPlace(anchor, this._left ? this.props.startTag : this.props.endTag, time, true); - else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; - } - } - - // checks if the two anchors are the same with start and end time - isSame = (m1: any, m2: any) => { - return this.anchorStart(m1) === this.anchorStart(m2) && this.anchorEnd(m1) === this.anchorEnd(m2); - } - - @computed get selectionContainer() { - return CollectionStackedTimeline.SelectingRegion !== this ? (null) : <div className="audiobox-container" style={{ - left: `${Math.min(NumCast(this._markerStart), NumCast(this._markerEnd)) / this.props.duration * 100}%`, - width: `${Math.abs(this._markerStart - this._markerEnd) / this.props.duration * 100}%`, height: "100%", top: "0%" - }} />; - } - // starting the drag event for anchor resizing @action onPointerDownTimeline = (e: React.PointerEvent): void => { - const rect = this._timeline?.getBoundingClientRect();// (e.target as any).getBoundingClientRect(); - if (rect && e.target !== this._audioRef.current && this.props.active()) { + const rect = this._timeline?.getBoundingClientRect(); + const clientX = e.clientX; + if (rect && this.props.active()) { const wasPlaying = this.props.playing(); if (wasPlaying) this.props.Pause(); else if (!this._doubleTime) { this._doubleTime = setTimeout(() => { this._doubleTime = undefined; - this.props.setTime((e.clientX - rect.x) / rect.width * this.props.duration); - }, 300); + this.props.setTime((clientX - rect.x) / rect.width * this.duration); + }, 300); // 300ms is double-tap timeout + } + const wasSelecting = CollectionStackedTimeline.SelectingRegion === this; + if (!wasSelecting) { + this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); + CollectionStackedTimeline.SelectingRegion = this; } - this._markerStart = this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); - CollectionStackedTimeline.SelectingRegion = this; setupMoveUpEvents(this, e, action(e => { this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); return false; }), - action((e, movement) => { + action((e, movement, isClick) => { this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); if (this._markerEnd < this._markerStart) { const tmp = this._markerStart; this._markerStart = this._markerEnd; this._markerEnd = tmp; } - CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd); - CollectionStackedTimeline.SelectingRegion = undefined; + if (!isClick) { + CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd); + } + (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined); }), (e, doubleTap) => { this.props.select(false); e.shiftKey && this.createAnchor(this.currentTime); !wasPlaying && doubleTap && this.props.Play(); - } - , this.props.isSelected(true) || this.props.isChildActive()); + }, + this.props.isSelected(true) || this.props.isChildActive()); } } @@ -227,36 +213,33 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument } - toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.props.duration, screen_delta / width * this.props.duration)); // starting the drag event for anchor resizing - onPointerDown = (e: React.PointerEvent, m: Doc, left: boolean): void => { - this._currAnchor = m; - this._left = left; + onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { this._timeline?.setPointerCapture(e.pointerId); + const newTime = (e: PointerEvent) => { + const rect = (e.target as any).getBoundingClientRect(); + return this.toTimeline(e.clientX - rect.x, rect.width); + } + const changeAnchor = (anchor: Doc, left: boolean, time: number) => { + const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; + if (timelineOnly) Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true); + else left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; + return false; + } setupMoveUpEvents(this, e, + (e) => changeAnchor(anchor, left, newTime(e)), (e) => { - const rect = (e.target as any).getBoundingClientRect(); - this.changeAnchor(this._currAnchor, this.toTimeline(e.clientX - rect.x, rect.width)); - return false; - }, - (e) => { - const rect = (e.target as any).getBoundingClientRect(); - this.props.setTime(this.toTimeline(e.clientX - rect.x, rect.width)); + this.props.setTime(newTime(e)); this._timeline?.releasePointerCapture(e.pointerId); }, emptyFunction); } - rangeClickScript = () => CollectionStackedTimeline.RangeScript; - labelClickScript = () => CollectionStackedTimeline.LabelScript; - rangePlayScript = () => CollectionStackedTimeline.RangePlayScript; - labelPlayScript = () => CollectionStackedTimeline.LabelPlayScript; - // makes sure no anchors overlaps each other by setting the correct position and width getLevel = (m: Doc, placed: { anchorStartTime: number, anchorEndTime: number, level: number }[]) => { const timelineContentWidth = this.props.PanelWidth(); const x1 = this.anchorStart(m); - const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.props.duration); + const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.duration); let max = 0; const overlappedLevels = new Set(placed.map(p => { const y1 = p.anchorStartTime; @@ -281,18 +264,15 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument ref={action((r: DocumentView | null) => anchor.view = r)} Document={mark} DataDoc={undefined} - PanelWidth={() => width} - PanelHeight={() => height} renderDepth={this.props.renderDepth + 1} - focus={() => this.props.playLink(mark)} - rootSelected={returnFalse} LayoutTemplate={undefined} LayoutTemplateString={LabelBox.LayoutString("data")} - ContainingCollectionDoc={this.props.Document} - removeDocument={this.props.removeDocument} + PanelWidth={() => width} + PanelHeight={() => height} ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-x, -y)} - parentActive={(out) => this.props.isSelected(out) || this.props.isChildActive()} - whenActiveChanged={this.props.whenActiveChanged} + focus={() => this.props.playLink(mark)} + parentActive={out => this.props.isSelected(out) || this.props.isChildActive()} + rootSelected={returnFalse} onClick={script} onDoubleClick={this.props.Document.autoPlay ? undefined : doublescript} ignoreAutoHeight={false} @@ -307,8 +287,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument {inner.view} {!inner.anchor.view || !SelectionManager.IsSelected(inner.anchor.view) ? (null) : <> - <div key="left" className="left-resizer" onPointerDown={e => this.onPointerDown(e, mark, true)} /> - <div key="right" className="resizer" onPointerDown={e => this.onPointerDown(e, mark, false)} /> + <div key="left" className="collectionStackedTimeline-left-resizer" onPointerDown={e => this.onAnchorDown(e, mark, true)} /> + <div key="right" className="collectionStackedTimeline-resizer" onPointerDown={e => this.onAnchorDown(e, mark, false)} /> </>} </>; }); @@ -319,39 +299,28 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = []; const drawAnchors = this.anchorDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; - return <div className="audiobox-timeline" style={{ height: "100%", width: "100%" }} ref={this.timelineRef} - onClick={e => { - if (this.props.isChildActive() || this.props.isSelected(false)) { - e.stopPropagation(); e.preventDefault(); - } - }} - onPointerDown={e => { - if (this.props.isChildActive() || this.props.isSelected(false)) { - e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e); - } - }}> + const isActive = this.props.isChildActive() || this.props.isSelected(false); + return <div className="collectionStackedTimeline" ref={(timeline: HTMLDivElement | null) => this._timeline = timeline} + onClick={e => isActive && StopEvent(e)} onPointerDown={e => isActive && this.onPointerDownTimeline(e)}> {drawAnchors.map(d => { - const m = d.anchor; - const start = this.anchorStart(m); - const end = this.anchorEnd(m, start + 10 / timelineContentWidth * this.props.duration); - const left = start / this.props.duration * timelineContentWidth; + const start = this.anchorStart(d.anchor); + const end = this.anchorEnd(d.anchor, start + 10 / timelineContentWidth * this.duration); + const left = start / this.duration * timelineContentWidth; const top = d.level / maxLevel * timelineContentHeight; const timespan = end - start; return this.props.Document.hideAnchors ? (null) : - <div className={`audiobox-marker-${this.props.PanelHeight() < 32 ? "mini" : ""}timeline`} key={m[Id]} - style={{ left, top, width: `${timespan / this.props.duration * 100}%`, height: `${1 / maxLevel * 100}%` }} - onClick={e => { this.props.playFrom(start, this.anchorEnd(m)); e.stopPropagation(); }} > - {this.renderAnchor(m, this.rangeClickScript, this.rangePlayScript, + <div className={"collectionStackedTimeline-marker-timeline"} key={d.anchor[Id]} + style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${timelineContentHeight / maxLevel}px` }} + onClick={e => { this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} > + {this.renderAnchor(d.anchor, this.rangeClickScript, this.rangePlayScript, left, top, - timelineContentWidth * timespan / this.props.duration, + timelineContentWidth * timespan / this.duration, timelineContentHeight / maxLevel)} </div>; })} {this.selectionContainer} - <div className="audiobox-current" ref={this._audioRef} onClick={e => { e.stopPropagation(); e.preventDefault(); }} - style={{ left: `${this.currentTime / this.props.duration * 100}%`, pointerEvents: "none" }} - /> + <div className="collectionStackedTimeline-current" style={{ left: `${this.currentTime / this.duration * 100}%` }} /> </div>; } } diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 4a3bbf8d8..fc881ca25 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -7,11 +7,6 @@ position: relative; cursor: default; - .audiobox-inner { - width:100%; - height: 100%; - } - .audiobox-buttons { display: flex; width: 100%; @@ -26,20 +21,13 @@ display: inherit; background: dimgray; left: 0px; - } - - .audiobox-dictation:hover { - color: white; - cursor: pointer; + &:hover { + color: white; + cursor: pointer; + } } } - .audiobox-handle { - width: 20px; - height: 100%; - display: inline-block; - } - .audiobox-control, .audiobox-control-interactive { top: 0; @@ -53,21 +41,16 @@ pointer-events: all; } + .audiobox-record-interactive, .audiobox-record { pointer-events: all; width: 100%; height: 100%; position: relative; - pointer-events: none; } - .audiobox-record-interactive { - pointer-events: all; - width: 100%; - height: 100%; - position: relative; - - + .audiobox-record { + pointer-events: none; } .recording { @@ -95,10 +78,9 @@ margin-bottom: auto; width: 25px; padding: 5px; - } - - .buttons:hover { - background-color: crimson; + &:hover{ + background-color: crimson; + } } } @@ -138,13 +120,10 @@ border-radius: 50%; background-color: black; color: white; - } - - .audiobox-playhead:hover { - // background-color: black; - // border-radius: 5px; - background-color: grey; - color: lightgrey; + &:hover { + background-color: grey; + color: lightgrey; + } } .audiobox-dictation { @@ -166,29 +145,6 @@ z-index: 1000; overflow: hidden; - .audiobox-container { - position: absolute; - width: 10px; - top: 2.5%; - height: 0px; - background: lightblue; - border-radius: 5px; - // box-shadow: black 2px 2px 1px; - opacity: 0.3; - z-index: 500; - border-style: solid; - border-color: darkblue; - border-width: 1px; - } - - .audiobox-current { - width: 1px; - height: 100%; - background-color: red; - position: absolute; - top: 0px; - } - .waveform { position: relative; width: 100%; @@ -206,161 +162,21 @@ width: 100% !important; } } - - .audiobox-linker, - .audiobox-linker-mini { - position: absolute; - width: 15px; - min-height: 10px; - height: 15px; - margin-left: -2.55px; - background: gray; - border-radius: 100%; - opacity: 0.9; - box-shadow: black 2px 2px 1px; - - .linkAnchorBox-cont { - position: relative !important; - height: 100% !important; - width: 100% !important; - left: unset !important; - top: unset !important; - } - } - - .audiobox-linker-mini { - width: 8px; - min-height: 8px; - height: 8px; - box-shadow: black 1px 1px 1px; - margin-left: -1; - margin-top: -2; - - .linkAnchorBox-cont { - position: relative !important; - height: 100% !important; - width: 100% !important; - left: unset !important; - top: unset !important; - } - } - - .audiobox-linker:hover, - .audiobox-linker-mini:hover { - opacity: 1; - } - - .audiobox-marker-container, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 10px; - top: 2.5%; - background: gray; - border-radius: 50%; - box-shadow: black 2px 2px 1px; - overflow: visible; - cursor: pointer; - - .audiobox-marker { - position: relative; - height: 100%; - // height: calc(100% - 15px); - width: 100%; - //margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - } - - .audiobox-marker-timeline, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 90%; - top: 2.5%; - border-radius: 5px; - - .left-resizer { - background: dimgrey; - } - .resizer { - background: dimgrey; - } - - .audiobox-marker { - position: relative; - height: calc(100% - 15px); - margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - - .resizer { - position: absolute; - top: 0; - right: 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - - .click { - position: relative; - height: 100%; - width: 100%; - z-index: 100; - } - - .left-resizer { - position: absolute; - left: 0; - top : 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - } - - .audiobox-marker-timeline:hover, - .audiobox-marker-minicontainer:hover { - opacity: 0.8; - } - - .audiobox-marker-minicontainer { - width: 5px; - border-radius: 1px; - - .audiobox-marker { - position: relative; - height: 100%; - margin-top: 8px; - } - } } - .current-time { + .audioBox-total-time, + .audioBox-current-time { position: absolute; font-size: 8; top: 100%; - left: 30px; color: white; } + .audioBox-current-time { + left: 30px; + } - .total-time { - position: absolute; - top: 100%; - font-size: 8; + .audioBox-total-time { right: 2px; - color: white; } } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 741a03900..9f343e904 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -81,7 +81,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD getLinkData(l: Doc) { let la1 = l.anchor1 as Doc; let la2 = l.anchor2 as Doc; - const linkTime = this._stackedTimeline.current?.anchorStart(la2) || this._stackedTimeline.current?.anchorStart(la1); + const linkTime = this._stackedTimeline.current?.anchorStart(la2) || this._stackedTimeline.current?.anchorStart(la1) || 0; if (Doc.AreProtosEqual(la1, this.dataDoc)) { la1 = l.anchor2 as Doc; la2 = l.anchor1 as Doc; @@ -344,42 +344,35 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD } } + isActiveChild = () => this._isChildActive; + timelineWhenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(runInAction(() => this._isChildActive = isActive)); + timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-AudioBox.playheadWidth, -(100 - this.heightPercent) / 200 * this.props.PanelHeight()); + setAnchorTime = (time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time; + timelineHeight = () => this.props.PanelHeight() * this.heightPercent / 100 * this.heightPercent / 100; // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) + timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth; @computed get renderTimeline() { - return <CollectionStackedTimeline ref={this._stackedTimeline} - Document={this.props.Document} + return <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props} fieldKey={this.annotationKey} renderDepth={this.props.renderDepth + 1} - parentActive={this.props.parentActive} startTag={"audioStart"} endTag={"audioEnd"} focus={emptyFunction} - styleProvider={this.props.styleProvider} - docFilters={this.props.docFilters} - docRangeFilters={this.props.docRangeFilters} - searchFilterDocs={this.props.searchFilterDocs} - rootSelected={this.props.rootSelected} - addDocTab={this.props.addDocTab} - pinToPres={this.props.pinToPres} bringToFront={emptyFunction} - ContainingCollectionDoc={this.props.ContainingCollectionDoc} - ContainingCollectionView={this.props.ContainingCollectionView} CollectionView={undefined} duration={this.duration} playFrom={this.playFrom} - setTime={(time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time} + setTime={this.setAnchorTime} playing={this.playing} - select={this.props.select} - isSelected={this.props.isSelected} - whenActiveChanged={action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive))} + whenActiveChanged={this.timelineWhenActiveChanged} removeDocument={this.removeDocument} - ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(0, -(100 - this.heightPercent) / 200 * this.props.PanelHeight())} - isChildActive={() => this._isChildActive} + ScreenToLocalTransform={this.timelineScreenToLocal} + isChildActive={this.isActiveChild} Play={this.Play} Pause={this.Pause} active={this.active} playLink={this.playLink} - PanelWidth={this.props.PanelWidth} - PanelHeight={() => this.props.PanelHeight() * this.heightPercent / 100 * this.heightPercent / 100}// panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) + PanelWidth={this.timelineWidth} + PanelHeight={this.timelineHeight} />; } @@ -413,17 +406,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD <div className="audiobox-dictation" /> <div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} > <div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.audioState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.audioState === "paused" ? "play" : "pause"} size={"1x"} /></div> - <div className="audiobox-timeline" style={{ height: `100%`, left: AudioBox.playheadWidth, width: `calc(100% - ${AudioBox.playheadWidth}px)`, background: "white" }}> + <div className="audiobox-timeline" style={{ top: 0, height: `100%`, left: AudioBox.playheadWidth, width: `calc(100% - ${AudioBox.playheadWidth}px)`, background: "white" }}> <div className="waveform"> {this.waveform} </div> + {this.renderTimeline} </div> - {this.renderTimeline} {this.audio} - <div className="current-time"> + <div className="audioBox-current-time"> {formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))} </div> - <div className="total-time"> + <div className="audioBox-total-time"> {formatTime(Math.round(this.duration))} </div> </div> diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 1da3a2779..92d6e2612 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -421,7 +421,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent<FieldViewProps, ImageD } @action marqueeDown = (e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.props.Document._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; } @action finishMarquee = () => { diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index ac4d64f12..b9123587b 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -10,138 +10,8 @@ .inkingCanvas-paths-markers { opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround } - - .audiobox-timeline { - position: absolute; - width: 100%; + .collectionStackedTimeline { background: beige; - border: gray solid 1px; - border-radius: 3px; - z-index: 1000; - overflow: hidden; - bottom: 0; - - .audiobox-current { - width: 1px; - height: 100%; - background-color: red; - position: absolute; - top: 0px; - } - - .audiobox-container { - position: absolute; - width: 10px; - top: 2.5%; - height: 0px; - background: lightblue; - border-radius: 5px; - // box-shadow: black 2px 2px 1px; - opacity: 0.3; - z-index: 500; - border-style: solid; - border-color: darkblue; - border-width: 1px; - } - - .audiobox-marker-timeline, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 10px; - top: 2.5%; - border-radius: 50%; - box-shadow: black 2px 2px 1px; - overflow: visible; - cursor: pointer; - - .left-resizer { - background: dimgrey; - } - .resizer { - background: dimgrey; - } - .audiobox-marker { - position: relative; - height: 100%; - // height: calc(100% - 15px); - width: 100%; - //margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - } - - .audiobox-marker-timeline, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 90%; - top: 2.5%; - border-radius: 5px; - box-shadow: black 2px 2px 1px; - - .audiobox-marker { - position: relative; - height: calc(100% - 15px); - margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - - .resizer { - position: absolute; - top: 0; - right: 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - - .click { - position: relative; - height: 100%; - width: 100%; - z-index: 100; - } - - .left-resizer { - position: absolute; - left: 0; - top: 0; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - - // .contentFittingDocumentView-previewDoc { - // width: 100% !important; - // transform: none !important; - // } - } - - .audiobox-marker-container1:hover, - .audiobox-marker-minicontainer:hover { - opacity: 0.8; - } - - .audiobox-marker-minicontainer { - width: 5px; - border-radius: 1px; - - .audiobox-marker { - position: relative; - height: calc(100% - 8px); - margin-top: 8px; - } - } } } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 63e463e8b..c360a924e 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -7,7 +7,7 @@ import { Dictionary } from "typescript-collections"; import { Doc, DocListCast } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; -import { createSchema, makeInterface } from "../../../fields/Schema"; +import { makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { VideoField } from "../../../fields/URLField"; import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils } from "../../../Utils"; @@ -86,13 +86,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD this.dataDoc[this.fieldKey + "-duration"] = this.player!.duration; } - static keyEventsWrapper = (e: KeyboardEvent) => { - VideoBox.Instance._stackedTimeline.current?.keyEvents(e); - } - @action public Play = (update: boolean = true) => { - document.removeEventListener("keydown", VideoBox.keyEventsWrapper, true); - document.addEventListener("keydown", VideoBox.keyEventsWrapper, true); this._playing = true; try { update && this.player?.play(); @@ -248,7 +242,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD componentWillUnmount() { this.Pause(); Object.keys(this._disposers).forEach(d => this._disposers[d]?.()); - document.removeEventListener("keydown", VideoBox.keyEventsWrapper, true); } @action @@ -535,7 +528,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD } marqueeDown = action((e: React.PointerEvent) => { - if (!e.altKey && e.button === 0 && this.props.Document._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; }); finishMarquee = action(() => { |