diff options
author | bobzel <zzzman@gmail.com> | 2021-01-14 17:13:57 -0500 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2021-01-14 17:13:57 -0500 |
commit | 4eb2de2c53280104fc809bbd387ed97460312e69 (patch) | |
tree | 44b114eab98d6759158c54cc5eefb960efbbc195 /src | |
parent | 8176561879eecadec6a2f8926f08db93807aa16e (diff) |
simplified links to audio to always have a target Doc anchor. updated auto links to audio when recording. fixed making text selection links.
Diffstat (limited to 'src')
-rw-r--r-- | src/client/documents/Documents.ts | 12 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 8 | ||||
-rw-r--r-- | src/client/views/nodes/AudioBox.tsx | 88 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 17 | ||||
-rw-r--r-- | src/client/views/nodes/formattedText/FormattedTextBox.tsx | 3 |
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; } }); |