aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/documents/Documents.ts3
-rw-r--r--src/client/views/nodes/AudioBox.scss118
-rw-r--r--src/client/views/nodes/AudioBox.tsx373
-rw-r--r--src/client/views/nodes/AudioResizer.scss11
-rw-r--r--src/client/views/nodes/AudioResizer.tsx48
-rw-r--r--src/fields/documentSchemas.ts4
6 files changed, 528 insertions, 29 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index e2569ec70..170426db3 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -190,6 +190,9 @@ export interface DocumentOptions {
searchQuery?: string; // for queryBox
filterQuery?: string;
linearViewIsExpanded?: boolean; // is linear view expanded
+ isLabel?: boolean; // whether the document is a label or not (video / audio)
+ audioStart?: number; // the time frame where the audio should begin playing
+ audioEnd?: number; // the time frame where the audio should stop playing
}
class EmptyBox {
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index e9420a072..6601c6f24 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -46,6 +46,40 @@
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;
+ }
}
.audiobox-controls {
@@ -54,6 +88,7 @@
position: relative;
display: flex;
padding-left: 2px;
+ background: black;
.audiobox-player {
margin-top: auto;
@@ -64,16 +99,28 @@
padding-right: 5px;
display: flex;
- .audiobox-playhead,
- .audiobox-dictation {
+ .audiobox-playhead {
position: relative;
margin-top: auto;
margin-bottom: auto;
- width: 25px;
+ margin-right: 2px;
+ width: 30px;
+ height: 25px;
padding: 2px;
+ border-radius: 50%;
+ background-color: dimgrey;
+ }
+
+ .audiobox-playhead:hover {
+ background-color: white;
}
.audiobox-dictation {
+ position: relative;
+ margin-top: auto;
+ margin-bottom: auto;
+ width: 25px;
+ padding: 2px;
align-items: center;
display: inherit;
background: dimgray;
@@ -81,11 +128,12 @@
.audiobox-timeline {
position: relative;
- height: 100%;
+ height: 80%;
width: 100%;
background: white;
border: gray solid 1px;
border-radius: 3px;
+ z-index: 1000;
.audiobox-current {
width: 1px;
@@ -142,11 +190,34 @@
.audiobox-marker-minicontainer {
position: absolute;
width: 10px;
+ height: 10px;
+ top: 2.5%;
+ background: gray;
+ border-radius: 50%;
+ box-shadow: black 2px 2px 1px;
+ overflow: auto;
+
+ .audiobox-marker {
+ position: relative;
+ height: calc(100% - 15px);
+ margin-top: 15px;
+ }
+
+ .audio-marker:hover {
+ border: orange 2px solid;
+ }
+ }
+
+ .audiobox-marker-container1,
+ .audiobox-marker-minicontainer {
+ position: absolute;
+ width: 10px;
height: 90%;
top: 2.5%;
background: gray;
border-radius: 5px;
box-shadow: black 2px 2px 1px;
+ opacity: 0.3;
.audiobox-marker {
position: relative;
@@ -157,6 +228,29 @@
.audio-marker:hover {
border: orange 2px solid;
}
+
+ .resizer {
+ position: absolute;
+ right: 0;
+ cursor: ew-resize;
+ height: 100%;
+ width: 2px;
+ z-index: 100;
+ }
+
+ .left-resizer {
+ position: absolute;
+ left: 0;
+ cursor: ew-resize;
+ height: 100%;
+ width: 2px;
+ z-index: 100;
+ }
+ }
+
+ .audiobox-marker-container1:hover,
+ .audiobox-marker-minicontainer:hover {
+ opacity: 1;
}
.audiobox-marker-minicontainer {
@@ -170,6 +264,22 @@
}
}
}
+
+ .current-time {
+ position: absolute;
+ font-size: 12;
+ top: calc(100% - 10px);
+ left: 30px;
+ color: white;
+ }
+
+ .total-time {
+ position: absolute;
+ top: calc(100% - 10px);
+ font-size: 12;
+ right: 2px;
+ color: white;
+ }
}
}
}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 5c921cea4..5863c8789 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -2,13 +2,13 @@ import React = require("react");
import { FieldViewProps, FieldView } from './FieldView';
import { observer } from "mobx-react";
import "./AudioBox.scss";
-import { Cast, DateCast, NumCast } from "../../../fields/Types";
+import { Cast, DateCast, NumCast, FieldValue, ScriptCast } from "../../../fields/Types";
import { AudioField, nullAudio } from "../../../fields/URLField";
-import { ViewBoxBaseComponent } from "../DocComponent";
+import { ViewBoxBaseComponent, ViewBoxAnnotatableComponent } from "../DocComponent";
import { makeInterface, createSchema } from "../../../fields/Schema";
import { documentSchema } from "../../../fields/documentSchemas";
import { Utils, returnTrue, emptyFunction, returnOne, returnTransparent, returnFalse, returnZero } from "../../../Utils";
-import { runInAction, observable, reaction, IReactionDisposer, computed, action } from "mobx";
+import { runInAction, observable, reaction, IReactionDisposer, computed, action, trace, toJS } from "mobx";
import { DateField } from "../../../fields/DateField";
import { SelectionManager } from "../../util/SelectionManager";
import { Doc, DocListCast } from "../../../fields/Doc";
@@ -18,9 +18,17 @@ import { Id } from "../../../fields/FieldSymbols";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
import { DocumentView } from "./DocumentView";
import { Docs, DocUtils } from "../../documents/Documents";
-import { ComputedField } from "../../../fields/ScriptField";
+import { ComputedField, ScriptField } from "../../../fields/ScriptField";
import { Networking } from "../../Network";
import { LinkAnchorBox } from "./LinkAnchorBox";
+import { FormattedTextBox } from "./formattedText/FormattedTextBox";
+import { RichTextField } from "../../../fields/RichTextField";
+import { AudioResizer } from "./AudioResizer";
+import { List } from "../../../fields/List";
+import { LabelBox } from "./LabelBox";
+import { Transform } from "../../util/Transform";
+import { Scripting } from "../../util/Scripting";
+
// testing testing
@@ -40,19 +48,37 @@ type AudioDocument = makeInterface<[typeof documentSchema, typeof audioSchema]>;
const AudioDocument = makeInterface(documentSchema, audioSchema);
@observer
-export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument>(AudioDocument) {
+export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
public static Enabled = false;
+ static Instance: AudioBox;
+
_linkPlayDisposer: IReactionDisposer | undefined;
_reactionDisposer: IReactionDisposer | undefined;
_scrubbingDisposer: IReactionDisposer | undefined;
_ele: HTMLAudioElement | null = null;
_recorder: any;
_recordStart = 0;
+ _pauseStart = 0;
+ _pauseEnd = 0;
+ _pausedTime = 0;
_stream: MediaStream | undefined;
+ _start: number = 0;
+ _hold: boolean = false;
+ _left: boolean = false;
+ _amount: number = 1;
+ private _isPointerDown = false;
+ private _currMarker: any;
+
+ @observable private _dragging: boolean = false;
+ @observable private _duration = 0;
+ @observable private _rect: Array<any> = [];
+ @observable private _markers: Array<any> = [];
+ @observable private _paused: boolean = false;
@observable private static _scrubTime = 0;
+ @observable private _repeat: boolean = false;
@computed get audioState(): undefined | "recording" | "paused" | "playing" { return this.dataDoc.audioState as (undefined | "recording" | "paused" | "playing"); }
set audioState(value) { this.dataDoc.audioState = value; }
public static SetScrubTime = (timeInMillisFrom1970: number) => { runInAction(() => AudioBox._scrubTime = 0); runInAction(() => AudioBox._scrubTime = timeInMillisFrom1970); };
@@ -66,6 +92,9 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
this._scrubbingDisposer?.();
}
componentDidMount() {
+ if (!this.dataDoc.markerAmount) {
+ this.dataDoc.markerAmount = 0;
+ }
runInAction(() => this.audioState = this.path ? "paused" : undefined);
this._linkPlayDisposer = reaction(() => this.layoutDoc.scrollToLinkID,
scrollLinkId => {
@@ -106,14 +135,23 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
}
pause = action(() => {
- this._ele!.pause();
- this.audioState = "paused";
+ if (this._repeat) {
+ this.playFrom(0);
+ } else {
+ this._ele!.pause();
+ this.audioState = "paused";
+ }
});
playFromTime = (absoluteTime: number) => {
this.recordingStart && this.playFrom((absoluteTime - this.recordingStart) / 1000);
}
- playFrom = (seekTimeInSeconds: number) => {
+
+ @action
+ playFrom = (seekTimeInSeconds: number, endTime: number = this.dataDoc.duration) => {
+ let play;
+ clearTimeout(play);
+ this._duration = endTime - seekTimeInSeconds;
if (this._ele && AudioBox.Enabled) {
if (seekTimeInSeconds < 0) {
if (seekTimeInSeconds > -1) {
@@ -122,9 +160,13 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
this.pause();
}
} else if (seekTimeInSeconds <= this._ele.duration) {
+ console.log("playing");
this._ele.currentTime = seekTimeInSeconds;
this._ele.play();
runInAction(() => this.audioState = "playing");
+ if (endTime !== this.dataDoc.duration) {
+ play = setTimeout(() => this.pause(), (this._duration) * 1000);
+ }
} else {
this.pause();
}
@@ -134,8 +176,13 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
updateRecordTime = () => {
if (this.audioState === "recording") {
- setTimeout(this.updateRecordTime, 30);
- this.layoutDoc.currentTimecode = (new Date().getTime() - this._recordStart) / 1000;
+ if (this._paused) {
+ setTimeout(this.updateRecordTime, 30);
+ this._pausedTime += (new Date().getTime() - this._recordStart) / 1000;
+ } else {
+ setTimeout(this.updateRecordTime, 30);
+ this.layoutDoc.currentTimecode = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
+ }
}
}
@@ -167,7 +214,7 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
stopRecording = action(() => {
this._recorder.stop();
this._recorder = undefined;
- this.dataDoc.duration = (new Date().getTime() - this._recordStart) / 1000;
+ this.dataDoc.duration = (new Date().getTime() - this._recordStart - this.pauseTime) / 1000;
this.audioState = "paused";
this._stream?.getAudioTracks()[0].stop();
const ind = DocUtils.ActiveRecordings.indexOf(this.props.Document);
@@ -222,7 +269,197 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
</audio>;
}
+ @action
+ onRepeat = (e: React.MouseEvent) => {
+ this._repeat = !this._repeat;
+ e.stopPropagation();
+ }
+
+ @action
+ recordPause = (e: React.MouseEvent) => {
+ this._pauseStart = new Date().getTime();
+ this._paused = true;
+ this._recorder.pause();
+ e.stopPropagation();
+
+ }
+
+ @action
+ recordPlay = (e: React.MouseEvent) => {
+ this._pauseEnd = new Date().getTime();
+ this._paused = false;
+ this._recorder.resume();
+ e.stopPropagation();
+
+ }
+
+ @computed get pauseTime() {
+ return (this._pauseEnd - this._pauseStart);
+ }
+
+ @action
+ newMarker(marker: Doc) {
+ this._markers.push(marker);
+ if (this.dataDoc.markers) {
+ this.dataDoc.markers.push(marker);
+ } else {
+ this.dataDoc.markers = new List<Doc>(this._markers)
+ }
+
+ console.log(this.dataDoc.markers)
+ this._amount++;
+ }
+
+ start(marker: number) {
+ console.log("start!");
+ this._hold = true;
+ this._start = marker;
+ }
+
+ @action
+ end(marker: number) {
+ console.log("end!");
+ this._hold = false;
+ //this._markers.push(Docs.Create.LabelDocument({ isLabel: false, audioStart: this._start, audioEnd: marker, _showSidebar: false, _autoHeight: true, annotationOn: this.props.Document }))
+ if (this.dataDoc[this.annotationKey]) {
+ this.dataDoc[this.annotationKey].push(Docs.Create.LabelDocument({ title: "hi", isLabel: false, audioStart: this._start, audioEnd: marker, _showSidebar: false, _autoHeight: true, annotationOn: this.props.Document }));
+ } else {
+ this.dataDoc[this.annotationKey] = new List<Doc>([Docs.Create.LabelDocument({ title: "hi", isLabel: false, audioStart: this._start, audioEnd: marker, _showSidebar: false, _autoHeight: true, annotationOn: this.props.Document })]);
+ }
+
+ this._start = 0;
+ this._amount++;
+ }
+
+ onPointerDown = (e: React.PointerEvent, m: any, left: boolean): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isPointerDown = true;
+ console.log("click");
+ this._currMarker = m;
+ const targetele = document.getElementById("timeline");
+ targetele?.setPointerCapture(e.pointerId);
+ this._left = left;
+
+
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ @action
+ onPointerUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isPointerDown = false;
+ this._dragging = false;
+
+ const rect = (e.target as any).getBoundingClientRect();
+ this._ele!.currentTime = this.layoutDoc.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
+
+ const targetele = document.getElementById("timeline");
+ targetele?.releasePointerCapture(e.pointerId);
+
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ onPointerMove = async (e: PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ console.log("drag");
+
+ if (!this._isPointerDown) {
+ return;
+ }
+
+ const rect = await (e.target as any).getBoundingClientRect();
+
+ // if (e.target as HTMLElement === document.getElementById("timeline")) {
+
+ let newTime = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
+
+ this.changeMarker(this._currMarker, newTime);
+ // }
+ }
+
+ @action
+ changeMarker = (m: any, time: any) => {
+ for (let i = 0; i < this.dataDoc.markers.length; i++) {
+ if (this.isSame(this.dataDoc.markers[i], m)) {
+ // this._left ? this._markers[i][0] = time : this._markers[i][1] = time;
+ this._left ? this.dataDoc.markers[i].audioStart = time : this.dataDoc.markers[i].audioEnd = time;
+ }
+ }
+ }
+
+ isSame = (m1: any, m2: any) => {
+ if (m1.audioStart === m2.audioStart && m1.audioEnd === m2.audioEnd) {
+ return true;
+ }
+ return false;
+ }
+
+ @action
+ isOverlap = (m: any, i: number) => {
+ let counter = 0;
+ let check = [];
+
+ if (i == 0) {
+ this._markers = [];
+ }
+ for (let marker of this._markers) {
+ if ((m.audioEnd > marker.audioStart && m.audioStart < marker.audioEnd)) {
+ counter++;
+ check.push(marker)
+ }
+ }
+
+
+ if (this.dataDoc.markerAmount < counter) {
+ this.dataDoc.markerAmount = counter;
+ }
+
+ this._markers.push(m);
+
+ return counter;
+ }
+
+ formatTime = (time: number) => {
+ const hours = Math.floor(time / 60 / 60);
+ const minutes = Math.floor(time / 60) - (hours * 60);
+ const seconds = time % 60;
+
+ return hours.toString().padStart(2, '0') + ':' + minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0');
+ }
+
+ @action
+ onHover = () => {
+ this._dragging = true;
+ }
+
+ @action
+ onLeave = () => {
+ this._dragging = false;
+ }
+ // onMouseOver={this.onHover} onMouseLeave={this.onLeave}
+
+ change = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ const rect = (e.target as any).getBoundingClientRect();
+
+ const wasPaused = this.audioState === "paused";
+ this._ele!.currentTime = this.layoutDoc.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
+ wasPaused && this.pause();
+ console.log("double!");
+ }
+
+ // see if time is encapsulated by comparing time on both sides (for moving onto a new row in the timeline for the markers)
+
render() {
+ trace();
const interactive = this.active() ? "-interactive" : "";
return <div className={`audiobox-container`} onContextMenu={this.specificContextMenu} onClick={!this.path ? this.recordClick : undefined}>
{!this.path ?
@@ -230,24 +467,99 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
<div className="audiobox-dictation" onClick={this.onFile}>
<FontAwesomeIcon style={{ width: "30px", background: this.layoutDoc.playOnSelect ? "yellow" : "rgba(0,0,0,0)" }} icon="file-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
</div>
- <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this.audioState === "recording" ? "red" : "black" }}>
- {this.audioState === "recording" ? "STOP" : "RECORD"}
- </button>
+ {/* <button className={`audiobox-record${interactive}`} style={{ backgroundColor: this.audioState === "recording" ? "lightgrey" : "black" }}>
+ {this.audioState === "recording" ?
+ <div className="recording" style={{}}>
+ 10:00
+ <FontAwesomeIcon style={{ width: "100%" }} icon={"stop-circle"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+ <FontAwesomeIcon style={{ width: "100%" }} icon={"pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /> </div> : "RECORD"}
+ </button> */}
+ {this.audioState === "recording" ?
+ <div className="recording" onClick={e => e.stopPropagation()}>
+ <div className="buttons" onClick={this.recordClick}>
+ <FontAwesomeIcon style={{ width: "100%" }} icon={"stop"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+ </div>
+ <div className="buttons" onClick={this._paused ? this.recordPlay : this.recordPause}>
+ <FontAwesomeIcon style={{ width: "100%" }} icon={this._paused ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} />
+ </div>
+ <div className="time">{this.formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}</div>
+ </div>
+
+ :
+ <button className={`audiobox-record${interactive}`} style={{ backgroundColor: "black" }}>
+ RECORD
+ </button>}
</div> :
- <div className="audiobox-controls">
- <div className="audiobox-player" onClick={this.onPlay}>
- <div className="audiobox-playhead"> <FontAwesomeIcon style={{ width: "100%" }} icon={this.audioState === "paused" ? "play" : "pause"} size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
- <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%", background: this.layoutDoc.playOnSelect ? "yellow" : "dimGray" }} icon="hand-point-left" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
- <div className="audiobox-timeline" onClick={e => e.stopPropagation()}
+ <div className="audiobox-controls" onClick={this.layoutDoc.playOnSelect ? this.onPlay : undefined}>
+ <div className="background">
+ </div>
+ <div className="audiobox-player" >
+ <div className="audiobox-playhead" title={this.audioState === "paused" ? "play" : "pause"} onClick={this.onPlay}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px" }} icon={this.audioState === "paused" ? "play" : "pause"} size={"1x"} /></div>
+ {/* <div className="audiobox-playhead" onClick={this.onStop}><FontAwesomeIcon style={{ width: "100%", background: this.layoutDoc.playOnSelect ? "darkgrey" : "" }} icon="hand-point-left" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div>
+ <div className="audiobox-playhead" onClick={this.onRepeat}><FontAwesomeIcon style={{ width: "100%", background: this._repeat ? "darkgrey" : "" }} icon="redo-alt" size={this.props.PanelHeight() < 36 ? "1x" : "2x"} /></div> */}
+ <div className="audiobox-timeline" id="timeline" onClick={e => { e.stopPropagation(); e.preventDefault(); }} onDoubleClick={e => this.change}
onPointerDown={e => {
+ e.stopPropagation();
+ e.preventDefault();
if (e.button === 0 && !e.ctrlKey) {
const rect = (e.target as any).getBoundingClientRect();
- const wasPaused = this.audioState === "paused";
+
+ if (e.target as HTMLElement !== document.getElementById("current")) {
+ const wasPaused = this.audioState === "paused";
+ this._ele!.currentTime = this.layoutDoc.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
+ wasPaused && this.pause();
+ }
+ }
+ if (e.button === 0 && e.altKey) {
+
+ this.newMarker(Docs.Create.TextDocument("", { isLabel: true, audioStart: this._ele!.currentTime }));
+ }
+
+ if (e.button === 0 && e.shiftKey) {
+ const rect = (e.target as any).getBoundingClientRect();
this._ele!.currentTime = this.layoutDoc.currentTimecode = (e.clientX - rect.x) / rect.width * NumCast(this.dataDoc.duration);
- wasPaused && this.pause();
- e.stopPropagation();
+ this._hold ? this.end(this._ele!.currentTime) : this.start(this._ele!.currentTime);
}
- }} >
+ }}>
+ {DocListCast(this.dataDoc[this.annotationKey]).map((m, i) => {
+
+ // let text = Docs.Create.TextDocument("hello", { title: "label", _showSidebar: false, _autoHeight: false });
+ let rect;
+
+ (!m.isLabel) ?
+
+ rect =
+ <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container1"} title={`${this.formatTime(Math.round(NumCast(m.audioStart)))}` + " - " + `${this.formatTime(Math.round(NumCast(m.audioEnd)))}`} key={i} id={"audiobox-marker-container1"} style={{ left: `${NumCast(m.audioStart) / NumCast(this.dataDoc.duration, 1) * 100}%`, width: `${(NumCast(m.audioEnd) - NumCast(m.audioStart)) / NumCast(this.dataDoc.duration, 1) * 100}%`, height: `${1 / (this.dataDoc.markerAmount + 2) * 100}%`, top: `${this.isOverlap(m, i) * 1 / (this.dataDoc.markerAmount + 2) * 100}%` }} onClick={e => { this.playFrom(NumCast(m.audioStart), NumCast(m.audioEnd)); e.stopPropagation() }} >
+ {/* <FormattedTextBox {...this.props} key={"label" + i} /> */}
+ <div className="left-resizer" onPointerDown={e => this.onPointerDown(e, m, true)}></div>
+ <DocumentView {...this.props}
+ Document={m}
+ pointerEvents={true}
+ NativeHeight={returnZero}
+ NativeWidth={returnZero}
+ rootSelected={returnFalse}
+ LayoutTemplate={undefined}
+ ContainingCollectionDoc={this.props.Document}
+ dontRegisterView={true}
+ removeDocument={undefined}
+ parentActive={returnTrue}
+ onClick={() => ScriptCast(ScriptField.MakeScript("this.playFrom((NumCast(m.audioStart), NumCast(m.audioEnd)))"))}
+ ignoreAutoHeight={false}
+ ScreenToLocalTransform={Transform.Identity}
+ bringToFront={emptyFunction}
+ backgroundColor={returnTransparent} />
+ {/* <LabelBox {... this.props} Document={m} /> */}
+ <div className="resizer" onPointerDown={e => this.onPointerDown(e, m, false)}></div>
+ </div>
+ :
+ rect =
+ <div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={i} style={{ left: `${NumCast(m.audioStart) / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
+ {/* <DocumentView {...this.props}
+ Document={text}
+ parentActive={returnTrue} /> */}
+ </div>;
+ return rect;
+ })}
{DocListCast(this.dataDoc.links).map((l, i) => {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
@@ -257,6 +569,8 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
la2 = l.anchor1 as Doc;
linkTime = NumCast(l.anchor1_timecode);
}
+
+
return !linkTime ? (null) :
<div className={this.props.PanelHeight() < 32 ? "audiobox-marker-minicontainer" : "audiobox-marker-container"} key={l[Id]} style={{ left: `${linkTime / NumCast(this.dataDoc.duration, 1) * 100}%` }}>
<div className={this.props.PanelHeight() < 32 ? "audioBox-linker-mini" : "audioBox-linker"} key={"linker" + i}>
@@ -277,12 +591,21 @@ export class AudioBox extends ViewBoxBaseComponent<FieldViewProps, AudioDocument
onPointerDown={e => { if (e.button === 0 && !e.ctrlKey) { const wasPaused = this.audioState === "paused"; this.playFrom(linkTime); wasPaused && this.pause(); e.stopPropagation(); } }} />
</div>;
})}
- <div className="audiobox-current" style={{ left: `${NumCast(this.layoutDoc.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
+ <div className="audiobox-current" id="current" onClick={e => { e.stopPropagation(); e.preventDefault(); }} style={{ left: `${NumCast(this.layoutDoc.currentTimecode) / NumCast(this.dataDoc.duration, 1) * 100}%` }} />
{this.audio}
+
+ </div>
+ <div className="current-time">
+ {this.formatTime(Math.round(NumCast(this.layoutDoc.currentTimecode)))}
+ </div>
+ <div className="total-time">
+ {this.formatTime(Math.round(NumCast(this.layoutDoc.duration)))}
</div>
</div>
</div>
}
</div>;
}
-} \ No newline at end of file
+}
+
+Scripting.addGlobal(function playFrom(start: number, end: number) { return AudioBox.Instance.playFrom(start, end); }) \ No newline at end of file
diff --git a/src/client/views/nodes/AudioResizer.scss b/src/client/views/nodes/AudioResizer.scss
new file mode 100644
index 000000000..892ad21e7
--- /dev/null
+++ b/src/client/views/nodes/AudioResizer.scss
@@ -0,0 +1,11 @@
+.resizer {
+ width: 0px;
+ height: 100%;
+ position: absolute;
+ right: 0px;
+ z-index: 999;
+ cursor: e-resize;
+ content: " ";
+ display: inline-block;
+ border-left: 20px solid transparent;
+} \ No newline at end of file
diff --git a/src/client/views/nodes/AudioResizer.tsx b/src/client/views/nodes/AudioResizer.tsx
new file mode 100644
index 000000000..f9ab8353f
--- /dev/null
+++ b/src/client/views/nodes/AudioResizer.tsx
@@ -0,0 +1,48 @@
+import { observer } from "mobx-react"
+import React = require("react");
+import "./AudioResizer.scss";
+
+@observer
+export class AudioResizer extends React.Component {
+ private _isPointerDown = false;
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isPointerDown = true;
+ console.log("click");
+
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+
+ onPointerUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ this._isPointerDown = false;
+
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+
+ onPointerMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ console.log("drag");
+
+ if (!this._isPointerDown) {
+ return;
+ }
+
+ let resize = document.getElementById("resizer");
+ if (resize) {
+ resize.style.right += e.movementX;
+ }
+ }
+
+ render() {
+ return <div className="resizer" onPointerDown={this.onPointerDown}></div>
+ }
+} \ No newline at end of file
diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts
index ddffb56c3..c77d80d76 100644
--- a/src/fields/documentSchemas.ts
+++ b/src/fields/documentSchemas.ts
@@ -19,6 +19,10 @@ export const documentSchema = createSchema({
currentTimecode: "number", // current play back time of a temporal document (video / audio)
displayTimecode: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video)
inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently
+ isLabel: "boolean", // whether the document is a label or not (video / audio)
+ audioStart: "number", // the time frame where the audio should begin playing
+ audioEnd: "number", // the time frame where the audio should stop playing
+ markers: listSpec(Doc), // list of markers for audio / video
x: "number", // x coordinate when in a freeform view
y: "number", // y coordinate when in a freeform view
z: "number", // z "coordinate" - non-zero specifies the overlay layer of a freeformview