aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx49
-rw-r--r--src/client/views/nodes/AudioBox.scss121
-rw-r--r--src/client/views/nodes/AudioBox.tsx129
-rw-r--r--src/client/views/nodes/VideoBox.tsx10
4 files changed, 202 insertions, 107 deletions
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 89da6692a..9577256c9 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -354,6 +354,13 @@ export class CollectionStackedTimeline extends CollectionSubView<
// determine x coordinate of drop and assign it to the documents being dragged --- see internalDocDrop of collectionFreeFormView.tsx for how it's done when dropping onto a 2D freeform view
+ const x = docDragData.offset[0];
+ const timelineContentWidth = this.props.PanelWidth();
+ for (let i = 0; i < docDragData.droppedDocuments.length; i++) {
+ const d = docDragData.droppedDocuments[i];
+ d._timecodeToShow = x / timelineContentWidth * this.props.trimDuration + NumCast(d._timecodeToShow);
+ d._timecodeToHide = x / timelineContentWidth * this.props.trimDuration + NumCast(d._timecodeToHide);
+ }
return true;
}
@@ -573,11 +580,15 @@ export class CollectionStackedTimeline extends CollectionSubView<
);
const left = this.props.trimming ?
(start / this.duration) * timelineContentWidth
- : (start - this.trimStart) / this.props.trimDuration * timelineContentWidth;
- const top = (d.level / maxLevel) * this.timelineContentHeight();
+ : Math.max((start - this.trimStart) / this.props.trimDuration * timelineContentWidth, 0);
+ const top = (d.level / maxLevel) * this.timelineContentHeight() + 15;
const timespan = end - start;
- const width = (timespan / this.props.trimDuration) * timelineContentWidth;
- const height = this.timelineContentHeight() / maxLevel;
+ let width = (timespan / this.props.trimDuration) * timelineContentWidth;
+ width = (!this.props.trimming && left == 0) ?
+ width - ((this.trimStart - start) / this.props.trimDuration * timelineContentWidth) : width;
+ width = (!this.props.trimming && this.trimEnd < end) ?
+ width - ((end - this.trimEnd) / this.props.trimDuration * timelineContentWidth) : width;
+ const height = (this.timelineContentHeight()) / maxLevel;
return this.props.Document.hideAnchors ? null : (
<div
className={"collectionStackedTimeline-marker-timeline"}
@@ -851,21 +862,21 @@ class StackedTimelineAnchor extends React.Component<StackedTimelineAnchorProps>
{inner.view}
{!inner.anchor.view ||
!SelectionManager.IsSelected(inner.anchor.view) ? null : (
- <>
- <div
- key="left"
- className="collectionStackedTimeline-left-resizer"
- onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
- />
- <div
- key="right"
- className="collectionStackedTimeline-resizer"
- onPointerDown={(e) =>
- this.onAnchorDown(e, this.props.mark, false)
- }
- />
- </>
- )}
+ <>
+ <div
+ key="left"
+ className="collectionStackedTimeline-left-resizer"
+ onPointerDown={(e) => this.onAnchorDown(e, this.props.mark, true)}
+ />
+ <div
+ key="right"
+ className="collectionStackedTimeline-resizer"
+ onPointerDown={(e) =>
+ this.onAnchorDown(e, this.props.mark, false)
+ }
+ />
+ </>
+ )}
</>
);
}
diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss
index a6494e540..6adda4730 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -3,13 +3,100 @@
.audiobox-container,
.audiobox-container-interactive {
+ width: 100%;
+ height: 100%;
+ position: inherit;
+ display: flex;
+ position: relative;
+ cursor: default;
+
+ .audiobox-buttons {
+ display: flex;
+ width: 100%;
+ align-items: center;
+
+ .audiobox-dictation {
+ position: relative;
+ width: 30px;
+ height: 100%;
+ align-items: center;
+ display: inherit;
+ background: $medium-gray;
+ left: 0px;
+ color: $dark-gray;
+ &:hover {
+ color: $black;
+ cursor: pointer;
+ }
+ }
+ }
+
+ .audiobox-control,
+ .audiobox-control-interactive {
+ top: 0;
+ max-height: 32px;
+ width: 100%;
+ display: inline-block;
+ pointer-events: none;
+ }
+
+ .audiobox-control-interactive {
+ pointer-events: all;
+ }
+
+ .audiobox-record-interactive,
+ .audiobox-record {
+ pointer-events: all;
+ cursor: pointer;
+ width: 100%;
+ height: 100%;
+ position: relative;
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ color: white;
+ font-weight: bold;
+ }
+
+ .audiobox-record {
+ pointer-events: none;
+ }
+
+ .recording {
+ margin-top: auto;
+ margin-bottom: auto;
width: 100%;
height: 100%;
- position: inherit;
- display: flex;
position: relative;
- cursor: default;
+ padding-right: 5px;
+ display: flex;
+ background-color: $medium-blue;
+
+ .time {
+ position: relative;
+ width: 100%;
+ font-size: $large-header;
+ text-align: center;
+ }
+
+ .recording-buttons {
+ position: relative;
+ margin-top: auto;
+ margin-bottom: auto;
+ color: $dark-gray;
+ &:hover {
+ color: $black;
+ }
+ }
+ .time, .recording-buttons {
+ display: flex;
+ align-items: center;
+ padding: 5px;
+ }
+ }
.audiobox-buttons {
display: flex;
width: 100%;
@@ -46,26 +133,6 @@
pointer-events: all;
}
- .audiobox-record-interactive,
- .audiobox-record {
- pointer-events: all;
- cursor: pointer;
- width: 100%;
- height: 100%;
- position: relative;
- display: flex;
- flex-direction: row;
- align-items: center;
- justify-content: center;
- gap: 10px;
- color: white;
- font-weight: bold;
- }
-
- .audiobox-record {
- pointer-events: none;
- }
-
.recording {
margin-top: auto;
margin-bottom: auto;
@@ -195,6 +262,12 @@
.audioBox-total-time {
right: 2px;
}
+
+ .audiobox-zoom {
+ bottom: 0;
+ left: 30px;
+ width: 70px;
+ }
}
}
}
@@ -219,4 +292,4 @@
.audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-buttons {
width: 70px;
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index c79828470..830c73278 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -33,6 +33,7 @@ import { FieldView, FieldViewProps } from "./FieldView";
import { LinkDocPreview } from "./LinkDocPreview";
import { faLessThan } from "@fortawesome/free-solid-svg-icons";
import { Colors } from "../global/globalEnums";
+import e = require("connect-flash");
declare class MediaRecorder {
constructor(e: any); // whatever MediaRecorder has
@@ -336,8 +337,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
(this.layoutDoc.dontAutoPlayFollowedLinks ? "" : "Don't") +
" play when link is selected",
event: () =>
- (this.layoutDoc.dontAutoPlayFollowedLinks =
- !this.layoutDoc.dontAutoPlayFollowedLinks),
+ (this.layoutDoc.dontAutoPlayFollowedLinks =
+ !this.layoutDoc.dontAutoPlayFollowedLinks),
icon: "expand-arrows-alt",
});
funcs.push({
@@ -637,77 +638,77 @@ export class AudioBox extends ViewBoxAnnotatableComponent<
</div>
</div>
) : (
- <div
- className={`audiobox-record${interactive}`}
- style={{ backgroundColor: Colors.DARK_GRAY }}
- >
- <FontAwesomeIcon icon="microphone" />
+ <div
+ className={`audiobox-record${interactive}`}
+ style={{ backgroundColor: Colors.DARK_GRAY }}
+ >
+ <FontAwesomeIcon icon="microphone" />
RECORD
- </div>
- )}
+ </div>
+ )}
</div>
) : (
+ <div
+ className="audiobox-controls"
+ style={{
+ pointerEvents:
+ this._isAnyChildContentActive || this.props.isContentActive()
+ ? "all"
+ : "none",
+ }}
+ >
+ <div className="audiobox-dictation" />
<div
- className="audiobox-controls"
- style={{
- pointerEvents:
- this._isAnyChildContentActive || this.props.isContentActive()
- ? "all"
- : "none",
- }}
+ className="audiobox-player"
+ style={{ height: `${AudioBox.heightPercent}%` }}
>
- <div className="audiobox-dictation" />
<div
- className="audiobox-player"
- style={{ height: `${AudioBox.heightPercent}%` }}
+ className="audiobox-buttons"
+ title={this.mediaState === "paused" ? "play" : "pause"}
+ onClick={this.mediaState === "paused" ? this.Play : this.Pause}
>
- <div
- className="audiobox-buttons"
- title={this.mediaState === "paused" ? "play" : "pause"}
- onClick={this.mediaState === "paused" ? this.Play : this.Pause}
- >
- {" "}
- <FontAwesomeIcon
- icon={this.mediaState === "paused" ? "play" : "pause"}
- size={"1x"}
- />
- </div>
- <div
- className="audiobox-buttons"
- title={this._trimming ? "finish" : "trim"}
- onClick={this._trimming ? this.finishTrim : this.startTrim}
- >
- <FontAwesomeIcon
- icon={this._trimming ? "check" : "cut"}
- size={"1x"}
- />
- </div>
- <div
- className="audiobox-timeline"
- style={{
- top: 0,
- height: `100%`,
- left: AudioBox.playheadWidth,
- width: `calc(100% - ${AudioBox.playheadWidth}px)`,
- background: "white",
- }}
- >
- {this.renderTimeline}
- </div>
- {this.audio}
- <div className="audioBox-current-time">
- {this._trimming ?
- formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))
- : formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this._trimStart)))}
- </div>
- <div className="audioBox-total-time">
- {this._trimming || !this._trimEnd ?
- formatTime(Math.round(NumCast(this.duration)))
- : formatTime(Math.round(NumCast(this.trimDuration)))}
- </div>
+ {" "}
+ <FontAwesomeIcon
+ icon={this.mediaState === "paused" ? "play" : "pause"}
+ size={"1x"}
+ />
+ </div>
+ <div
+ className="audiobox-buttons"
+ title={this._trimming ? "finish" : "trim"}
+ onClick={this._trimming ? this.finishTrim : this.startTrim}
+ >
+ <FontAwesomeIcon
+ icon={this._trimming ? "check" : "cut"}
+ size={"1x"}
+ />
+ </div>
+ <div
+ className="audiobox-timeline"
+ style={{
+ top: 0,
+ height: `100%`,
+ left: AudioBox.playheadWidth,
+ width: `calc(100% - ${AudioBox.playheadWidth}px)`,
+ background: "white",
+ }}
+ >
+ {this.renderTimeline}
+ </div>
+ {this.audio}
+ <div className="audioBox-current-time">
+ {this._trimming ?
+ formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))
+ : formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode) - NumCast(this._trimStart)))}
+ </div>
+ <div className="audioBox-total-time">
+ {this._trimming || !this._trimEnd ?
+ formatTime(Math.round(NumCast(this.duration)))
+ : formatTime(Math.round(NumCast(this.trimDuration)))}
</div>
</div>
- )}
+ </div>
+ )}
</div>
);
}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index e3ba638b3..3fc460102 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -61,9 +61,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@observable _playTimer?: NodeJS.Timeout = undefined;
@observable _fullScreen = false;
@observable _playing = false;
+ @observable _trimming: boolean = false;
+ @observable _trimStart: number = NumCast(this.layoutDoc.clipStart) ? NumCast(this.layoutDoc.clipStart) : 0;
+ @observable _trimEnd: number = NumCast(this.layoutDoc.clipEnd) ? NumCast(this.layoutDoc.clipEnd)
+ : this.duration;
+
@computed get links() { return DocListCast(this.dataDoc.links); }
@computed get heightPercent() { return NumCast(this.layoutDoc._timelineHeightPercent, 100); }
@computed get duration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); }
+ @computed get trimDuration() {
+ return this._trimming && this._trimEnd ? this.duration : this._trimEnd - this._trimStart;
+ }
private get transition() { return this._clicking ? "left 0.5s, width 0.5s, height 0.5s" : ""; }
public get player(): HTMLVideoElement | null { return this._videoRef; }
@@ -202,6 +210,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent<ViewBoxAnnotatableProp
@action
updateTimecode = () => {
this.player && (this.layoutDoc._currentTimecode = this.player.currentTime);
+ this.layoutDoc.clipEnd = this.layoutDoc.clipEnd ? Math.min(this.duration, NumCast(this.layoutDoc.clipEnd)) : this.duration;
+ this._trimEnd = this._trimEnd ? Math.min(this.duration, this._trimEnd) : this.duration;
try {
this._youtubePlayer && (this.layoutDoc._currentTimecode = this._youtubePlayer.getCurrentTime?.());
} catch (e) {