aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbobzel <zzzman@gmail.com>2021-01-14 17:13:57 -0500
committerbobzel <zzzman@gmail.com>2021-01-14 17:13:57 -0500
commit4eb2de2c53280104fc809bbd387ed97460312e69 (patch)
tree44b114eab98d6759158c54cc5eefb960efbbc195
parent8176561879eecadec6a2f8926f08db93807aa16e (diff)
simplified links to audio to always have a target Doc anchor. updated auto links to audio when recording. fixed making text selection links.
-rw-r--r--src/client/documents/Documents.ts12
-rw-r--r--src/client/util/DragManager.ts8
-rw-r--r--src/client/views/nodes/AudioBox.tsx88
-rw-r--r--src/client/views/nodes/DocumentView.tsx17
-rw-r--r--src/client/views/nodes/formattedText/FormattedTextBox.tsx3
5 files changed, 41 insertions, 87 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index c457fb722..b32cbd3d0 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -1,4 +1,4 @@
-import { runInAction, action } from "mobx";
+import { action, runInAction } from "mobx";
import { basename, extname } from "path";
import { DateField } from "../../fields/DateField";
import { Doc, DocListCast, DocListCastAsync, Field, HeightSym, Opt, WidthSym } from "../../fields/Doc";
@@ -42,19 +42,19 @@ import { ImageBox } from "../views/nodes/ImageBox";
import { KeyValueBox } from "../views/nodes/KeyValueBox";
import { LabelBox } from "../views/nodes/LabelBox";
import { LinkBox } from "../views/nodes/LinkBox";
+import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup";
import { PDFBox } from "../views/nodes/PDFBox";
import { PresBox } from "../views/nodes/PresBox";
import { ScreenshotBox } from "../views/nodes/ScreenshotBox";
import { ScriptingBox } from "../views/nodes/ScriptingBox";
import { SliderBox } from "../views/nodes/SliderBox";
+import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
import { VideoBox } from "../views/nodes/VideoBox";
import { WebBox } from "../views/nodes/WebBox";
import { PresElementBox } from "../views/presentationview/PresElementBox";
import { SearchBox } from "../views/search/SearchBox";
import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo";
import { DocumentType } from "./DocumentTypes";
-import { TaskCompletionBox } from "../views/nodes/TaskCompletedBox";
-import { LinkDescriptionPopup } from "../views/nodes/LinkDescriptionPopup";
const path = require('path');
const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", ""));
@@ -610,7 +610,7 @@ export namespace Docs {
proto.links = ComputedField.MakeFunction("links(self)");
viewDoc.author = Doc.CurrentUserEmail;
- viewDoc.type !== DocumentType.LINK && DocUtils.MakeLinkToActiveAudio(viewDoc);
+ viewDoc.type !== DocumentType.LINK && viewDoc.type !== DocumentType.LABEL && DocUtils.MakeLinkToActiveAudio(viewDoc);
viewDoc["acl-Public"] = dataDoc["acl-Public"] = Doc.UserDoc()?.defaultAclPrivate ? SharingPermissions.None : SharingPermissions.Add;
viewDoc["acl-Override"] = dataDoc["acl-Override"] = "None";
@@ -1008,10 +1008,10 @@ export namespace DocUtils {
});
}
- export let ActiveRecordings: Doc[] = [];
+ export let ActiveRecordings: AudioBox[] = [];
export function MakeLinkToActiveAudio(doc: Doc) {
- DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d }, "audio link", "audio timeline"));
+ DocUtils.ActiveRecordings.map(d => DocUtils.MakeLink({ doc: doc }, { doc: d.getAnchor() || d.props.Document }, "audio link", "audio timeline"));
}
export function MakeLink(source: { doc: Doc }, target: { doc: Doc }, linkRelationship: string = "", description: string = "", id?: string, allowParCollectionLink?: boolean, showPopup?: number[]) {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index d24348746..52ccfda74 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -529,15 +529,16 @@ export namespace DragManager {
endDrag();
};
const upHandler = (e: PointerEvent) => {
- dispatchDrag(eles, e, dragData, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag);
- options?.dragComplete?.(new DragCompleteEvent(false, dragData));
+ const complete = new DragCompleteEvent(false, dragData);
+ dispatchDrag(eles, e, complete, xFromLeft, yFromTop, xFromRight, yFromBottom, options, finishDrag);
+ options?.dragComplete?.(complete);
endDrag();
};
document.addEventListener("pointermove", moveHandler, true);
document.addEventListener("pointerup", upHandler);
}
- function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any },
+ function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, complete: DragCompleteEvent,
xFromLeft: number, yFromTop: number, xFromRight: number, yFromBottom: number, options?: DragOptions, finishDrag?: (e: DragCompleteEvent) => void) {
const removed = dragEles.map(dragEle => {
const ret = { ele: dragEle, w: dragEle.style.width, h: dragEle.style.height, o: dragEle.style.overflow };
@@ -558,7 +559,6 @@ export namespace DragManager {
});
const { thisX, thisY } = snapDrag(e, xFromLeft, yFromTop, xFromRight, yFromBottom);
if (target) {
- const complete = new DragCompleteEvent(false, dragData);
target.dispatchEvent(
new CustomEvent<DropEvent>("dashPreDrop", {
bubbles: true,
diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx
index 21dfec888..9f8bdcd57 100644
--- a/src/client/views/nodes/AudioBox.tsx
+++ b/src/client/views/nodes/AudioBox.tsx
@@ -30,7 +30,6 @@ import { FieldView, FieldViewProps } from './FieldView';
import { FormattedTextBoxComment } from "./formattedText/FormattedTextBoxComment";
import { LinkDocPreview } from "./LinkDocPreview";
import { computedFn } from "mobx-utils";
-import { ObservableValue } from "mobx/lib/internal";
declare class MediaRecorder {
// whatever MediaRecorder has
@@ -46,7 +45,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
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 playheadWidth = 30; // width of playhead
static heightPercent = 80; // height of timeline in percent of height of audioBox.
static Instance: AudioBox;
@@ -104,60 +103,34 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
getLinkData(l: Doc) {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- let linkTime = NumCast(l.anchor2_timecode);
+ let linkTime = NumCast(la2.audioStart, NumCast(la1.audioStart));
if (Doc.AreProtosEqual(la1, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
- linkTime = NumCast(l.anchor1_timecode);
}
return { la1, la2, linkTime };
}
componentWillUnmount() {
- this._disposers.reaction?.();
- this._disposers.linkPlay?.();
- this._disposers.scrubbing?.();
- this._disposers.audioStart?.();
+ Object.values(this._disposers).forEach(disposer => disposer?.());
+ const ind = DocUtils.ActiveRecordings.indexOf(this);
+ ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
}
getAnchor = () => {
- return this._ele?.currentTime ? this.createMarker(this._ele?.currentTime) : this.rootDoc;
+ const time = this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined);
+ return time ? this.createMarker(time) : this.rootDoc;
}
+
@action
componentDidMount() {
if (!this.dataDoc.markerAmount) {
this.dataDoc.markerAmount = 0;
}
- this.props.setContentView?.(this);
+ this.props.setContentView?.(this); // this tells the DocumentView that this AudioBox is the "content" of the document. this allows the DocumentView to indirectly call getAnchor() on the AudioBox when making a link.
this.audioState = this.path ? "paused" : undefined;
- this._disposers.linkPlay = reaction(() => this.layoutDoc.scrollToLinkID,
- scrollLinkId => {
- if (scrollLinkId) {
- this.links.filter(l => l[Id] === scrollLinkId).map(l => {
- const { linkTime } = this.getLinkData(l);
- setTimeout(() => { this.playFromTime(linkTime); Doc.linkFollowHighlight(l); }, 250);
- });
- Doc.SetInPlace(this.layoutDoc, "scrollToLinkID", undefined, false);
- }
- }, { fireImmediately: true });
-
- // for play when link is selected
- this._disposers.reaction = reaction(() => SelectionManager.Views(),
- selected => {
- const sel = selected.length ? selected[0].props.Document : undefined;
- let link;
- sel && this.links.forEach(l => {
- if (l.anchor1 === sel || l.anchor2 === sel && !sel.audioStart) {
- link = this.playLink(sel);
- }
- });
- // for links created during recording
- if (!link) {
- this.layoutDoc.playOnSelect && this.recordingStart && sel && sel.creationDate && !Doc.AreProtosEqual(sel, this.props.Document) && this.playFromTime(DateCast(sel.creationDate).date.getTime());
- this.layoutDoc.playOnSelect && this.recordingStart && !sel && this.pause();
- }
- });
+
this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime));
this._disposers.audioStart = reaction(
@@ -183,27 +156,18 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
}
playLink = (doc: Doc) => {
- let link = false;
- !Doc.AreProtosEqual(doc, this.props.Document) && this.links.forEach(l => {
- if (l.anchor1 === doc || l.anchor2 === doc) {
- const { la1, la2, linkTime } = this.getLinkData(l);
- let startTime = linkTime;
- if (la2.audioStart) startTime = NumCast(la2.audioStart);
- if (la1.audioStart) startTime = NumCast(la1.audioStart);
-
- let endTime;
- if (la1.audioEnd) endTime = NumCast(la1.audioEnd);
- if (la2.audioEnd) endTime = NumCast(la2.audioEnd);
-
- if (startTime) {
- link = true;
- this.layoutDoc.playOnSelect && (endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime));
- }
+ console.log(doc);
+ this.links.filter(l => l.anchor1 === doc || l.anchor2 === doc).forEach(l => {
+ const { la1, la2 } = this.getLinkData(l);
+ const startTime = NumCast(la1.audioStart, NumCast(la2.audioStart, null));
+ const endTime = NumCast(la1.audioEnd, NumCast(la2.audioEnd, null));
+ if (startTime !== undefined) {
+ this.layoutDoc.playOnSelect && (endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime));
}
});
-
- this.layoutDoc.playOnSelect && Doc.AreProtosEqual(doc, this.props.Document) && this.pause();
- return link;
+ if (doc.annotationOn === this.rootDoc) {
+ this.playFrom(NumCast(doc.audioStart), Cast(doc.audioEnd, "number", null));
+ }
}
// for updating the timecode
@@ -227,7 +191,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this.audioState = "paused";
});
- // play audio for documents created during recording
+ // play audio for documents created during recording
playFromTime = (absoluteTime: number) => {
this.recordingStart && this.playFrom((absoluteTime - this.recordingStart) / 1000);
}
@@ -292,7 +256,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._stream = await navigator.mediaDevices.getUserMedia({ audio: true });
this._recorder = new MediaRecorder(this._stream);
this.dataDoc[this.props.fieldKey + "-recordingStart"] = new DateField(new Date());
- DocUtils.ActiveRecordings.push(this.props.Document);
+ DocUtils.ActiveRecordings.push(this);
this._recorder.ondataavailable = async (e: any) => {
const [{ result }] = await Networking.UploadFilesToServer(e.data);
if (!(result instanceof Error)) {
@@ -315,14 +279,14 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
ContextMenu.Instance?.addItem({ description: "Options...", subitems: funcs, icon: "asterisk" });
}
- // stops the recording
+ // stops the recording
stopRecording = action(() => {
this._recorder.stop();
this._recorder = undefined;
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);
+ const ind = DocUtils.ActiveRecordings.indexOf(this);
ind !== -1 && (DocUtils.ActiveRecordings.splice(ind, 1));
});
@@ -413,7 +377,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
this._ele!.currentTime = this.layoutDoc._currentTimecode = (e.clientX - rect.x) / rect.width * this.audioDuration;
wasPaused && this.pause();
- this.props.select(false)
+ this.props.select(false);
const toTimeline = (screen_delta: number) => screen_delta / rect.width * this.audioDuration;
this._markerStart = this._markerEnd = toTimeline(e.clientX - rect.x);
setupMoveUpEvents(this, e,
@@ -568,7 +532,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent<FieldViewProps, AudioD
rangePlayScript = () => AudioBox.RangePlayScript;
labelPlayScript = () => AudioBox.LabelPlayScript;
renderInner = computedFn(function (this: AudioBox, mark: Doc, script: undefined | (() => ScriptField), doublescript: undefined | (() => ScriptField), x: number, y: number, width: number, height: number) {
- let marker = observable({ view: undefined as any });
+ const marker = observable({ view: undefined as any });
return {
marker, view: <DocumentView key="view" {...this.props} ref={action((r: DocumentView | null) => marker.view = r)}
Document={mark}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 98995d040..131e33e2a 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -52,7 +52,7 @@ export interface DocumentViewSharedProps {
fitContentsToDoc?: boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document
ContainingCollectionView: Opt<CollectionView>;
ContainingCollectionDoc: Opt<Doc>;
- setContentView?: (view: { getAnchor: () => Doc }) => any,
+ setContentView?: (view: { getAnchor: () => Doc }) => any;
CollectionFreeFormDocumentView?: () => CollectionFreeFormDocumentView;
PanelWidth: () => number;
PanelHeight: () => number;
@@ -553,7 +553,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
const linkSource = de.complete.annoDragData ? de.complete.annoDragData.annotationDocument : de.complete.linkDragData ? de.complete.linkDragData.linkSourceDocument : undefined;
if (linkSource && linkSource !== this.props.Document) {
e.stopPropagation();
- de.complete.linkDocument = DocUtils.MakeLink({ doc: linkSource }, { doc: this.props.Document }, "link", undefined, undefined, undefined, [de.x, de.y]);
+ de.complete.linkDocument = DocUtils.MakeLink({ doc: linkSource }, { doc: this._componentView?.getAnchor() || this.rootDoc }, "link", undefined, undefined, undefined, [de.x, de.y]);
}
}
@@ -693,7 +693,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
screenToLocal = () => this.props.ScreenToLocalTransform().translate(0, -this.headerMargin);
contentScaling = () => this.ContentScale;
onClickFunc = () => this.onClickHandler;
- makeLink = () => this.props.DocumentView._link; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
+ makeLink = () => this.props.DocumentView.LinkBeingCreated; // pass the link placeholde to child views so they can react to make a specialized anchor. This is essentially a function call to the descendants since the value of the _link variable will immediately get set back to undefined.
setContentView = (view: { getAnchor: () => Doc }) => this._componentView = view;
@observable contentsActive: () => boolean = returnFalse;
@action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive;
@@ -722,15 +722,6 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
</div>;
}
- // used to decide whether a link anchor view should be created or not.
- // if it's a temporal link (currently just for Audio), then the audioBox will display the anchor and we don't want to display it here.
- // would be good to generalize this some way.
- isNonTemporalLink = (linkDoc: Doc) => {
- const anchor = Cast(Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1 : linkDoc.anchor2, Doc) as Doc;
- const ept = Doc.AreProtosEqual(this.props.Document, Cast(linkDoc.anchor1, Doc) as Doc) ? linkDoc.anchor1_timecode : linkDoc.anchor2_timecode;
- return anchor.type === DocumentType.AUDIO && NumCast(ept) ? false : true;
- }
-
@undoBatch
hideLinkAnchor = (doc: Doc | Doc[]) => (doc instanceof Doc ? [doc] : doc).reduce((flg, doc) => flg && (doc.hidden = true), true)
anchorPanelWidth = () => this.props.PanelWidth() || 1;
@@ -745,7 +736,7 @@ export class DocumentViewInternal extends DocComponent<DocumentViewInternalProps
if (this.props.LayoutTemplateString?.includes(LinkAnchorBox.name)) return null;
if (this.layoutDoc.presBox || this.rootDoc.type === DocumentType.LINK || this.props.dontRegisterView) return (null);
- const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden && this.isNonTemporalLink(d));
+ const filtered = DocUtils.FilterDocs(this.directLinks, this.props.docFilters(), []).filter(d => !d.hidden);
return filtered.map((d, i) =>
<div className="documentView-anchorCont" key={i + 1}>
<DocumentView {...this.props}
diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
index 2b9910dfb..c129d0204 100644
--- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx
+++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx
@@ -320,12 +320,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp
DocListCast(this.dataDoc.links).map((l, i) => {
let la1 = l.anchor1 as Doc;
let la2 = l.anchor2 as Doc;
- this._linkTime = NumCast(l.anchor2_timecode);
+ this._linkTime = NumCast(la1.audioStart, NumCast(la2.audioStart));
audioState = la2.audioState;
if (Doc.AreProtosEqual(la2, this.dataDoc)) {
la1 = l.anchor2 as Doc;
la2 = l.anchor1 as Doc;
- this._linkTime = NumCast(l.anchor1_timecode);
audioState = la1.audioState;
}
});