aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/client/util/DictationManager.ts2
-rw-r--r--src/client/util/DragManager.ts2
-rw-r--r--src/client/util/LinkManager.ts20
-rw-r--r--src/client/views/AudioWaveform.scss17
-rw-r--r--src/client/views/AudioWaveform.tsx61
-rw-r--r--src/client/views/DocumentDecorations.tsx2
-rw-r--r--src/client/views/SidebarAnnos.tsx2
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.scss7
-rw-r--r--src/client/views/collections/CollectionStackedTimeline.tsx80
-rw-r--r--src/client/views/nodes/AudioBox.scss18
-rw-r--r--src/client/views/nodes/AudioBox.tsx41
-rw-r--r--src/client/views/nodes/DocumentView.tsx8
-rw-r--r--src/client/views/nodes/ScreenshotBox.tsx2
-rw-r--r--src/client/views/nodes/VideoBox.tsx9
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx2
-rw-r--r--src/server/DashUploadUtils.ts5
16 files changed, 165 insertions, 113 deletions
diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts
index f00cdce1e..a93b2f573 100644
--- a/src/client/util/DictationManager.ts
+++ b/src/client/util/DictationManager.ts
@@ -89,7 +89,7 @@ export namespace DictationManager {
export const listen = async (options?: Partial<ListeningOptions>) => {
if (pendingListen instanceof Promise) return pendingListen.then(pl => innerListen(options));
return innerListen(options);
- }
+ };
const innerListen = async (options?: Partial<ListeningOptions>) => {
let results: string | undefined;
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 1809a77bf..38d0ecaa6 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -388,7 +388,7 @@ export namespace DragManager {
if (dragElement !== ele) {
const children = [Array.from(ele.children), Array.from(dragElement.children)];
while (children[0].length) {
- const childs = [children[0].pop(), children[1].pop()]
+ const childs = [children[0].pop(), children[1].pop()];
if (childs[0]?.children) {
children[0].push(...Array.from(childs[0].children));
children[1].push(...Array.from(childs[1]!.children));
diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts
index 159011516..3c3d5c3b8 100644
--- a/src/client/util/LinkManager.ts
+++ b/src/client/util/LinkManager.ts
@@ -33,7 +33,7 @@ export class LinkManager {
constructor() {
LinkManager._instance = this;
setTimeout(() => {
- LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase as Doc)];
+ LinkManager.userDocs = [Doc.LinkDBDoc().data as Doc, ...SharingManager.Instance.users.map(user => user.linkDatabase)];
const addLinkToDoc = (link: Doc): any => {
const a1 = link?.anchor1;
const a2 = link?.anchor2;
@@ -43,7 +43,7 @@ export class LinkManager {
Doc.GetProto(a2)[DirectLinksSym].add(link);
Doc.GetProto(link)[DirectLinksSym].add(link);
}
- }
+ };
const remLinkFromDoc = (link: Doc): any => {
const a1 = link?.anchor1;
const a2 = link?.anchor2;
@@ -53,23 +53,23 @@ export class LinkManager {
Doc.GetProto(a2)[DirectLinksSym].delete(link);
Doc.GetProto(link)[DirectLinksSym].delete(link);
}
- }
+ };
const watchUserLinks = (userLinks: List<Doc>) => {
const toRealField = (field: Field) => field instanceof ProxyField ? field.value() : field; // see List.ts. data structure is not a simple list of Docs, but a list of ProxyField/Fields
observe(userLinks, change => {
- switch (change.type) {
+ switch (change.type as any) {
case "splice":
(change as any).added.forEach((link: any) => addLinkToDoc(toRealField(link)));
(change as any).removed.forEach((link: any) => remLinkFromDoc(toRealField(link)));
break;
- case "update": let oldValue = change.oldValue;
+ case "update": //let oldValue = change.oldValue;
}
}, true);
- }
+ };
observe(LinkManager.userDocs, change => {
- switch (change.type) {
+ switch (change.type as any) {
case "splice": (change as any).added.forEach(watchUserLinks); break;
- case "update": let oldValue = change.oldValue;
+ case "update": //let oldValue = change.oldValue;
}
}, true);
});
@@ -82,7 +82,9 @@ export class LinkManager {
public deleteAllLinksOnAnchor(anchor: Doc) { LinkManager.Instance.relatedLinker(anchor).forEach(linkDoc => LinkManager.Instance.deleteLink(linkDoc)); }
public getAllRelatedLinks(anchor: Doc) { return this.relatedLinker(anchor); } // finds all links that contain the given anchor
- public getAllDirectLinks(anchor: Doc): Doc[] { return Array.from(Doc.GetProto(anchor)[DirectLinksSym]); } // finds all links that contain the given anchor
+ public getAllDirectLinks(anchor: Doc): Doc[] {
+ return Array.from(Doc.GetProto(anchor)[DirectLinksSym]);
+ } // finds all links that contain the given anchor
public getAllLinks(): Doc[] { return []; }//this.allLinks(); }
// allLinks = computedFn(function allLinks(this: any): Doc[] {
diff --git a/src/client/views/AudioWaveform.scss b/src/client/views/AudioWaveform.scss
new file mode 100644
index 000000000..e20434a25
--- /dev/null
+++ b/src/client/views/AudioWaveform.scss
@@ -0,0 +1,17 @@
+.audioWaveform {
+ 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;
+ }
+}
diff --git a/src/client/views/AudioWaveform.tsx b/src/client/views/AudioWaveform.tsx
new file mode 100644
index 000000000..7ff9c1071
--- /dev/null
+++ b/src/client/views/AudioWaveform.tsx
@@ -0,0 +1,61 @@
+import React = require("react");
+import axios from "axios";
+import { action, computed } from "mobx";
+import { observer } from "mobx-react";
+import Waveform from "react-audio-waveform";
+import { Doc } from "../../fields/Doc";
+import { List } from "../../fields/List";
+import { listSpec } from "../../fields/Schema";
+import { Cast } from "../../fields/Types";
+import { numberRange } from "../../Utils";
+import "./AudioWaveform.scss";
+
+export interface AudioWaveformProps {
+ duration: number;
+ mediaPath: string;
+ dataDoc: Doc;
+ PanelHeight: () => number;
+}
+
+@observer
+export class AudioWaveform extends React.Component<AudioWaveformProps> {
+ public static NUMBER_OF_BUCKETS = 100;
+ @computed get _waveHeight() { return Math.max(50, this.props.PanelHeight()); }
+ componentDidMount() {
+ const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []);
+ if (!audioBuckets.length) {
+ this.props.dataDoc.audioBuckets = new List<number>([0, 0]); /// "lock" to prevent other views from computing the same data
+ setTimeout(this.createWaveformBuckets);
+ }
+ }
+ // decodes the audio file into peaks for generating the waveform
+ createWaveformBuckets = async () => {
+ axios({ url: this.props.mediaPath, responseType: "arraybuffer" })
+ .then(response => {
+ const context = new window.AudioContext();
+ context.decodeAudioData(response.data,
+ action(buffer => {
+ const decodedAudioData = buffer.getChannelData(0);
+ const bucketDataSize = Math.floor(decodedAudioData.length / AudioWaveform.NUMBER_OF_BUCKETS);
+ const brange = Array.from(Array(bucketDataSize));
+ this.props.dataDoc.audioBuckets = new List<number>(
+ numberRange(AudioWaveform.NUMBER_OF_BUCKETS).map((i: number) =>
+ brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2));
+ }));
+ });
+ }
+
+ render() {
+ const audioBuckets = Cast(this.props.dataDoc.audioBuckets, listSpec("number"), []);
+ return <div className="audioWaveform">
+ <Waveform
+ color={"darkblue"}
+ height={this._waveHeight}
+ barWidth={0.1}
+ pos={this.props.duration}
+ duration={this.props.duration}
+ peaks={audioBuckets.length === AudioWaveform.NUMBER_OF_BUCKETS ? audioBuckets : undefined}
+ progressColor={"blue"} />
+ </div>;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index da50ba4d8..a92891ee5 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -94,7 +94,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b
setupMoveUpEvents(this, e, e => this.onBackgroundMove(true, e), (e) => { }, action((e) => {
!this._edtingTitle && (this._accumulatedTitle = this._titleControlString.startsWith("#") ? this.selectionTitle : this._titleControlString);
this._edtingTitle = true;
- this._keyinput.current && setTimeout(this._keyinput.current!.focus);
+ this._keyinput.current && setTimeout(this._keyinput.current.focus);
}));
}
diff --git a/src/client/views/SidebarAnnos.tsx b/src/client/views/SidebarAnnos.tsx
index 87887483f..6b0b928b3 100644
--- a/src/client/views/SidebarAnnos.tsx
+++ b/src/client/views/SidebarAnnos.tsx
@@ -79,7 +79,7 @@ export class SidebarAnnos extends React.Component<FieldViewProps & ExtraProps> {
sidebarStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps | DocumentViewProps>, property: string) => {
if (property === StyleProp.ShowTitle) return StrCast(this.props.layoutDoc["sidebar-childShowTitle"], "title");
- return this.props.styleProvider?.(doc, props, property)
+ return this.props.styleProvider?.(doc, props, property);
}
render() {
const renderTag = (tag: string) => {
diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss
index cc56831f3..2b9f3d782 100644
--- a/src/client/views/collections/CollectionStackedTimeline.scss
+++ b/src/client/views/collections/CollectionStackedTimeline.scss
@@ -58,4 +58,11 @@
left: 0;
}
}
+
+ .collectionStackedTimeline-waveform {
+ position: absolute;
+ width: 100%;
+ top: 0;
+ left: 0;
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx
index 66b74277b..2191a3df5 100644
--- a/src/client/views/collections/CollectionStackedTimeline.tsx
+++ b/src/client/views/collections/CollectionStackedTimeline.tsx
@@ -1,26 +1,26 @@
import React = require("react");
-import { action, computed, IReactionDisposer, observable, runInAction, reaction } from "mobx";
+import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, Opt, DocListCast } from "../../../fields/Doc";
+import { computedFn } from "mobx-utils";
+import { Doc, DocListCast } from "../../../fields/Doc";
import { Id } from "../../../fields/FieldSymbols";
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, StopEvent, returnOne } from "../../../Utils";
+import { emptyFunction, formatTime, OmitKeys, returnFalse, returnOne, setupMoveUpEvents, StopEvent } from "../../../Utils";
import { Docs } from "../../documents/Documents";
+import { LinkManager } from "../../util/LinkManager";
import { Scripting } from "../../util/Scripting";
import { SelectionManager } from "../../util/SelectionManager";
+import { Transform } from "../../util/Transform";
import { undoBatch } from "../../util/UndoManager";
+import { AudioWaveform } from "../AudioWaveform";
import { CollectionSubView } from "../collections/CollectionSubView";
-import { DocumentView, DocAfterFocusFunc, DocFocusFunc, DocumentViewProps } from "../nodes/DocumentView";
+import { LightboxView } from "../LightboxView";
+import { DocAfterFocusFunc, DocFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView";
import { LabelBox } from "../nodes/LabelBox";
import "./CollectionStackedTimeline.scss";
-import { Transform } from "../../util/Transform";
-import { LinkManager } from "../../util/LinkManager";
-import { computedFn } from "mobx-utils";
-import { LightboxView } from "../LightboxView";
-import { FormattedTextBox } from "../nodes/formattedText/FormattedTextBox";
type PanZoomDocument = makeInterface<[]>;
const PanZoomDocument = makeInterface();
@@ -35,6 +35,7 @@ export type CollectionStackedTimelineProps = {
isChildActive: () => boolean;
startTag: string;
endTag: string;
+ mediaPath: string;
};
@observer
@@ -232,11 +233,42 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
placed.push({ anchorStartTime: x1, anchorEndTime: x2, level });
return level;
}
+
+ dictationHeight = () => this.props.PanelHeight() / 3;
+ timelineContentHeight = () => this.props.PanelHeight() * 2 / 3;
+ @computed get renderDictation() {
+ const dictation = Cast(this.dataDoc[this.props.fieldKey.replace("annotations", "dictation")], Doc, null);
+ return !dictation ? (null) : <div style={{ position: "absolute", height: this.dictationHeight(), top: this.timelineContentHeight(), background: "tan" }}>
+ <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
+ Document={dictation}
+ PanelHeight={this.dictationHeight}
+ isAnnotationOverlay={true}
+ select={emptyFunction}
+ active={returnFalse}
+ scaling={returnOne}
+ xMargin={25}
+ yMargin={10}
+ whenActiveChanged={emptyFunction}
+ removeDocument={returnFalse}
+ moveDocument={returnFalse}
+ addDocument={returnFalse}
+ CollectionView={undefined}
+ renderDepth={this.props.renderDepth + 1}>
+ </DocumentView>
+ </div>;
+ }
+ @computed get renderAudioWaveform() {
+ return !this.props.mediaPath ? (null) : <div style={{ position: "absolute", width: "100%", top: 0, left: 0 }}>
+ <AudioWaveform
+ duration={this.duration}
+ mediaPath={this.props.mediaPath}
+ dataDoc={this.dataDoc}
+ PanelHeight={this.timelineContentHeight} />
+ </div>;
+ }
currentTimecode = () => this.currentTime;
render() {
const timelineContentWidth = this.props.PanelWidth();
- const timelineContentHeight = this.props.PanelHeight() * 2 / 3;
- const dictationHeight = this.props.PanelHeight() / 3;
const overlaps: { anchorStartTime: number, anchorEndTime: number, level: number }[] = [];
const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor }));
const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2;
@@ -247,11 +279,11 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
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 top = d.level / maxLevel * this.timelineContentHeight();
const timespan = end - start;
return this.props.Document.hideAnchors ? (null) :
<div className={"collectionStackedTimeline-marker-timeline"} key={d.anchor[Id]}
- style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${timelineContentHeight / maxLevel}px` }}
+ style={{ left, top, width: `${timespan / this.duration * timelineContentWidth}px`, height: `${this.timelineContentHeight() / maxLevel}px` }}
onClick={e => { this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} >
<StackedTimelineAnchor {...this.props}
mark={d.anchor}
@@ -260,7 +292,7 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
left={left}
top={top}
width={timelineContentWidth * timespan / this.duration}
- height={timelineContentHeight / maxLevel}
+ height={this.timelineContentHeight() / maxLevel}
toTimeline={this.toTimeline}
layoutDoc={this.layoutDoc}
currentTimecode={this.currentTimecode}
@@ -270,24 +302,8 @@ export class CollectionStackedTimeline extends CollectionSubView<PanZoomDocument
</div>;
})}
{this.selectionContainer}
- <div style={{ position: "absolute", height: dictationHeight, top: timelineContentHeight, background: "tan" }}>
- <DocumentView {...OmitKeys(this.props, ["NativeWidth", "NativeHeight", "setContentView"]).omit}
- Document={this.dataDoc[this.props.fieldKey.replace("annotations", "dictation")]}
- PanelHeight={() => dictationHeight}
- isAnnotationOverlay={true}
- select={emptyFunction}
- active={returnFalse}
- scaling={returnOne}
- xMargin={25}
- yMargin={10}
- whenActiveChanged={emptyFunction}
- removeDocument={returnFalse}
- moveDocument={returnFalse}
- addDocument={returnFalse}
- CollectionView={undefined}
- renderDepth={this.props.renderDepth + 1}>
- </DocumentView>
- </div>
+ {this.renderAudioWaveform}
+ {this.renderDictation}
<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 fc881ca25..3fcb024df 100644
--- a/src/client/views/nodes/AudioBox.scss
+++ b/src/client/views/nodes/AudioBox.scss
@@ -144,24 +144,6 @@
border-radius: 3px;
z-index: 1000;
overflow: hidden;
-
- .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-total-time,
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 2f7a6cfd8..06a27c22a 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -1,18 +1,15 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import axios from "axios";
import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx";
import { observer } from "mobx-react";
-import Waveform from "react-audio-waveform";
import { DateField } from "../../../fields/DateField";
import { Doc, DocListCast, Opt } from "../../../fields/Doc";
import { documentSchema } from "../../../fields/documentSchemas";
-import { List } from "../../../fields/List";
-import { createSchema, listSpec, makeInterface } from "../../../fields/Schema";
+import { makeInterface } from "../../../fields/Schema";
import { ComputedField } from "../../../fields/ScriptField";
import { Cast, NumCast } from "../../../fields/Types";
import { AudioField, nullAudio } from "../../../fields/URLField";
-import { emptyFunction, formatTime, numberRange, Utils } from "../../../Utils";
+import { emptyFunction, formatTime, Utils } from "../../../Utils";
import { DocUtils } from "../../documents/Documents";
import { Networking } from "../../Network";
import { CurrentUserUtils } from "../../util/CurrentUserUtils";
@@ -35,7 +32,6 @@ const AudioDocument = makeInterface(documentSchema);
export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioDocument>(AudioDocument) {
public static LayoutString(fieldKey: string) { return FieldView.LayoutString(AudioBox, fieldKey); }
public static Enabled = false;
- public static NUMBER_OF_BUCKETS = 100;
static playheadWidth = 30; // width of playhead
static heightPercent = 80; // height of timeline in percent of height of audioBox.
static Instance: AudioBox;
@@ -295,35 +291,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
e.stopPropagation();
}
- // returns the audio waveform
- @computed get waveform() {
- const audioBuckets = Cast(this.dataDoc.audioBuckets, listSpec("number"), []);
- !audioBuckets.length && setTimeout(() => this.createWaveformBuckets());
- return <Waveform
- color={"darkblue"}
- height={this._waveHeight}
- barWidth={0.1}
- pos={this.duration}
- duration={this.duration}
- peaks={audioBuckets.length === AudioBox.NUMBER_OF_BUCKETS ? audioBuckets : undefined}
- progressColor={"blue"} />;
- }
-
- // decodes the audio file into peaks for generating the waveform
- createWaveformBuckets = async () => {
- axios({ url: this.path, responseType: "arraybuffer" })
- .then(response => (new (window.AudioContext)()).decodeAudioData(response.data,
- action(buffer => {
- const decodedAudioData = buffer.getChannelData(0);
- const bucketDataSize = Math.floor(decodedAudioData.length / AudioBox.NUMBER_OF_BUCKETS);
- const brange = Array.from(Array(bucketDataSize));
- this.dataDoc.audioBuckets = new List<number>(
- numberRange(AudioBox.NUMBER_OF_BUCKETS).map(i =>
- brange.reduce((p, x, j) => Math.abs(Math.max(p, decodedAudioData[i * bucketDataSize + j])), 0) / 2));
- }))
- );
- }
-
playing = () => this.mediaState === "playing";
playLink = (link: Doc) => {
const stack = this._stackedTimeline.current;
@@ -353,6 +320,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
@computed get renderTimeline() {
return <CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
fieldKey={this.annotationKey}
+ mediaPath={this.path}
renderDepth={this.props.renderDepth + 1}
startTag={"_timecodeToShow" /* audioStart */}
endTag={"_timecodeToHide" /* audioEnd */}
@@ -407,9 +375,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
<div className="audiobox-player" style={{ height: `${AudioBox.heightPercent}%` }} >
<div className="audiobox-playhead" style={{ width: AudioBox.playheadWidth }} title={this.mediaState === "paused" ? "play" : "pause"} onClick={this.Play}> <FontAwesomeIcon style={{ width: "100%", position: "absolute", left: "0px", top: "5px", borderWidth: "thin", borderColor: "white" }} icon={this.mediaState === "paused" ? "play" : "pause"} size={"1x"} /></div>
<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.audio}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index df769a407..8a2a755ac 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1102,16 +1102,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
render() {
TraceMobx();
- const xshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined;
- const yshift = this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined;
+ const xshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Xshift) <= 0.001 ? this.props.PanelWidth() : undefined);
+ const yshift = () => (this.props.Document.isInkMask ? InkingStroke.MaskDim : Math.abs(this.Yshift) <= 0.001 ? this.props.PanelHeight() : undefined);
return (<div className="contentFittingDocumentView">
{!this.props.Document || !this.props.PanelWidth() ? (null) : (
<div className="contentFittingDocumentView-previewDoc" ref={this.ContentRef}
style={{
position: this.props.Document.isInkMask ? "absolute" : undefined,
transform: `translate(${this.centeringX}px, ${this.centeringY}px)`,
- width: xshift ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
- height: yshift ?? (this.fitWidth ? `${this.panelHeight}px` :
+ width: xshift() ?? `${100 * (this.props.PanelWidth() - this.Xshift * 2) / this.props.PanelWidth()}%`,
+ height: yshift() ?? (this.fitWidth ? `${this.panelHeight}px` :
`${100 * this.effectiveNativeHeight / this.effectiveNativeWidth * this.props.PanelWidth() / this.props.PanelHeight()}%`),
}}>
<DocumentViewInternal {...this.props}
diff --git a/src/client/views/nodes/ScreenshotBox.tsx b/src/client/views/nodes/ScreenshotBox.tsx
index 6c019d6f2..aba7be68d 100644
--- a/src/client/views/nodes/ScreenshotBox.tsx
+++ b/src/client/views/nodes/ScreenshotBox.tsx
@@ -146,7 +146,7 @@ export class ScreenshotBox extends ViewBoxAnnotatableComponent<FieldViewProps, S
}
contentFunc = () => [this.content];
videoPanelHeight = () => NumCast(this.dataDoc[this.fieldKey + "-nativeHeight"], 1) / NumCast(this.dataDoc[this.fieldKey + "-nativeWidth"], 1) * this.props.PanelWidth();
- formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight())
+ formattedPanelHeight = () => Math.max(0, this.props.PanelHeight() - this.videoPanelHeight());
render() {
TraceMobx();
return <div className="videoBox" onContextMenu={this.specificContextMenu} style={{ width: "100%", height: "100%" }} >
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 4e03589d6..da05b0c13 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -297,9 +297,9 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
// returns the path of the audio file
@computed get audiopath() {
- const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField);
- const path = (field instanceof AudioField) ? field.url.href : "";
- return path === nullAudio ? "" : path;
+ const field = Cast(this.props.Document[this.props.fieldKey + '-audio'], AudioField, null);
+ const vfield = Cast(this.dataDoc[this.fieldKey], VideoField, null);
+ return field?.url.href ?? vfield?.url.href ?? "";
}
// ref for updating time
_audioPlayer: HTMLAudioElement | null = null;
@@ -322,7 +322,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
<source src={field.url.href} type="video/mp4" />
Not supported.
</video>
- {!this.audiopath ? (null) :
+ {!this.audiopath || this.audiopath === field.url.href ? (null) :
<audio ref={this.setAudioRef} className={`audiobox-control${this.active() ? "-interactive" : ""}`}>
<source src={this.audiopath} type="audio/mpeg" />
Not supported.
@@ -510,6 +510,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent<FieldViewProps, VideoD
return <div className="videoBox-stackPanel" style={{ transition: this.transition, height: `${100 - this.heightPercent}%` }}>
<CollectionStackedTimeline ref={this._stackedTimeline} {...this.props}
fieldKey={this.annotationKey}
+ mediaPath={this.audiopath}
renderDepth={this.props.renderDepth + 1}
startTag={"_timecodeToShow" /* videoStart */}
endTag={"_timecodeToHide" /* videoEnd */}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index aaf3a938e..abfc63b40 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -1153,7 +1153,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
setTimeout(func);
}
else docView.ComponentView?.playFrom?.(timecode, Cast(anchor.timecodeToHide, "number", null)); // bcz: would be nice to find the next audio tag in the doc and play until that
- }
+ };
func();
}
});
diff --git a/src/server/DashUploadUtils.ts b/src/server/DashUploadUtils.ts
index e2c0e7ac9..ff6b2381c 100644
--- a/src/server/DashUploadUtils.ts
+++ b/src/server/DashUploadUtils.ts
@@ -16,7 +16,7 @@ import { resolvedServerUrl } from "./server_Initialization";
import { AcceptableMedia, Upload } from './SharedMediaTypes';
import request = require('request-promise');
const parse = require('pdf-parse');
-var ffmpeg = require("fluent-ffmpeg");
+const ffmpeg = require("fluent-ffmpeg");
const requestImageSize = require("../client/util/request-image-size");
export enum SizeSuffix {
@@ -75,7 +75,8 @@ export namespace DashUploadUtils {
if (format.includes("x-matroska")) {
await new Promise(res => ffmpeg(file.path)
.videoCodec("copy") // this will copy the data instead of reencode it
- .save(file.path.replace(".mkv", ".mp4")).on('end', () => res()));
+ .save(file.path.replace(".mkv", ".mp4"))
+ .on('end', res));
file.path = file.path.replace(".mkv", ".mp4");
format = ".mp4";
}