aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Utils.ts12
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss407
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx213
-rw-r--r--src/client/views/nodes/AudioBox.scss224
-rw-r--r--src/client/views/nodes/AudioBox.tsx43
-rw-r--r--src/client/views/nodes/ImageBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.scss132
-rw-r--r--src/client/views/nodes/VideoBox.tsx11
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(() => {