From 4e75d733d91fddf1f99028fe9351aeba16a7b36e Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Jan 2021 10:10:34 -0500 Subject: re-added click behavior lost in refactoring --- .../collections/CollectionStackedTimeline.tsx | 47 +++++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 1775250fa..44fc0f709 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -58,10 +58,10 @@ export class CollectionStackedTimeline extends CollectionSubView { - this.props.playFrom(this.anchorStart(anchorDoc), this.anchorEnd(anchorDoc, this.props.duration)); + playOnClick = (anchorDoc: Doc, clientX: number) => { + const seekTimeInSeconds = this.anchorStart(anchorDoc); + const endTime = this.anchorEnd(anchorDoc); + if (this.layoutDoc.autoPlay) { + if (this.props.playing()) this.props.Pause(); + else this.props.playFrom(seekTimeInSeconds, endTime); + } else { + if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) && endTime > NumCast(this.layoutDoc._currentTimecode)) { + if (!this.layoutDoc.autoPlay && this.props.playing()) { + this.props.Pause(); + } else { + this.props.Play(); + } + } else { + this.props.playFrom(seekTimeInSeconds, endTime); + } + } return { select: true }; } - // play back the audio from time @action - clickAnchor = (anchorDoc: Doc) => { - if (this.props.Document.autoPlay) return this.playOnClick(anchorDoc); - this.props.setTime(this.anchorStart(anchorDoc)); + clickAnchor = (anchorDoc: Doc, clientX: number) => { + const seekTimeInSeconds = this.anchorStart(anchorDoc); + const endTime = this.anchorEnd(anchorDoc); + if (seekTimeInSeconds < NumCast(this.layoutDoc._currentTimecode) + 1e-4 && endTime > NumCast(this.layoutDoc._currentTimecode) - 1e-4) { + if (this.props.playing()) this.props.Pause(); + else if (this.layoutDoc.autoPlay) this.props.Play(); + else if (!this.layoutDoc.autoPlay) { + const rect = this._timeline?.getBoundingClientRect(); + rect && this.props.setTime(this.toTimeline(clientX - rect.x, rect.width)); + } + } else { + if (this.layoutDoc.autoPlay) this.props.playFrom(seekTimeInSeconds, endTime); + else this.props.setTime(seekTimeInSeconds); + } return { select: true }; } + toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.props.duration, screen_delta / width * this.props.duration)); // starting the drag event for anchor resizing onPointerDown = (e: React.PointerEvent, m: Doc, left: boolean): void => { -- cgit v1.2.3-70-g09d2 From 3576c88e3c5bc9c6745c2f7fa70721c4057cae0e Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Jan 2021 10:42:42 -0500 Subject: fixed runtime error warning --- .../collectionFreeForm/CollectionFreeFormView.tsx | 21 +++++++++------------ src/client/views/nodes/VideoBox.tsx | 4 ++-- 2 files changed, 11 insertions(+), 14 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 6619205af..73375569f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1651,18 +1651,15 @@ class CollectionFreeFormViewPannableContents extends React.Component - {!this.props.presPinView ? (null) :
-
-
-
-
-
-
-
} - - ); + return !this.props.presPinView ? (null) : +
+
+
+
+
+
+
+
; } } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index bfac7dc1c..7f123757b 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -310,8 +310,8 @@ export class VideoBox extends ViewBoxAnnotatableComponentLoading : -
+ return !field ?
Loading
: +
} placement="top"> diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 44fc0f709..54cc86523 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -84,8 +84,8 @@ export class CollectionStackedTimeline extends CollectionSubView NumCast(anchor.anchorStartTime, NumCast(anchor._timecodeToShow, NumCast(anchor.videoStart))); - anchorEnd = (anchor: Doc, defaultVal: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor._timecodeToHide, NumCast(anchor.videoEnd, defaultVal))); + anchorStart = (anchor: Doc) => NumCast(anchor.anchorStartTime, NumCast(anchor._timecodeToShow, NumCast(anchor.videoStart, NumCast(anchor.audioStart)))); + anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor._timecodeToHide, NumCast(anchor.videoEnd, NumCast(anchor.audioEnd, val)))); getLinkData(l: Doc) { let la1 = l.anchor1 as Doc; @@ -106,7 +106,11 @@ export class CollectionStackedTimeline extends CollectionSubView, time: number) => { - anchor && (this._left ? anchor.anchorStartTime = time : anchor.anchorEndTime = time); + if (anchor) { + const timelineOnly = Cast(anchor.anchorStartTime, "number", null) !== undefined; + if (timelineOnly) this._left ? anchor.anchorStartTime = time : anchor.anchorEndTime = time; + else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; + } } // checks if the two anchors are the same with start and end time @@ -288,6 +292,7 @@ export class CollectionStackedTimeline extends CollectionSubView }; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7b1b1bf0c..5ce8fcdcb 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -30,7 +30,6 @@ import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { EditableView } from '../EditableView'; import { InkingStroke } from "../InkingStroke"; -import { InkStrokeProperties } from '../InkStrokeProperties'; import { StyleLayers, StyleProp } from "../StyleProvider"; import { CollectionFreeFormDocumentView } from "./CollectionFreeFormDocumentView"; import { DocumentContentsView } from "./DocumentContentsView"; @@ -83,6 +82,7 @@ export interface DocumentViewSharedProps { export interface DocumentViewProps extends DocumentViewSharedProps { // properties specific to DocumentViews but not to FieldView freezeDimensions?: boolean; + hideResizeHandles?: boolean; // whether to suppress DocumentDecorations when this document is selected hideTitle?: boolean; // forces suppression of title. e.g, treeView document labels suppress titles in case they are globally active via settings treeViewDoc?: Doc; contentPointerEvents?: string; // pointer events allowed for content of a document view. eg. set to "none" in menuSidebar for sharedDocs so that you can select a document, but not interact with its contents diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index 19f605278..ac4d64f12 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -183,14 +183,15 @@ pointer-events:all; } -.timeline-button { +.videoBox-timelineButton { position: absolute; display: flex; align-items: center; z-index: 1010; - bottom: 35px; - right: 235px; + bottom: 5px; + right: 5px; color: white; + cursor: pointer; background: dimgrey; width: 20px; height: 20px; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 7f123757b..6286b5e26 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -380,16 +380,11 @@ export class VideoBox extends ViewBoxAnnotatableComponent{"" + formatTime(curTime)} {" " + Math.round((curTime - Math.trunc(curTime)) * 100)}
, -
+
, -
this.layoutDoc._timelineShow = !this.layoutDoc._timelineShow)} - style={{ - transform: `scale(${this.scaling()})`, - right: this.scaling() * 10 - 10, - bottom: this.scaling() * 10 - 10 - }}> - +
this.layoutDoc._timelineShow = !this.layoutDoc._timelineShow)}> +
, VideoBox._showControls ? (null) : [ //
@@ -411,7 +406,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + onSnapshot = (e: React.MouseEvent) => { this.Snapshot(); e.stopPropagation(); e.preventDefault(); -- cgit v1.2.3-70-g09d2 From f43f47662293ddb2cd4d2eb217bc4f01b6d22826 Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Jan 2021 13:22:01 -0500 Subject: animated expansion of video box timeline. --- src/client/views/DocumentDecorations.tsx | 2 +- .../collections/CollectionStackedTimeline.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 32 +++++++++++++++++----- 3 files changed, 27 insertions(+), 9 deletions(-) (limited to 'src') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 250b93188..54e0a7ac7 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -613,7 +613,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b }}> {closeIcon} {bounds.r - bounds.x < 100 ? null : titleArea} - {seldoc.props.hideDecorations ? (null) : + {seldoc.props.hideResizeHandles ? (null) : <> {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) : {`${seldoc.finalLayoutKey.includes("icon") ? "De" : ""}Iconify Document`}
} placement="top"> diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 54cc86523..4b14c3508 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -292,7 +292,7 @@ export class CollectionStackedTimeline extends CollectionSubView }; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 6286b5e26..506ba8c49 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -55,16 +55,17 @@ export class VideoBox extends ViewBoxAnnotatableComponent = new Dictionary(); @observable _screenCapture = false; - @observable _visible: boolean = false; + @observable _clicking = false; @observable _forceCreateYouTubeIFrame = false; @observable _playTimer?: NodeJS.Timeout = undefined; @observable _fullScreen = false; @observable _playing = false; @computed get links() { return DocListCast(this.dataDoc.links); } - @computed get heightPercent() { return this.layoutDoc._timelineShow ? NumCast(this.layoutDoc._videoTimelineHeightPercent, VideoBox.heightPercent) : 100; } + @computed get heightPercent() { return NumCast(this.layoutDoc._timelineHeightPercent, 100); } @computed get duration() { return NumCast(this.dataDoc[this.fieldKey + "-duration"]); } @computed get anchorDocs() { return DocListCast(this.dataDoc[this.annotationKey + "-timeline"]).concat(DocListCast(this.dataDoc[this.annotationKey])); } + private get transition() { return this._clicking ? "left 0.5s, width 0.5s, height 0.5s" : ""; } public get player(): HTMLVideoElement | null { return this._videoRef; } constructor(props: Readonly) { @@ -383,8 +384,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent
, -
this.layoutDoc._timelineShow = !this.layoutDoc._timelineShow)}> - +
+
, VideoBox._showControls ? (null) : [ //
@@ -412,6 +413,23 @@ export class VideoBox extends ViewBoxAnnotatableComponent { + this._clicking = true; + setupMoveUpEvents(this, e, + action((e: PointerEvent) => { + this._clicking = false; + if (this.active()) { + const local = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); + this.layoutDoc._timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100)); + } + return false; + }), emptyFunction, + () => { + this.layoutDoc._timelineHeightPercent = this.heightPercent !== 100 ? 100 : VideoBox.heightPercent; + setTimeout(action(() => this._clicking = false), 500); + }, this.active(), this.active()); + }); + onResetDown = (e: React.PointerEvent) => { setupMoveUpEvents(this, e, (e: PointerEvent) => { this.Seek(Math.max(0, (this.layoutDoc._currentTimecode || 0) + Math.sign(e.movementX) * 0.0333)); @@ -478,7 +496,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent + return
[this.youtubeVideoId ? this.youtubeContent : this.content]; @computed get annotationLayer() { - return
; + return
; } marqueeDown = action((e: React.PointerEvent) => { @@ -548,7 +566,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent
-
+
Date: Wed, 27 Jan 2021 16:33:45 -0500 Subject: updated documentdecorations title display to be more visible on complex backgrounds. fixed link following behavior (espicall from presentations) to timeline annotations. --- src/client/views/DocComponent.tsx | 5 ++- src/client/views/DocumentDecorations.scss | 38 ++++++++++++---------- src/client/views/DocumentDecorations.tsx | 6 ++-- src/client/views/MainView.scss | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 10 +++--- src/client/views/nodes/PresBox.tsx | 7 ++-- 6 files changed, 39 insertions(+), 29 deletions(-) (limited to 'src') diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 8d545c61b..99f13295d 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -131,7 +131,10 @@ export function ViewBoxAnnotatableComponent

effectiveAcl === AclEdit || effectiveAcl === AclAdmin || GetEffectiveAcl(doc) === AclAdmin); if (docs.length) { const docs = doc instanceof Doc ? [doc] : doc; - docs.map(doc => doc.isPushpin = doc.annotationOn = undefined); + docs.map(doc => { + Doc.SetInPlace(doc, "isPushpin", undefined, true); + Doc.SetInPlace(doc, "annotationOn", undefined, true); + }); const targetDataDoc = this.dataDoc; const value = DocListCast(targetDataDoc[annotationKey ?? this.annotationKey]); const toRemove = value.filter(v => docs.includes(v)); diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index f9b8c1940..22e120167 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -79,7 +79,7 @@ $linkGap : 3px; grid-column: 5; grid-row: 4; border-radius: 100%; - background: dimgray; + background: black; height: 8; right: -12; top: 12; @@ -145,7 +145,7 @@ $linkGap : 3px; .documentDecorations-topRightResizer:hover, .documentDecorations-bottomLeftResizer:hover { cursor: nesw-resize; - background: dimGray; + background: black; opacity: 1; } @@ -169,6 +169,14 @@ $linkGap : 3px; cursor: pointer; } + .documentDecorations-titleBackground { + background: #ffffffcf; + border-radius: 8px; + width: 100%; + height: 100%; + position: absolute; + } + .documentDecorations-title { opacity: 1; grid-column-start: 2; @@ -177,26 +185,22 @@ $linkGap : 3px; overflow: hidden; text-align: center; display: flex; - border-bottom: solid 1px; - margin-left: 10px; - width: calc(100% - 10px); + margin-left: 5px; + height: 22px; + position: absolute; + .documentDecorations-titleSpan { + width: 100%; + border-radius: 8px; + background: #ffffffcf; + position: absolute; + display: inline-block; + cursor: move; + } } .focus-visible { margin-left: 0px; } - - .publishBox { - width: 20px; - height: 22px; - grid-column-start: 3; - grid-column-end: 4; - pointer-events: all; - background: darkgray; - display: inline-block; - position: absolute; - right: 0; - } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 54e0a7ac7..8d8f4cd3b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -577,8 +577,8 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b const titleArea = this._edtingTitle ? this.titleBlur(true)} onChange={action(e => this._accumulatedTitle = e.target.value)} onKeyPress={this.titleEntered} /> : -

- {`${this.selectionTitle}`} +
+ {`${this.selectionTitle}`}
; let inMainMenuPanel = false; @@ -612,7 +612,7 @@ export class DocumentDecorations extends React.Component<{ boundsLeft: number, b top: bounds.y - this._resizeBorderWidth / 2 - this._titleHeight, }}> {closeIcon} - {bounds.r - bounds.x < 100 ? null : titleArea} + {titleArea} {seldoc.props.hideResizeHandles ? (null) : <> {SelectionManager.Views().length !== 1 || seldoc.Document.type === DocumentType.INK ? (null) : diff --git a/src/client/views/MainView.scss b/src/client/views/MainView.scss index d6a455a22..8ccb64744 100644 --- a/src/client/views/MainView.scss +++ b/src/client/views/MainView.scss @@ -61,7 +61,7 @@ } .mainView-container { - color: dimgray; + color: black; .lm_title { background: #cacaca; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 73375569f..8c3f0997f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -931,11 +931,13 @@ export class CollectionFreeFormView extends CollectionSubView self._dragArray.splice(0, self._dragArray.length, ...dragViewCache); self._eleArray.splice(0, self._eleArray.length, ...eleViewCache); }); - const openInTab = () => { - collectionDocView ? collectionDocView.props.addDocTab(targetDoc, "") : this.props.addDocTab(targetDoc, ":left"); + const openInTab = (doc: Doc, finished?: () => void) => { + collectionDocView ? collectionDocView.props.addDocTab(doc, "") : this.props.addDocTab(doc, ":left"); this.layoutDoc.presCollection = targetDoc; // this still needs some fixing setTimeout(resetSelection, 500); + doc !== targetDoc && setTimeout(() => finished?.(), 100); /// give it some time to create the targetDoc if we're opening up its context }; // If openDocument is selected then it should open the document for the user if (activeItem.openDocument) { - openInTab(); + openInTab(targetDoc); } else if (curDoc.presMovement === PresMovement.Pan && targetDoc) { await DocumentManager.Instance.jumpToDocument(targetDoc, false, openInTab, srcContext, undefined, undefined, undefined, includesDoc || tab ? undefined : resetSelection); // documents open in new tab instead of on right } else if ((curDoc.presMovement === PresMovement.Zoom || curDoc.presMovement === PresMovement.Jump) && targetDoc) { -- cgit v1.2.3-70-g09d2 From e24584cc674f763aec5d0e8f55f95a1d4c14522f Mon Sep 17 00:00:00 2001 From: bobzel Date: Wed, 27 Jan 2021 17:30:36 -0500 Subject: fixed undo for timeline region creation. fixed inking tools display after deleting an active text. box --- src/client/views/collections/CollectionStackedTimeline.tsx | 2 ++ src/client/views/nodes/formattedText/FormattedTextBox.tsx | 1 + 2 files changed, 3 insertions(+) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 4b14c3508..21fbef1ac 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -16,6 +16,7 @@ import { CollectionSubView } from "../collections/CollectionSubView"; import { DocumentView } from "../nodes/DocumentView"; import { LabelBox } from "../nodes/LabelBox"; import "./CollectionStackedTimeline.scss"; +import { undoBatch } from "../../util/UndoManager"; type PanZoomDocument = makeInterface<[]>; const PanZoomDocument = makeInterface(); @@ -164,6 +165,7 @@ export class CollectionStackedTimeline extends CollectionSubView Date: Wed, 27 Jan 2021 18:00:09 -0500 Subject: fixed focusing on nested documents in a text document. --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/client/views/nodes/formattedText/RichTextSchema.tsx | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index f3ac837d9..36d268fe9 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1302,7 +1302,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this.endUndoTypingBatch(); this.unhighlightSearchTerms(); this._editorView?.destroy(); - RichTextMenu.Instance.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); + RichTextMenu.Instance?.TextView === this && RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); FormattedTextBoxComment.tooltip && (FormattedTextBoxComment.tooltip.style.display = "none"); } diff --git a/src/client/views/nodes/formattedText/RichTextSchema.tsx b/src/client/views/nodes/formattedText/RichTextSchema.tsx index d272b6b8c..abbb89780 100644 --- a/src/client/views/nodes/formattedText/RichTextSchema.tsx +++ b/src/client/views/nodes/formattedText/RichTextSchema.tsx @@ -136,6 +136,8 @@ export class DashDocView { addDocument={returnFalse} rootSelected={this._textBox.props.isSelected} removeDocument={removeDoc} + layerProvider={this._textBox.props.layerProvider} + styleProvider={this._textBox.props.styleProvider} ScreenToLocalTransform={this.getDocTransform} addDocTab={this._textBox.props.addDocTab} pinToPres={returnFalse} @@ -143,7 +145,6 @@ export class DashDocView { PanelWidth={finalLayout[WidthSym]} PanelHeight={finalLayout[HeightSym]} focus={this.outerFocus} - styleProvider={DefaultStyleProvider} parentActive={returnFalse} whenActiveChanged={returnFalse} bringToFront={emptyFunction} -- cgit v1.2.3-70-g09d2 From 01923ad2a499c6b8f571d5bc3eafaf006c91d8e4 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jan 2021 11:59:25 -0500 Subject: fixed warnings in freeformview focusDocument. enabled zooming in on images/videos. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 17 +++++++++++------ src/client/views/nodes/VideoBox.tsx | 6 +----- 2 files changed, 12 insertions(+), 11 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8c3f0997f..4d277dc1c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -816,8 +816,11 @@ export class CollectionFreeFormView extends CollectionSubView NumCast(anchor.anchorStartTime, NumCast(anchor._timecodeToShow, NumCast(anchor.videoStart))); - anchorEnd = (anchor: Doc, defaultVal: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor._timecodeToHide, NumCast(anchor.videoEnd, defaultVal))); - getAnchor = () => { return this._stackedTimeline.current?.createAnchor(Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc; } @@ -534,8 +531,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent; } - contentFunc = () => [this.youtubeVideoId ? this.youtubeContent : this.content]; - @computed get annotationLayer() { return
; } @@ -549,6 +544,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent [this.youtubeVideoId ? this.youtubeContent : this.content]; scaling = () => this.props.scaling?.() || 1; panelWidth = () => this.props.PanelWidth() * this.heightPercent / 100; panelHeight = () => this.layoutDoc._fitWidth ? this.panelWidth() / Doc.NativeAspect(this.rootDoc) : this.props.PanelHeight() * this.heightPercent / 100; -- cgit v1.2.3-70-g09d2 From e517c709905a2d4b5c631bf148a4c5074f0f5210 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jan 2021 13:02:02 -0500 Subject: fixed timeline sizing in videoBox --- src/client/views/nodes/VideoBox.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'src') diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 65bb9abaf..a9c64b0bd 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -416,7 +416,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { this._clicking = false; if (this.active()) { - const local = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); + const local = this.props.ScreenToLocalTransform().scale(this.props.scaling?.() || 1).transformPoint(e.clientX, e.clientY); this.layoutDoc._timelineHeightPercent = Math.max(0, Math.min(100, local[1] / this.props.PanelHeight() * 100)); } return false; -- cgit v1.2.3-70-g09d2 From 3b6a42e00789d7a43fd82f12a2d10766d3058e90 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jan 2021 13:09:17 -0500 Subject: preven filter facets from being editable --- src/client/views/nodes/FilterBox.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index badacbc5c..8d1c952de 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -185,6 +185,7 @@ export class FilterBox extends ViewBoxBaseComponent ScriptField.MakeScript("")!} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} -- cgit v1.2.3-70-g09d2 From 61fcef6b466c40f5b5d9c5831f25d3df240dbdf3 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jan 2021 13:11:05 -0500 Subject: from last --- src/client/views/nodes/FilterBox.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/FilterBox.tsx b/src/client/views/nodes/FilterBox.tsx index 8d1c952de..71196ea19 100644 --- a/src/client/views/nodes/FilterBox.tsx +++ b/src/client/views/nodes/FilterBox.tsx @@ -158,7 +158,7 @@ export class FilterBox extends ViewBoxBaseComponent script : undefined; } - + suppressChildClick = () => ScriptField.MakeScript("")!; render() { const facetCollection = this.props.Document; const flyout =
e.stopPropagation()}> @@ -182,10 +182,10 @@ export class FilterBox extends ViewBoxBaseComponent ScriptField.MakeScript("")!} + onChildClick={this.suppressChildClick} docFilters={returnEmptyFilter} docRangeFilters={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} -- cgit v1.2.3-70-g09d2 From 663f888a8948cd4081ed5bc5b00c1c51e3db83a5 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jan 2021 13:38:41 -0500 Subject: parameterized StackedTimeline with tag names for starting/ending an anchor --- .../views/collections/CollectionStackedTimeline.tsx | 16 +++++++++------- src/client/views/nodes/AudioBox.tsx | 10 +++++----- src/client/views/nodes/VideoBox.tsx | 9 ++++----- 3 files changed, 18 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 21fbef1ac..801433151 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -29,6 +29,8 @@ export type CollectionStackedTimelineProps = { playing: () => boolean; setTime: (time: number) => void; isChildActive: () => boolean; + startTag: string; + endTag: string; }; @observer @@ -85,13 +87,13 @@ export class CollectionStackedTimeline extends CollectionSubView NumCast(anchor.anchorStartTime, NumCast(anchor._timecodeToShow, NumCast(anchor.videoStart, NumCast(anchor.audioStart)))); - anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor.anchorEndTime, NumCast(anchor._timecodeToHide, NumCast(anchor.videoEnd, NumCast(anchor.audioEnd, val)))); + anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); + anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag])); getLinkData(l: Doc) { let la1 = l.anchor1 as Doc; let la2 = l.anchor2 as Doc; - const linkTime = NumCast(la2.anchorStartTime, NumCast(la1.anchorStartTime)); + const linkTime = NumCast(la2[this.props.startTag], NumCast(la1[this.props.startTag])); if (Doc.AreProtosEqual(la1, this.dataDoc)) { la1 = l.anchor2 as Doc; la2 = l.anchor1 as Doc; @@ -108,8 +110,8 @@ export class CollectionStackedTimeline extends CollectionSubView, time: number) => { if (anchor) { - const timelineOnly = Cast(anchor.anchorStartTime, "number", null) !== undefined; - if (timelineOnly) this._left ? anchor.anchorStartTime = time : anchor.anchorEndTime = time; + const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; + if (timelineOnly) this._left ? anchor[this.props.startTag] = time : anchor[this.props.endTag] = time; else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; } } @@ -173,10 +175,10 @@ export class CollectionStackedTimeline extends CollectionSubView; -const AudioDocument = makeInterface(documentSchema, audioSchema); +type AudioDocument = makeInterface<[typeof documentSchema]>; +const AudioDocument = makeInterface(documentSchema); @observer export class AudioBox extends ViewBoxAnnotatableComponent(AudioDocument) { @@ -352,6 +350,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent; -const VideoDocument = makeInterface(documentSchema, timeSchema); +type VideoDocument = makeInterface<[typeof documentSchema]>; +const VideoDocument = makeInterface(documentSchema); @observer export class VideoBox extends ViewBoxAnnotatableComponent(VideoDocument) { @@ -499,6 +496,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent Date: Thu, 28 Jan 2021 13:57:10 -0500 Subject: from last --- src/client/documents/Documents.ts | 2 -- src/client/views/StyleProvider.tsx | 2 +- src/client/views/collections/CollectionStackedTimeline.tsx | 2 +- 3 files changed, 2 insertions(+), 4 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 080270321..142e14ff8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -153,8 +153,6 @@ export interface DocumentOptions { _currentFrame?: number; // the current frame of a frame-based collection (e.g., progressive slide) _timecodeToShow?: number; // the time that a document should be displayed (e.g., when an annotation shows up as a video plays) _timecodeToHide?: number; // the time that a document should be hidden - anchorStartTime?: number; // the time when an annotation starts on a timeline (e.g., when an audio anchor starts on an audio/video timeline) - anchorEndTime?: number; // the time when an annotaiton ends on a timeline lastFrame?: number; // the last frame of a frame-based collection (e.g., progressive slide) activeFrame?: number; // the active frame of a document in a frame base collection appearFrame?: number; // the frame in which the document appears diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 8628be014..803390055 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -108,7 +108,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt Date: Thu, 28 Jan 2021 14:28:27 -0500 Subject: more cleanup for switching back to parameterized audioTag/videoTag instead of anchorStartTag --- src/client/views/StyleProvider.tsx | 2 +- src/client/views/collections/CollectionStackedTimeline.tsx | 8 ++++---- src/client/views/nodes/AudioBox.tsx | 8 ++++---- src/client/views/nodes/PresBox.tsx | 6 +++--- src/client/views/nodes/VideoBox.tsx | 4 ++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 2 +- src/fields/documentSchemas.ts | 2 -- 7 files changed, 15 insertions(+), 17 deletions(-) (limited to 'src') diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 803390055..3956b8c5b 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -141,7 +141,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); - anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag])); + anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val)); getLinkData(l: Doc) { let la1 = l.anchor1 as Doc; @@ -111,7 +111,7 @@ export class CollectionStackedTimeline extends CollectionSubView, time: number) => { if (anchor) { const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; - if (timelineOnly) this._left ? anchor[this.props.startTag] = time : anchor[this.props.endTag] = time; + if (timelineOnly) Doc.SetInPlace(anchor, this._left ? this.props.startTag : this.props.endTag, time, true); else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; } } @@ -177,8 +177,8 @@ export class CollectionStackedTimeline extends CollectionSubView l.anchor1 === link || l.anchor2 === link).forEach(l => { const { la1, la2 } = this.getLinkData(l); - const startTime = NumCast(la1.anchorStartTime, NumCast(la2.anchorStartTime, null)); - const endTime = NumCast(la1.anchorEndTime, NumCast(la2.anchorEndTime, null)); + const startTime = this._stackedTimeline.current?.anchorStart(la1) || this._stackedTimeline.current?.anchorStart(la2); + const endTime = this._stackedTimeline.current?.anchorEnd(la1) || this._stackedTimeline.current?.anchorEnd(la2); if (startTime !== undefined) { if (this.layoutDoc.playOnSelect) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime); else this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime; @@ -351,7 +351,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent if (audio) { audio.mediaStart = "manual"; audio.mediaStop = "manual"; - audio.presStartTime = NumCast(doc.anchorStartTime); - audio.presEndTime = NumCast(doc.anchorEndTime); - audio.presDuration = NumCast(doc.anchorEndTime) - NumCast(doc.anchorStartTime); + audio.presStartTime = NumCast(doc.audioStart, NumCast(doc.videoStart)); + audio.presEndTime = NumCast(doc.audioEnd, NumCast(doc.videoEnd)); + audio.presDuration = audio.presStartTime - audio.presEndTime; TabDocView.PinDoc(audio, { audioRange: true }); setTimeout(() => this.removeDocument(doc), 0); return false; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index c1cf858c0..ee5fffcc6 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -480,8 +480,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - const startTime = NumCast(doc.anchorStartTime, NumCast(doc._timecodeToShow)); - const endTime = NumCast(doc.anchorEndTime, NumCast(doc._timecodeToHide, null)); + const startTime = this._stackedTimeline.current?.anchorStart(doc) || 0; + const endTime = this._stackedTimeline.current?.anchorEnd(doc); if (startTime !== undefined) { if (this.layoutDoc.playOnSelect) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime); else this.Seek(startTime); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 36d268fe9..96c34860b 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -361,7 +361,7 @@ 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(la1.anchorStartTime, NumCast(la2.anchorStartTime)); + this._linkTime = NumCast(la1.audioStart, NumCast(la2.audioStart)); audioState = la2.audioState; if (Doc.AreProtosEqual(la2, this.dataDoc)) { la1 = l.anchor2 as Doc; diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index d52f3a928..b10c2b015 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -21,8 +21,6 @@ export const documentSchema = createSchema({ _currentTimecode: "number", // current play back time of a temporal document (video / audio) _timecodeToShow: "number", // the time that a document should be displayed (e.g., time an annotation should be displayed on a video) isLabel: "boolean", // whether the document is a label or not (video / audio) - anchorStartTime: "number", // the time code where a document activates (eg in Audio or video timelines) - anchorEndTime: "number", // the time code where a document deactivates 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 -- cgit v1.2.3-70-g09d2 From ecacd10cd3f70013d7112f5742eb4168e898949e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jan 2021 14:36:28 -0500 Subject: fixed so marquee select doesn't happen when zoomed in on an image/video --- src/client/views/nodes/ImageBox.tsx | 16 +++++++--------- src/client/views/nodes/VideoBox.tsx | 2 +- src/client/views/nodes/WebBox.tsx | 4 +--- 3 files changed, 9 insertions(+), 13 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 649fe8f40..1da3a2779 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,7 +1,8 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, runInAction, reaction, IReactionDisposer } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; -import { DataSym, Doc, DocListCast, HeightSym, WidthSym } from '../../../fields/Doc'; +import { Dictionary } from 'typescript-collections'; +import { DataSym, Doc, DocListCast, WidthSym } from '../../../fields/Doc'; import { documentSchema } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -11,7 +12,7 @@ import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; import { AudioField, ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; -import { emptyFunction, returnOne, returnZero, Utils, OmitKeys } from '../../../Utils'; +import { emptyFunction, OmitKeys, returnOne, Utils } from '../../../Utils'; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Docs } from '../../documents/Documents'; @@ -22,15 +23,12 @@ import { ContextMenu } from "../../views/ContextMenu"; import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; import { ContextMenuProps } from '../ContextMenuItem'; import { ViewBoxAnnotatableComponent } from '../DocComponent'; +import { MarqueeAnnotator } from '../MarqueeAnnotator'; +import { StyleProp } from '../StyleProvider'; import { FaceRectangles } from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); -import { StyleProp } from '../StyleProvider'; -import { AnchorMenu } from '../pdf/AnchorMenu'; -import { Dictionary } from 'typescript-collections'; -import { MarqueeAnnotator } from '../MarqueeAnnotator'; -import { Annotation } from '../pdf/Annotation'; const path = require('path'); const { Howl } = require('howler'); @@ -423,7 +421,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; + if (!e.altKey && e.button === 0 && this.props.Document._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; } @action finishMarquee = () => { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index ee5fffcc6..63e463e8b 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -535,7 +535,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; + if (!e.altKey && e.button === 0 && this.props.Document._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; }); finishMarquee = action(() => { diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 37f268823..4b7f0bf77 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -462,9 +462,7 @@ export class WebBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.active(true)) { - this._marqueeing = [e.clientX, e.clientY]; - } + if (!e.altKey && e.button === 0 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; } @action -- cgit v1.2.3-70-g09d2 From 32361454f6582155bc84c154dc9315aa794b359e Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jan 2021 18:06:05 -0500 Subject: cleaned up video/audio/stackedTimeline css. restored SpaceKey trigger to start/stop creating an anchor. --- src/Utils.ts | 12 +- .../collections/CollectionStackedTimeline.scss | 407 +++------------------ .../collections/CollectionStackedTimeline.tsx | 213 +++++------ src/client/views/nodes/AudioBox.scss | 224 +----------- src/client/views/nodes/AudioBox.tsx | 43 +-- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/VideoBox.scss | 132 +------ src/client/views/nodes/VideoBox.tsx | 11 +- 8 files changed, 190 insertions(+), 854 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 3cf695a30..dcfb579ca 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -600,11 +600,16 @@ export function hasDescendantTarget(x: number, y: number, target: HTMLDivElement return entered; } +export function StopEvent(e: React.PointerEvent | React.MouseEvent) { + e.stopPropagation(); + e.preventDefault(); +} + export function setupMoveUpEvents( target: object, e: React.PointerEvent, moveEvent: (e: PointerEvent, down: number[], delta: number[]) => boolean, - upEvent: (e: PointerEvent, movement: number[]) => any, + upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any, clickEvent: (e: PointerEvent, doubleTap?: boolean) => any, stopPropagation: boolean = true, stopMovePropagation: boolean = true @@ -632,8 +637,9 @@ export function setupMoveUpEvents( const _upEvent = (e: PointerEvent): void => { (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < 300); (target as any)._lastTap = Date.now(); - upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY]); - if (Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4) { + const isClick = Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4; + upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY], isClick); + if (isClick) { if ((target as any)._doubleTime && (target as any)._doubleTap) { clearTimeout((target as any)._doubleTime); (target as any)._doubleTime = undefined; diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 1bb5bc720..9d1b46f27 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -1,373 +1,62 @@ -.audiobox-container, -.audiobox-container-interactive { +.collectionStackedTimeline { + position: absolute; width: 100%; height: 100%; - position: inherit; - display: flex; - position: relative; - cursor: default; - - .audiobox-inner { - width:100%; - height: 100%; - } - - .audiobox-buttons { - display: flex; - width: 100%; - align-items: center; - height: 100%; - - .audiobox-dictation { - position: relative; - width: 30px; - height: 100%; - align-items: center; - display: inherit; - background: dimgray; - left: 0px; - } - - .audiobox-dictation:hover { - color: white; - cursor: pointer; - } - } - - .audiobox-handle { - width: 20px; - height: 100%; - display: inline-block; - } - - .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 { - pointer-events: all; - width: 100%; + border: gray solid 1px; + border-radius: 3px; + z-index: 1000; + overflow: hidden; + top: 0px; + + .collectionStackedTimeline-selector { + position: absolute; + width: 10px; + top: 2.5%; + height: 95%; + background: lightblue; + border-radius: 5px; + opacity: 0.3; + z-index: 500; + border-style: solid; + border-color: darkblue; + border-width: 1px; + } + + .collectionStackedTimeline-current { + width: 1px; height: 100%; - position: relative; + background-color: red; + position: absolute; + top: 0px; pointer-events: none; } - .audiobox-record-interactive { - pointer-events: all; - 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; + .collectionStackedTimeline-marker-timeline { + position: absolute; + top: 2.5%; + height: 95%; + border-radius: 4px; + opacity: 0.3; + &:hover { + opacity: 1; } - } - - .audiobox-controls { - width: 100%; - height: 100%; - position: relative; - display: flex; - padding-left: 2px; - background: black; - .audiobox-dictation { + .collectionStackedTimeline-left-resizer, + .collectionStackedTimeline-resizer { + background: dimgrey; position: absolute; - width: 30px; + top: 0; height: 100%; - align-items: center; - display: inherit; - background: dimgray; - left: 0px; + width: 10px; + pointer-events: all; + cursor: ew-resize; + z-index: 100; } - - .audiobox-player { - margin-top: auto; - margin-bottom: auto; - width: 100%; - position: relative; - padding-right: 5px; - display: flex; - - .audiobox-playhead { - position: relative; - margin-top: auto; - margin-bottom: auto; - margin-right: 2px; - height: 25px; - padding: 2px; - border-radius: 50%; - background-color: black; - color: white; - } - - .audiobox-playhead:hover { - // background-color: black; - // border-radius: 5px; - background-color: grey; - color: lightgrey; - } - - .audiobox-dictation { - position: relative; - margin-top: auto; - margin-bottom: auto; - width: 25px; - padding: 2px; - align-items: center; - display: inherit; - background: dimgray; - } - - .audiobox-timeline { - position: absolute; - width: 100%; - border: gray solid 1px; - border-radius: 3px; - z-index: 1000; - overflow: hidden; - - .audiobox-container { - position: absolute; - width: 10px; - top: 2.5%; - height: 0px; - background: lightblue; - border-radius: 5px; - // box-shadow: black 2px 2px 1px; - opacity: 0.3; - z-index: 500; - border-style: solid; - border-color: darkblue; - border-width: 1px; - } - - .audiobox-current { - width: 1px; - height: 100%; - background-color: red; - position: absolute; - top: 0px; - } - - .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-linker, - .audiobox-linker-mini { - position: absolute; - width: 15px; - min-height: 10px; - height: 15px; - margin-left: -2.55px; - background: gray; - border-radius: 100%; - opacity: 0.9; - box-shadow: black 2px 2px 1px; - - .linkAnchorBox-cont { - position: relative !important; - height: 100% !important; - width: 100% !important; - left: unset !important; - top: unset !important; - } - } - - .audiobox-linker-mini { - width: 8px; - min-height: 8px; - height: 8px; - box-shadow: black 1px 1px 1px; - margin-left: -1; - margin-top: -2; - - .linkAnchorBox-cont { - position: relative !important; - height: 100% !important; - width: 100% !important; - left: unset !important; - top: unset !important; - } - } - - .audiobox-linker:hover, - .audiobox-linker-mini:hover { - opacity: 1; - } - - .audiobox-marker-container, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 10px; - top: 2.5%; - background: gray; - border-radius: 50%; - box-shadow: black 2px 2px 1px; - overflow: visible; - cursor: pointer; - - .audiobox-marker { - position: relative; - height: 100%; - // height: calc(100% - 15px); - width: 100%; - //margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - } - - .audiobox-marker-timeline, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 90%; - top: 2.5%; - border-radius: 5px; - - .left-resizer { - background: dimgrey; - } - .resizer { - background: dimgrey; - } - - .audiobox-marker { - position: relative; - height: calc(100% - 15px); - margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - - .resizer { - position: absolute; - top: 0; - right: 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - - .click { - position: relative; - height: 100%; - width: 100%; - z-index: 100; - } - - .left-resizer { - position: absolute; - left: 0; - top : 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - } - - .audiobox-marker-timeline:hover, - .audiobox-marker-minicontainer:hover { - opacity: 0.8; - } - - .audiobox-marker-minicontainer { - width: 5px; - border-radius: 1px; - - .audiobox-marker { - position: relative; - height: calc(100% - 8px); - margin-top: 8px; - } - } - } + .collectionStackedTimeline-resizer { + right: 0; + } + .collectionStackedTimeline-left-resizer { + left: 0; } - } -} - -@media only screen and (max-device-width: 480px) { - .audiobox-dictation { - font-size: 5em; - display: flex; - width: 100; - justify-content: center; - flex-direction: column; - align-items: center; - } - - .audiobox-container .audiobox-record, - .audiobox-container-interactive .audiobox-record { - font-size: 3em; - } - - .audiobox-container .audiobox-controls .audiobox-player .audiobox-playhead, - .audiobox-container .audiobox-controls .audiobox-player .audiobox-dictation, - .audiobox-container-interactive .audiobox-controls .audiobox-player .audiobox-playhead { - width: 70px; } } \ No newline at end of file diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 1d7e40e96..11a74c4bc 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -8,15 +8,15 @@ 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 } from "../../../Utils"; +import { emptyFunction, formatTime, OmitKeys, returnFalse, setupMoveUpEvents, StopEvent } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { Scripting } from "../../util/Scripting"; import { SelectionManager } from "../../util/SelectionManager"; +import { undoBatch } from "../../util/UndoManager"; import { CollectionSubView } from "../collections/CollectionSubView"; import { DocumentView } from "../nodes/DocumentView"; import { LabelBox } from "../nodes/LabelBox"; import "./CollectionStackedTimeline.scss"; -import { undoBatch } from "../../util/UndoManager"; type PanZoomDocument = makeInterface<[]>; const PanZoomDocument = makeInterface(); @@ -35,28 +35,26 @@ export type CollectionStackedTimelineProps = { @observer export class CollectionStackedTimeline extends CollectionSubView(PanZoomDocument) { + @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; static RangeScript: ScriptField; static LabelScript: ScriptField; static RangePlayScript: ScriptField; static LabelPlayScript: ScriptField; - _disposers: { [name: string]: IReactionDisposer } = {}; - _doubleTime: NodeJS.Timeout | undefined; // bcz: Hack! this must be called _doubleTime since setupMoveDragEvents will use that field name - _ele: HTMLAudioElement | null = null; - _start: number = 0; - _left: boolean = false; - _dragging = false; - _play: any = null; - _audioRef = React.createRef(); - _timeline: Opt; - _markerStart: number = 0; - _currAnchor: Opt; - - @observable static SelectingRegion: CollectionStackedTimeline | undefined = undefined; + private _doubleTime: NodeJS.Timeout | undefined; // bcz: Hack! this must be called _doubleTime since setupMoveDragEvents will use that field name + private _timeline: HTMLDivElement | null = null; + private _markerStart: number = 0; @observable _markerEnd: number = 0; - @observable _position: number = 0; + + get duration() { return this.props.duration; } @computed get anchorDocs() { return this.childDocs; } - @computed get currentTime() { return NumCast(this.props.Document._currentTimecode); } + @computed get currentTime() { return NumCast(this.layoutDoc._currentTimecode); } + @computed get selectionContainer() { + return CollectionStackedTimeline.SelectingRegion !== this ? (null) :
; + } constructor(props: any) { super(props); @@ -67,28 +65,37 @@ export class CollectionStackedTimeline extends CollectionSubView { - if (e.target instanceof HTMLInputElement) return; - if (!this.props.playing()) return; // can't create if video is not playing - switch (e.key) { - case "x": // currently set to x, but can be a different key - const currTime = this.currentTime; - if (this._start) { - this._markerStart = currTime; - // this._start = false; - // this._visible = true; - } else { - this.createAnchor(this._markerStart, currTime); - // this._start = true; - // this._visible = false; - } - } + componentWillUnmount() { + document.removeEventListener("keydown", this.keyEvents, true); + if (CollectionStackedTimeline.SelectingRegion === this) CollectionStackedTimeline.SelectingRegion = undefined; } anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val)); + toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration)); + rangeClickScript = () => CollectionStackedTimeline.RangeScript; + labelClickScript = () => CollectionStackedTimeline.LabelScript; + rangePlayScript = () => CollectionStackedTimeline.RangePlayScript; + labelPlayScript = () => CollectionStackedTimeline.LabelPlayScript; + + // for creating key anchors with key events + @action + keyEvents = (e: KeyboardEvent) => { + if (!(e.target instanceof HTMLInputElement) && this.props.isSelected(true)) { + switch (e.key) { + case " ": + if (!CollectionStackedTimeline.SelectingRegion) { + this._markerStart = this._markerEnd = this.currentTime; + CollectionStackedTimeline.SelectingRegion = this; + } else { + this.createAnchor(this._markerStart, this.currentTime); + CollectionStackedTimeline.SelectingRegion = undefined; + } + } + } + } getLinkData(l: Doc) { let la1 = l.anchor1 as Doc; @@ -101,69 +108,48 @@ export class CollectionStackedTimeline extends CollectionSubView { - this._timeline = timeline; - } - - // updates the anchor with the new time - @action - changeAnchor = (anchor: Opt, time: number) => { - if (anchor) { - const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; - if (timelineOnly) Doc.SetInPlace(anchor, this._left ? this.props.startTag : this.props.endTag, time, true); - else this._left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; - } - } - - // checks if the two anchors are the same with start and end time - isSame = (m1: any, m2: any) => { - return this.anchorStart(m1) === this.anchorStart(m2) && this.anchorEnd(m1) === this.anchorEnd(m2); - } - - @computed get selectionContainer() { - return CollectionStackedTimeline.SelectingRegion !== this ? (null) :
; - } - // starting the drag event for anchor resizing @action onPointerDownTimeline = (e: React.PointerEvent): void => { - const rect = this._timeline?.getBoundingClientRect();// (e.target as any).getBoundingClientRect(); - if (rect && e.target !== this._audioRef.current && this.props.active()) { + const rect = this._timeline?.getBoundingClientRect(); + const clientX = e.clientX; + if (rect && this.props.active()) { const wasPlaying = this.props.playing(); if (wasPlaying) this.props.Pause(); else if (!this._doubleTime) { this._doubleTime = setTimeout(() => { this._doubleTime = undefined; - this.props.setTime((e.clientX - rect.x) / rect.width * this.props.duration); - }, 300); + this.props.setTime((clientX - rect.x) / rect.width * this.duration); + }, 300); // 300ms is double-tap timeout + } + const wasSelecting = CollectionStackedTimeline.SelectingRegion === this; + if (!wasSelecting) { + this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); + CollectionStackedTimeline.SelectingRegion = this; } - this._markerStart = this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); - CollectionStackedTimeline.SelectingRegion = this; setupMoveUpEvents(this, e, action(e => { this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); return false; }), - action((e, movement) => { + action((e, movement, isClick) => { this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); if (this._markerEnd < this._markerStart) { const tmp = this._markerStart; this._markerStart = this._markerEnd; this._markerEnd = tmp; } - CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd); - CollectionStackedTimeline.SelectingRegion = undefined; + if (!isClick) { + CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && this.createAnchor(this._markerStart, this._markerEnd); + } + (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined); }), (e, doubleTap) => { this.props.select(false); e.shiftKey && this.createAnchor(this.currentTime); !wasPlaying && doubleTap && this.props.Play(); - } - , this.props.isSelected(true) || this.props.isChildActive()); + }, + this.props.isSelected(true) || this.props.isChildActive()); } } @@ -227,36 +213,33 @@ export class CollectionStackedTimeline extends CollectionSubView Math.max(0, Math.min(this.props.duration, screen_delta / width * this.props.duration)); // starting the drag event for anchor resizing - onPointerDown = (e: React.PointerEvent, m: Doc, left: boolean): void => { - this._currAnchor = m; - this._left = left; + onAnchorDown = (e: React.PointerEvent, anchor: Doc, left: boolean): void => { this._timeline?.setPointerCapture(e.pointerId); + const newTime = (e: PointerEvent) => { + const rect = (e.target as any).getBoundingClientRect(); + return this.toTimeline(e.clientX - rect.x, rect.width); + } + const changeAnchor = (anchor: Doc, left: boolean, time: number) => { + const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; + if (timelineOnly) Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true); + else left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; + return false; + } setupMoveUpEvents(this, e, + (e) => changeAnchor(anchor, left, newTime(e)), (e) => { - const rect = (e.target as any).getBoundingClientRect(); - this.changeAnchor(this._currAnchor, this.toTimeline(e.clientX - rect.x, rect.width)); - return false; - }, - (e) => { - const rect = (e.target as any).getBoundingClientRect(); - this.props.setTime(this.toTimeline(e.clientX - rect.x, rect.width)); + this.props.setTime(newTime(e)); this._timeline?.releasePointerCapture(e.pointerId); }, emptyFunction); } - rangeClickScript = () => CollectionStackedTimeline.RangeScript; - labelClickScript = () => CollectionStackedTimeline.LabelScript; - rangePlayScript = () => CollectionStackedTimeline.RangePlayScript; - labelPlayScript = () => CollectionStackedTimeline.LabelPlayScript; - // makes sure no anchors overlaps each other by setting the correct position and width getLevel = (m: Doc, placed: { anchorStartTime: number, anchorEndTime: number, level: number }[]) => { const timelineContentWidth = this.props.PanelWidth(); const x1 = this.anchorStart(m); - const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.props.duration); + const x2 = this.anchorEnd(m, x1 + 10 / timelineContentWidth * this.duration); let max = 0; const overlappedLevels = new Set(placed.map(p => { const y1 = p.anchorStartTime; @@ -281,18 +264,15 @@ export class CollectionStackedTimeline extends CollectionSubView anchor.view = r)} Document={mark} DataDoc={undefined} - PanelWidth={() => width} - PanelHeight={() => height} renderDepth={this.props.renderDepth + 1} - focus={() => this.props.playLink(mark)} - rootSelected={returnFalse} LayoutTemplate={undefined} LayoutTemplateString={LabelBox.LayoutString("data")} - ContainingCollectionDoc={this.props.Document} - removeDocument={this.props.removeDocument} + PanelWidth={() => width} + PanelHeight={() => height} ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(-x, -y)} - parentActive={(out) => this.props.isSelected(out) || this.props.isChildActive()} - whenActiveChanged={this.props.whenActiveChanged} + focus={() => this.props.playLink(mark)} + parentActive={out => this.props.isSelected(out) || this.props.isChildActive()} + rootSelected={returnFalse} onClick={script} onDoubleClick={this.props.Document.autoPlay ? undefined : doublescript} ignoreAutoHeight={false} @@ -307,8 +287,8 @@ export class CollectionStackedTimeline extends CollectionSubView -
this.onPointerDown(e, mark, true)} /> -
this.onPointerDown(e, mark, false)} /> +
this.onAnchorDown(e, mark, true)} /> +
this.onAnchorDown(e, mark, false)} /> } ; }); @@ -319,39 +299,28 @@ export class CollectionStackedTimeline extends CollectionSubView ({ level: this.getLevel(anchor, overlaps), anchor })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; - return
{ - if (this.props.isChildActive() || this.props.isSelected(false)) { - e.stopPropagation(); e.preventDefault(); - } - }} - onPointerDown={e => { - if (this.props.isChildActive() || this.props.isSelected(false)) { - e.button === 0 && !e.ctrlKey && this.onPointerDownTimeline(e); - } - }}> + const isActive = this.props.isChildActive() || this.props.isSelected(false); + return
this._timeline = timeline} + onClick={e => isActive && StopEvent(e)} onPointerDown={e => isActive && this.onPointerDownTimeline(e)}> {drawAnchors.map(d => { - const m = d.anchor; - const start = this.anchorStart(m); - const end = this.anchorEnd(m, start + 10 / timelineContentWidth * this.props.duration); - const left = start / this.props.duration * timelineContentWidth; + 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 timespan = end - start; return this.props.Document.hideAnchors ? (null) : -
{ this.props.playFrom(start, this.anchorEnd(m)); e.stopPropagation(); }} > - {this.renderAnchor(m, this.rangeClickScript, this.rangePlayScript, +
{ this.props.playFrom(start, this.anchorEnd(d.anchor)); e.stopPropagation(); }} > + {this.renderAnchor(d.anchor, this.rangeClickScript, this.rangePlayScript, left, top, - timelineContentWidth * timespan / this.props.duration, + timelineContentWidth * timespan / this.duration, timelineContentHeight / maxLevel)}
; })} {this.selectionContainer} -
{ e.stopPropagation(); e.preventDefault(); }} - style={{ left: `${this.currentTime / this.props.duration * 100}%`, pointerEvents: "none" }} - /> +
; } } diff --git a/src/client/views/nodes/AudioBox.scss b/src/client/views/nodes/AudioBox.scss index 4a3bbf8d8..fc881ca25 100644 --- a/src/client/views/nodes/AudioBox.scss +++ b/src/client/views/nodes/AudioBox.scss @@ -7,11 +7,6 @@ position: relative; cursor: default; - .audiobox-inner { - width:100%; - height: 100%; - } - .audiobox-buttons { display: flex; width: 100%; @@ -26,20 +21,13 @@ display: inherit; background: dimgray; left: 0px; - } - - .audiobox-dictation:hover { - color: white; - cursor: pointer; + &:hover { + color: white; + cursor: pointer; + } } } - .audiobox-handle { - width: 20px; - height: 100%; - display: inline-block; - } - .audiobox-control, .audiobox-control-interactive { top: 0; @@ -53,21 +41,16 @@ pointer-events: all; } + .audiobox-record-interactive, .audiobox-record { pointer-events: all; width: 100%; height: 100%; position: relative; - pointer-events: none; } - .audiobox-record-interactive { - pointer-events: all; - width: 100%; - height: 100%; - position: relative; - - + .audiobox-record { + pointer-events: none; } .recording { @@ -95,10 +78,9 @@ margin-bottom: auto; width: 25px; padding: 5px; - } - - .buttons:hover { - background-color: crimson; + &:hover{ + background-color: crimson; + } } } @@ -138,13 +120,10 @@ border-radius: 50%; background-color: black; color: white; - } - - .audiobox-playhead:hover { - // background-color: black; - // border-radius: 5px; - background-color: grey; - color: lightgrey; + &:hover { + background-color: grey; + color: lightgrey; + } } .audiobox-dictation { @@ -166,29 +145,6 @@ z-index: 1000; overflow: hidden; - .audiobox-container { - position: absolute; - width: 10px; - top: 2.5%; - height: 0px; - background: lightblue; - border-radius: 5px; - // box-shadow: black 2px 2px 1px; - opacity: 0.3; - z-index: 500; - border-style: solid; - border-color: darkblue; - border-width: 1px; - } - - .audiobox-current { - width: 1px; - height: 100%; - background-color: red; - position: absolute; - top: 0px; - } - .waveform { position: relative; width: 100%; @@ -206,161 +162,21 @@ width: 100% !important; } } - - .audiobox-linker, - .audiobox-linker-mini { - position: absolute; - width: 15px; - min-height: 10px; - height: 15px; - margin-left: -2.55px; - background: gray; - border-radius: 100%; - opacity: 0.9; - box-shadow: black 2px 2px 1px; - - .linkAnchorBox-cont { - position: relative !important; - height: 100% !important; - width: 100% !important; - left: unset !important; - top: unset !important; - } - } - - .audiobox-linker-mini { - width: 8px; - min-height: 8px; - height: 8px; - box-shadow: black 1px 1px 1px; - margin-left: -1; - margin-top: -2; - - .linkAnchorBox-cont { - position: relative !important; - height: 100% !important; - width: 100% !important; - left: unset !important; - top: unset !important; - } - } - - .audiobox-linker:hover, - .audiobox-linker-mini:hover { - opacity: 1; - } - - .audiobox-marker-container, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 10px; - top: 2.5%; - background: gray; - border-radius: 50%; - box-shadow: black 2px 2px 1px; - overflow: visible; - cursor: pointer; - - .audiobox-marker { - position: relative; - height: 100%; - // height: calc(100% - 15px); - width: 100%; - //margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - } - - .audiobox-marker-timeline, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 90%; - top: 2.5%; - border-radius: 5px; - - .left-resizer { - background: dimgrey; - } - .resizer { - background: dimgrey; - } - - .audiobox-marker { - position: relative; - height: calc(100% - 15px); - margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - - .resizer { - position: absolute; - top: 0; - right: 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - - .click { - position: relative; - height: 100%; - width: 100%; - z-index: 100; - } - - .left-resizer { - position: absolute; - left: 0; - top : 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - } - - .audiobox-marker-timeline:hover, - .audiobox-marker-minicontainer:hover { - opacity: 0.8; - } - - .audiobox-marker-minicontainer { - width: 5px; - border-radius: 1px; - - .audiobox-marker { - position: relative; - height: 100%; - margin-top: 8px; - } - } } - .current-time { + .audioBox-total-time, + .audioBox-current-time { position: absolute; font-size: 8; top: 100%; - left: 30px; color: white; } + .audioBox-current-time { + left: 30px; + } - .total-time { - position: absolute; - top: 100%; - font-size: 8; + .audioBox-total-time { right: 2px; - color: white; } } } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 741a03900..9f343e904 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -81,7 +81,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent this._isChildActive; + timelineWhenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(runInAction(() => this._isChildActive = isActive)); + timelineScreenToLocal = () => this.props.ScreenToLocalTransform().translate(-AudioBox.playheadWidth, -(100 - this.heightPercent) / 200 * this.props.PanelHeight()); + setAnchorTime = (time: number) => this._ele!.currentTime = this.layoutDoc._currentTimecode = time; + timelineHeight = () => this.props.PanelHeight() * this.heightPercent / 100 * this.heightPercent / 100; // panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) + timelineWidth = () => this.props.PanelWidth() - AudioBox.playheadWidth; @computed get renderTimeline() { - return this._ele!.currentTime = this.layoutDoc._currentTimecode = time} + setTime={this.setAnchorTime} playing={this.playing} - select={this.props.select} - isSelected={this.props.isSelected} - whenActiveChanged={action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive))} + whenActiveChanged={this.timelineWhenActiveChanged} removeDocument={this.removeDocument} - ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().translate(0, -(100 - this.heightPercent) / 200 * this.props.PanelHeight())} - isChildActive={() => this._isChildActive} + ScreenToLocalTransform={this.timelineScreenToLocal} + isChildActive={this.isActiveChild} Play={this.Play} Pause={this.Pause} active={this.active} playLink={this.playLink} - PanelWidth={this.props.PanelWidth} - PanelHeight={() => this.props.PanelHeight() * this.heightPercent / 100 * this.heightPercent / 100}// panelHeight * heightPercent is player height. * heightPercent is timeline height (as per css inline) + PanelWidth={this.timelineWidth} + PanelHeight={this.timelineHeight} />; } @@ -413,17 +406,17 @@ export class AudioBox extends ViewBoxAnnotatableComponent
-
+
{this.waveform}
+ {this.renderTimeline}
- {this.renderTimeline} {this.audio} -
+
{formatTime(Math.round(NumCast(this.layoutDoc._currentTimecode)))}
-
+
{formatTime(Math.round(this.duration))}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 1da3a2779..92d6e2612 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -421,7 +421,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.props.Document._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; } @action finishMarquee = () => { diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index ac4d64f12..b9123587b 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -10,138 +10,8 @@ .inkingCanvas-paths-markers { opacity : 0.4; // we shouldn't have to do this, but since chrome crawls to a halt with z-index unset in videoBox-content, this is a workaround } - - .audiobox-timeline { - position: absolute; - width: 100%; + .collectionStackedTimeline { background: beige; - border: gray solid 1px; - border-radius: 3px; - z-index: 1000; - overflow: hidden; - bottom: 0; - - .audiobox-current { - width: 1px; - height: 100%; - background-color: red; - position: absolute; - top: 0px; - } - - .audiobox-container { - position: absolute; - width: 10px; - top: 2.5%; - height: 0px; - background: lightblue; - border-radius: 5px; - // box-shadow: black 2px 2px 1px; - opacity: 0.3; - z-index: 500; - border-style: solid; - border-color: darkblue; - border-width: 1px; - } - - .audiobox-marker-timeline, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 10px; - top: 2.5%; - border-radius: 50%; - box-shadow: black 2px 2px 1px; - overflow: visible; - cursor: pointer; - - .left-resizer { - background: dimgrey; - } - .resizer { - background: dimgrey; - } - .audiobox-marker { - position: relative; - height: 100%; - // height: calc(100% - 15px); - width: 100%; - //margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - } - - .audiobox-marker-timeline, - .audiobox-marker-minicontainer { - position: absolute; - width: 10px; - height: 90%; - top: 2.5%; - border-radius: 5px; - box-shadow: black 2px 2px 1px; - - .audiobox-marker { - position: relative; - height: calc(100% - 15px); - margin-top: 15px; - } - - .audio-marker:hover { - border: orange 2px solid; - } - - .resizer { - position: absolute; - top: 0; - right: 0; - pointer-events: all; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - - .click { - position: relative; - height: 100%; - width: 100%; - z-index: 100; - } - - .left-resizer { - position: absolute; - left: 0; - top: 0; - cursor: ew-resize; - height: 100%; - width: 10px; - z-index: 100; - } - - // .contentFittingDocumentView-previewDoc { - // width: 100% !important; - // transform: none !important; - // } - } - - .audiobox-marker-container1:hover, - .audiobox-marker-minicontainer:hover { - opacity: 0.8; - } - - .audiobox-marker-minicontainer { - width: 5px; - border-radius: 1px; - - .audiobox-marker { - position: relative; - height: calc(100% - 8px); - margin-top: 8px; - } - } } } diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 63e463e8b..c360a924e 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -7,7 +7,7 @@ import { Dictionary } from "typescript-collections"; import { Doc, DocListCast } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; -import { createSchema, makeInterface } from "../../../fields/Schema"; +import { makeInterface } from "../../../fields/Schema"; import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { VideoField } from "../../../fields/URLField"; import { emptyFunction, formatTime, OmitKeys, returnOne, setupMoveUpEvents, Utils } from "../../../Utils"; @@ -86,13 +86,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - VideoBox.Instance._stackedTimeline.current?.keyEvents(e); - } - @action public Play = (update: boolean = true) => { - document.removeEventListener("keydown", VideoBox.keyEventsWrapper, true); - document.addEventListener("keydown", VideoBox.keyEventsWrapper, true); this._playing = true; try { update && this.player?.play(); @@ -248,7 +242,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent this._disposers[d]?.()); - document.removeEventListener("keydown", VideoBox.keyEventsWrapper, true); } @action @@ -535,7 +528,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - if (!e.altKey && e.button === 0 && this.props.Document._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; + if (!e.altKey && e.button === 0 && this.layoutDoc._viewScale === 1 && this.active(true)) this._marqueeing = [e.clientX, e.clientY]; }); finishMarquee = action(() => { -- cgit v1.2.3-70-g09d2 From 13fa5643246233c6f6a1fb232ef3dadbd7d8fa08 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jan 2021 18:36:54 -0500 Subject: videoBox cleanup. made video annotations default to last for 1 second. --- src/client/views/nodes/VideoBox.tsx | 49 ++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 28 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index c360a924e..57077d113 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -418,11 +418,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - setupMoveUpEvents(this, e, (e: PointerEvent) => { - this.Seek(Math.max(0, (this.layoutDoc._currentTimecode || 0) + Math.sign(e.movementX) * 0.0333)); - e.stopImmediatePropagation(); - return false; - }, emptyFunction, + setupMoveUpEvents(this, e, + (e: PointerEvent) => { + this.Seek(Math.max(0, (this.layoutDoc._currentTimecode || 0) + Math.sign(e.movementX) * 0.0333)); + e.stopImmediatePropagation(); + return false; + }, + emptyFunction, (e: PointerEvent) => this.layoutDoc._currentTimecode = 0); } @@ -441,7 +443,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent doc._timecodeToShow = curTime); + docs.forEach(doc => doc._timecodeToHide = (doc._timecodeToShow = curTime) + 1); return this.addDocument(doc); } @@ -481,44 +483,35 @@ export class VideoBox extends ViewBoxAnnotatableComponent this._playing; + isActiveChild = () => this._isChildActive; + timelineWhenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(runInAction(() => this._isChildActive = isActive)); + timelineScreenToLocal = () => this.props.ScreenToLocalTransform().scale(this.scaling()).translate(0, -this.heightPercent / 100 * this.props.PanelHeight()); + setAnchorTime = (time: number) => this.player!.currentTime = this.layoutDoc._currentTimecode = time; + timelineHeight = () => this.props.PanelHeight() * (100 - this.heightPercent) / 100; @computed get renderTimeline() { return
- this.player!.currentTime = this.layoutDoc._currentTimecode = time} - playing={() => this._playing} - select={this.props.select} - isSelected={this.props.isSelected} - whenActiveChanged={action((isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive))} + setTime={this.setAnchorTime} + playing={this.playing} + whenActiveChanged={this.timelineWhenActiveChanged} removeDocument={this.removeDocument} - ScreenToLocalTransform={() => this.props.ScreenToLocalTransform().scale(this.scaling()).translate(0, -this.heightPercent / 100 * this.props.PanelHeight())} - isChildActive={() => this._isChildActive} + ScreenToLocalTransform={this.timelineScreenToLocal} + isChildActive={this.isActiveChild} Play={this.Play} Pause={this.Pause} active={this.active} playLink={this.playLink} - PanelWidth={this.props.PanelWidth} - PanelHeight={() => this.props.PanelHeight() * (100 - this.heightPercent) / 100} + PanelHeight={this.timelineHeight} />
; } -- cgit v1.2.3-70-g09d2 From b11652e06205bf214f9504330df3980af643a7cc Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 28 Jan 2021 20:51:39 -0500 Subject: fixed up double click behavior in general but specifically for Audio/Video timelines --- src/Utils.ts | 15 ++++++++---- .../collections/CollectionStackedTimeline.tsx | 27 ++++++++-------------- .../collectionFreeForm/CollectionFreeFormView.tsx | 5 ++-- 3 files changed, 23 insertions(+), 24 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index dcfb579ca..39fc3dae4 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -612,10 +612,20 @@ export function setupMoveUpEvents( upEvent: (e: PointerEvent, movement: number[], isClick: boolean) => any, clickEvent: (e: PointerEvent, doubleTap?: boolean) => any, stopPropagation: boolean = true, - stopMovePropagation: boolean = true + stopMovePropagation: boolean = true, + noDoubleTapTimeout?: () => void ) { + const doubleTapTimeout = 300; + (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < doubleTapTimeout); + (target as any)._lastTap = Date.now(); (target as any)._downX = (target as any)._lastX = e.clientX; (target as any)._downY = (target as any)._lastY = e.clientY; + if (!(target as any)._doubleTime && noDoubleTapTimeout) { + (target as any)._doubleTime = setTimeout(() => { + noDoubleTapTimeout?.(); + (target as any)._doubleTime = undefined; + }, doubleTapTimeout); + } const _moveEvent = (e: PointerEvent): void => { if (Math.abs(e.clientX - (target as any)._downX) > Utils.DRAG_THRESHOLD || Math.abs(e.clientY - (target as any)._downY) > Utils.DRAG_THRESHOLD) { @@ -633,10 +643,7 @@ export function setupMoveUpEvents( (target as any)._lastY = e.clientY; stopMovePropagation && e.stopPropagation(); }; - (target as any)._doubleTap = false; const _upEvent = (e: PointerEvent): void => { - (target as any)._doubleTap = (Date.now() - (target as any)._lastTap < 300); - (target as any)._lastTap = Date.now(); const isClick = Math.abs(e.clientX - (target as any)._downX) < 4 && Math.abs(e.clientY - (target as any)._downY) < 4; upEvent(e, [e.clientX - (target as any)._downX, e.clientY - (target as any)._downY], isClick); if (isClick) { diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 11a74c4bc..1ccf474f2 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -1,5 +1,5 @@ import React = require("react"); -import { action, computed, IReactionDisposer, observable } from "mobx"; +import { action, computed, IReactionDisposer, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; import { Doc, Opt } from "../../../fields/Doc"; @@ -41,7 +41,6 @@ export class CollectionStackedTimeline extends CollectionSubView CollectionStackedTimeline.SelectingRegion = undefined); } anchorStart = (anchor: Doc) => NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); @@ -116,19 +114,13 @@ export class CollectionStackedTimeline extends CollectionSubView { - this._doubleTime = undefined; - this.props.setTime((clientX - rect.x) / rect.width * this.duration); - }, 300); // 300ms is double-tap timeout - } const wasSelecting = CollectionStackedTimeline.SelectingRegion === this; - if (!wasSelecting) { - this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); - CollectionStackedTimeline.SelectingRegion = this; - } setupMoveUpEvents(this, e, action(e => { + if (!wasSelecting && CollectionStackedTimeline.SelectingRegion !== this) { + this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); + CollectionStackedTimeline.SelectingRegion = this; + } this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); return false; }), @@ -149,7 +141,8 @@ export class CollectionStackedTimeline extends CollectionSubView !wasPlaying && this.props.setTime((clientX - rect.x) / rect.width * this.duration)); } } @@ -219,13 +212,13 @@ export class CollectionStackedTimeline extends CollectionSubView { const rect = (e.target as any).getBoundingClientRect(); return this.toTimeline(e.clientX - rect.x, rect.width); - } + }; const changeAnchor = (anchor: Doc, left: boolean, time: number) => { const timelineOnly = Cast(anchor[this.props.startTag], "number", null) !== undefined; if (timelineOnly) Doc.SetInPlace(anchor, left ? this.props.startTag : this.props.endTag, time, true); else left ? anchor._timecodeToShow = time : anchor._timecodeToHide = time; return false; - } + }; setupMoveUpEvents(this, e, (e) => changeAnchor(anchor, left, newTime(e)), (e) => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 4d277dc1c..6c7512f7c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -818,9 +818,8 @@ export class CollectionFreeFormView extends CollectionSubView Date: Fri, 29 Jan 2021 00:00:20 -0500 Subject: fixed automatic linking to audio recordings. now it creates anchors and it inserts carriage returns properly when adding timestamps and suppressing timestamps for code blocks. --- src/client/documents/Documents.ts | 2 -- .../collections/CollectionStackedTimeline.tsx | 18 +++++----- src/client/views/nodes/AudioBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 3 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 39 ++++++++-------------- 5 files changed, 24 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 142e14ff8..1a4aae17e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -720,8 +720,6 @@ export namespace Docs { linkDocProto.treeViewOpen = true;// setting this in the instance creator would set it on the view document. linkDocProto.anchor1 = source.doc; linkDocProto.anchor2 = target.doc; - linkDocProto.anchor1_timecode = source.doc._currentTimecode || source.doc._timecodeToShow; - linkDocProto.anchor2_timecode = target.doc._currentTimecode || target.doc._timecodeToShow; if (linkDocProto.linkBoxExcludedKeys === undefined) { Cast(linkDocProto.proto, Doc, null).linkBoxExcludedKeys = new List(["treeViewExpandedView", "aliases", "treeViewHideTitle", "removeDropProperties", "linkBoxExcludedKeys", "treeViewOpen", "aliasNumber", "isPrototype", "creationDate", "author"]); diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 1ccf474f2..02e88d939 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -148,20 +148,20 @@ export class CollectionStackedTimeline extends CollectionSubView([anchor]); + dataDoc[fieldKey] = new List([anchor]); } return anchor; } diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 9f343e904..6b9d12ac0 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -90,7 +90,7 @@ export class AudioBox extends ViewBoxAnnotatableComponent { - return this._stackedTimeline.current?.createAnchor(this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) || this.rootDoc; + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "audioStart", "audioEnd", this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) || this.rootDoc; } componentWillUnmount() { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 57077d113..79d584a1d 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -60,7 +60,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - return this._stackedTimeline.current?.createAnchor(Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc; + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "videoStart", "videoEnd", Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc; } choosePath(url: string) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 96c34860b..6914c20b4 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -99,10 +99,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp private _undoTyping?: UndoManager.Batch; private _disposers: { [name: string]: IReactionDisposer } = {}; private _dropDisposer?: DragManager.DragDropDisposer; - private _first: Boolean = true; private _recordingStart: number = 0; - private _currentTime: number = 0; - private _linkTime: number | null = null; private _pause: boolean = false; private _animatingScroll: number = 0; // hack to prevent scroll values from being written to document when scroll is animating @@ -356,24 +353,11 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp // for inserting timestamps insertTime = () => { - let audioState; - if (this._first) { - DocListCast(this.dataDoc.links).map((l, i) => { - let la1 = l.anchor1 as Doc; - let la2 = l.anchor2 as Doc; - 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; - audioState = la1.audioState; - } - }); - } - this._currentTime = Date.now(); - let time; - this._linkTime ? time = this.formatTime(Math.round(this._linkTime + this._currentTime / 1000 - this._recordingStart / 1000)) : time = null; - + let linkTime; + DocListCast(this.dataDoc.links).forEach((l, i) => { + const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; + if (anchor && (anchor.annotationOn as Doc).audioState === "recording") linkTime = NumCast(anchor.audioStart); + }); if (this._editorView) { const state = this._editorView.state; const now = Date.now(); @@ -388,13 +372,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } } - if (time && audioState === "recording") { - let value = ""; + + const path = (this._editorView.state.selection.$from as any).path; + if (linkTime && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) { + const time = this.formatTime(Math.round(linkTime + Date.now() / 1000 - this._recordingStart / 1000)); this._break = false; - value = this.layoutDoc._timeStampOnEnter ? "[" + time + "] " : "\n" + "[" + time + "] "; + const value = (this.layoutDoc._timeStampOnEnter ? "" : "\n") + "[" + time + "]"; const from = state.selection.from; - const inserted = state.tr.insertText(value).addMark(from, from + value.length + 1, mark); - this._editorView.dispatch(this._editorView.state.tr.insertText(value)); + const para = this._editorView.state.schema.nodes.paragraph.create(); + const replaced = this._editorView.state.tr.insertText(value).addMark(from, from + value.length + 1, mark).insert(from + value.length, para); + this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + value.length + 1)))); } } } -- cgit v1.2.3-70-g09d2 From 80362228b691fd55b569f0f507c4ee9667644559 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Jan 2021 13:47:31 -0500 Subject: changed how auto linking to audio is implemented. added audiotag html tags to click to play audio. --- src/client/views/nodes/AudioBox.tsx | 11 +++---- .../nodes/formattedText/FormattedTextBox.scss | 11 +++++++ .../views/nodes/formattedText/FormattedTextBox.tsx | 37 ++++++++++++--------- src/client/views/nodes/formattedText/nodes_rts.ts | 38 +++++++++++++++++++++- 4 files changed, 75 insertions(+), 22 deletions(-) (limited to 'src') diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 6b9d12ac0..57b5f3ec7 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -43,7 +43,6 @@ export class AudioBox extends ViewBoxAnnotatableComponent(); _stackedTimeline = React.createRef(); _recorder: any; _recordStart = 0; @@ -105,26 +104,26 @@ export class AudioBox extends ViewBoxAnnotatableComponent AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime)); + //this._disposers.scrubbing = reaction(() => AudioBox._scrubTime, (time) => this.layoutDoc.playOnSelect && this.playFromTime(AudioBox._scrubTime)); this._disposers.triggerAudio = reaction( () => !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc && this.props.renderDepth !== -1 ? NumCast(this.Document._triggerAudio, null) : undefined, start => start !== undefined && setTimeout(() => { - this._audioRef.current && this.playFrom(start); + this.playFrom(start); setTimeout(() => { this.Document._currentTimecode = start; this.Document._triggerAudio = undefined; }, 10); - }, this._audioRef.current ? 0 : 250), // wait for mainCont and try again to play + }), // wait for mainCont and try again to play { fireImmediately: true } ); this._disposers.audioStop = reaction( () => this.props.renderDepth !== -1 && !LinkDocPreview.TargetDoc && !FormattedTextBoxComment.linkDoc ? Cast(this.Document._audioStop, "number", null) : undefined, audioStop => audioStop !== undefined && setTimeout(() => { - this._audioRef.current && this.Pause(); + this.Pause(); setTimeout(() => this.Document._audioStop = undefined, 10); - }, this._audioRef.current ? 0 : 250), // wait for mainCont and try again to play + }), // wait for mainCont and try again to play { fireImmediately: true } ); } diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index b04f60500..866f556ff 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -10,6 +10,17 @@ outline: none !important; } +audiotag { + left: 0; + position: absolute; + cursor: pointer; + border-radius: 10px; + width: 10px; + margin-top: -2px; + font-size: 4px; + background: lightblue; +} + .formattedTextBox-cont { touch-action: none; background: inherit; diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 6914c20b4..d73fd9208 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -343,20 +343,16 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp pause = () => this._pause = true; - 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'); - } - // for inserting timestamps insertTime = () => { let linkTime; + let linkAnchor; DocListCast(this.dataDoc.links).forEach((l, i) => { const anchor = (l.anchor1 as Doc).annotationOn ? l.anchor1 as Doc : (l.anchor2 as Doc).annotationOn ? (l.anchor2 as Doc) : undefined; - if (anchor && (anchor.annotationOn as Doc).audioState === "recording") linkTime = NumCast(anchor.audioStart); + if (anchor && (anchor.annotationOn as Doc).audioState === "recording") { + linkTime = NumCast(anchor.audioStart); + linkAnchor = anchor; + } }); if (this._editorView) { const state = this._editorView.state; @@ -374,14 +370,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } const path = (this._editorView.state.selection.$from as any).path; - if (linkTime && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) { - const time = this.formatTime(Math.round(linkTime + Date.now() / 1000 - this._recordingStart / 1000)); + if (linkAnchor && linkTime && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) { + const time = linkTime + Date.now() / 1000 - this._recordingStart / 1000; this._break = false; - const value = (this.layoutDoc._timeStampOnEnter ? "" : "\n") + "[" + time + "]"; const from = state.selection.from; - const para = this._editorView.state.schema.nodes.paragraph.create(); - const replaced = this._editorView.state.tr.insertText(value).addMark(from, from + value.length + 1, mark).insert(from + value.length, para); - this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + value.length + 1)))); + const value = this._editorView.state.schema.nodes.audiotag.create({ timeCode: time, audioId: linkAnchor[Id] }); + const replaced = this._editorView.state.tr.insert(from - 1, value); + this._editorView.dispatch(replaced.setSelection(new TextSelection(replaced.doc.resolve(from + 1)))); } } } @@ -1299,6 +1294,18 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp _break = false; _collapsed = false; onPointerDown = (e: React.PointerEvent): void => { + if ((e.target as any).tagName === "AUDIOTAG") { + e.preventDefault(); + e.stopPropagation(); + const time = (e.target as any)?.dataset?.timecode || 0; + const audioid = (e.target as any)?.dataset?.audioid || 0; + DocServer.GetRefField(audioid).then(anchor => { + if (anchor instanceof Doc) { + const audiodoc = anchor.annotationOn as Doc; + audiodoc._triggerAudio = Number(time); + } + }); + } if (this._recording && !e.ctrlKey && e.button === 0) { this.stopDictation(true); this._break = true; diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index 64f7d27e5..f5bc05a2d 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -6,6 +6,14 @@ import { ParagraphNodeSpec, toParagraphDOM, getParagraphNodeAttrs } from "./Para const blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; +function formatAudioTime(time: number) { + time = Math.round(time); + const hours = Math.floor(time / 60 / 60); + const minutes = Math.floor(time / 60) - (hours * 60); + const seconds = time % 60; + + return minutes.toString().padStart(2, '0') + ':' + seconds.toString().padStart(2, '0'); +} // :: Object // [Specs](#model.NodeSpec) for the nodes defined in this schema. export const nodes: { [index: string]: NodeSpec } = { @@ -14,6 +22,34 @@ export const nodes: { [index: string]: NodeSpec } = { content: "block+" }, + audiotag: { + group: "block", + attrs: { + timeCode: { default: 0 }, + audioId: { default: "" } + }, + toDOM(node) { + return ['audiotag', + { + // style: see FormattedTextBox.scss + "data-timecode": node.attrs.timeCode, + "data-audioid": node.attrs.audioId, + }, + formatAudioTime(node.attrs.timeCode.toString()) + ] + }, + parseDOM: [ + { + tag: "audiotag", getAttrs(dom: any) { + return { + timeCode: dom.getAttribute("data-timecode"), + audioId: dom.getAttribute("data-audioid") + }; + } + }, + ] + }, + footnote: { group: "inline", content: "inline*", @@ -315,7 +351,7 @@ export const nodes: { [index: string]: NodeSpec } = { mapStyle: { default: "decimal" }, // "decimal", "multi", "bullet" visibility: { default: true } }, - content: 'paragraph+ | (paragraph ordered_list)', + content: '(paragraph|audiotag)+ | ((paragraph|audiotag)+ ordered_list)', parseDOM: [{ tag: "li", getAttrs(dom: any) { return { mapStyle: dom.getAttribute("data-mapStyle"), bulletStyle: dom.getAttribute("data-bulletStyle") }; -- cgit v1.2.3-70-g09d2 From 076e49222a26fe395c8658bdebc7de433c1bd3d7 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Jan 2021 14:08:04 -0500 Subject: from last --- src/client/views/nodes/formattedText/FormattedTextBox.scss | 4 ++++ 1 file changed, 4 insertions(+) (limited to 'src') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.scss b/src/client/views/nodes/formattedText/FormattedTextBox.scss index 866f556ff..81bca4c00 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.scss +++ b/src/client/views/nodes/formattedText/FormattedTextBox.scss @@ -20,6 +20,10 @@ audiotag { font-size: 4px; background: lightblue; } +audiotag:hover { + transform: scale(2); + transform-origin: bottom center; +} .formattedTextBox-cont { touch-action: none; -- cgit v1.2.3-70-g09d2 From 8709e3617aa44f7b374aff4346227a4400ff6faf Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Jan 2021 15:50:08 -0500 Subject: added flag to turn off audio anchors. made audio doc appear when clicking on audiotag. --- src/Utils.ts | 4 ++-- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 12 +++++++++--- 2 files changed, 11 insertions(+), 5 deletions(-) (limited to 'src') diff --git a/src/Utils.ts b/src/Utils.ts index 39fc3dae4..c7074c3da 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -500,9 +500,9 @@ export function addStyleSheet(styleType: string = "text/css") { const sheets = document.head.appendChild(style); return (sheets as any).sheet; } -export function addStyleSheetRule(sheet: any, selector: any, css: any) { +export function addStyleSheetRule(sheet: any, selector: any, css: any, selectorPrefix = ".") { const propText = typeof css === "string" ? css : Object.keys(css).map(p => p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p])).join(";"); - return sheet.insertRule("." + selector + "{" + propText + "}", sheet.cssRules.length); + return sheet.insertRule(selectorPrefix + selector + "{" + propText + "}", sheet.cssRules.length); } export function removeStyleSheetRule(sheet: any, rule: number) { if (sheet.rules.length) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index d73fd9208..d24ccd9ad 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -66,6 +66,7 @@ import { SubCollectionViewProps } from '../../collections/CollectionSubView'; import { StyleProp } from '../../StyleProvider'; import { AnchorMenu } from '../../pdf/AnchorMenu'; import { CurrentUserUtils } from '../../../util/CurrentUserUtils'; +import { DocumentManager } from '../../../util/DocumentManager'; export interface FormattedTextBoxProps { makeLink?: () => Opt; // bcz: hack: notifies the text document when the container has made a link. allows the text doc to react and setup a hyeprlink for any selected text @@ -338,6 +339,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp this._editorView.updateState(EditorState.fromJSON(this.config, json)); } } + if (window.getSelection()?.isCollapsed) AnchorMenu.Instance.fadeOut(true); } } @@ -550,10 +552,13 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } return ret; } - static _highlights: string[] = ["Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; + static _highlights: string[] = ["Audio Tags", "Text from Others", "Todo Items", "Important Items", "Disagree Items", "Ignore Items"]; updateHighlights = () => { clearStyleSheetRules(FormattedTextBox._userStyleSheet); + if (FormattedTextBox._highlights.indexOf("Audio Tags") === -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "audiotag", { display: "none" }, ""); + } if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, "UM-remote", { background: "yellow" }); } @@ -625,8 +630,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }); !Doc.UserDoc().noviceMode && changeItems.push({ description: "FreeForm", event: () => DocUtils.makeCustomViewClicked(this.rootDoc, Docs.Create.FreeformDocument, "freeform"), icon: "eye" }); const highlighting: ContextMenuProps[] = []; - const noviceHighlighting = ["My Text", "Text from Others"]; - const expertHighlighting = ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"]; + const noviceHighlighting = ["Audio Tags", "My Text", "Text from Others"]; + const expertHighlighting = [...noviceHighlighting, "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"]; (Doc.UserDoc().noviceMode ? noviceHighlighting : expertHighlighting).forEach(option => highlighting.push({ description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { @@ -1303,6 +1308,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp if (anchor instanceof Doc) { const audiodoc = anchor.annotationOn as Doc; audiodoc._triggerAudio = Number(time); + !DocumentManager.Instance.getDocumentView(audiodoc) && this.props.addDocTab(audiodoc, "add:bottom"); } }); } -- cgit v1.2.3-70-g09d2 From a695e0dd4e2541d6e093e41b88fdd41a32ebadb1 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Jan 2021 20:11:20 -0500 Subject: fixed taking videoBox snapshots. select video/audio doc after deleting an annotation. Fixed following links to videoBox timeline anchors. Fixed rendering timeline annotations by putting them in data-annotations-timelines to distinguish from regular annotations. --- src/client/views/DocComponent.tsx | 2 ++ src/client/views/MarqueeAnnotator.tsx | 6 ++++-- .../collections/CollectionStackedTimeline.tsx | 16 +++++++++------ src/client/views/nodes/AudioBox.tsx | 9 ++++---- src/client/views/nodes/VideoBox.tsx | 24 ++++++++++++++++------ src/client/views/nodes/formattedText/nodes_rts.ts | 2 +- 6 files changed, 40 insertions(+), 19 deletions(-) (limited to 'src') diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 99f13295d..b800ba777 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -74,6 +74,7 @@ export interface ViewBoxAnnotatableProps { fieldKey: string; layerProvider?: (doc: Doc) => boolean; active: () => boolean; + select: (isCtrlPressed: boolean) => void; whenActiveChanged: (isActive: boolean) => void; isSelected: (outsideReaction?: boolean) => boolean; rootSelected: (outsideReaction?: boolean) => boolean; @@ -145,6 +146,7 @@ export function ViewBoxAnnotatableComponent

number; + containerOffset?: () => number[]; mainCont: HTMLDivElement; savedAnnotations: Dictionary; annotationLayer: HTMLDivElement; @@ -69,9 +70,10 @@ export class MarqueeAnnotator extends React.Component { if ((this.props.savedAnnotations.values()[0][0] as any).marqueeing) { const scale = this.props.scaling?.() || 1; const anno = this.props.savedAnnotations.values()[0][0]; + const containerOffset = this.props.containerOffset?.() || [0, 0]; const mainAnnoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, annotationOn: this.props.rootDoc, title: "Annotation on " + this.props.rootDoc.title }); - if (anno.style.left) mainAnnoDoc.x = parseInt(anno.style.left) / scale; - if (anno.style.top) mainAnnoDoc.y = (NumCast(this.props.rootDoc._scrollTop) + parseInt(anno.style.top)) / scale; + if (anno.style.left) mainAnnoDoc.x = (parseInt(anno.style.left) - containerOffset[0]) / scale; + if (anno.style.top) mainAnnoDoc.y = (parseInt(anno.style.top) - containerOffset[1] + NumCast(this.props.rootDoc._scrollTop)) / scale; if (anno.style.height) mainAnnoDoc._height = parseInt(anno.style.height) / scale; if (anno.style.width) mainAnnoDoc._width = parseInt(anno.style.width) / scale; mainAnnoDoc.group = mainAnnoDoc; diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 02e88d939..a59ac109f 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -2,7 +2,7 @@ import React = require("react"); import { action, computed, IReactionDisposer, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; -import { Doc, Opt } from "../../../fields/Doc"; +import { Doc, Opt, DocListCast } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec, makeInterface } from "../../../fields/Schema"; @@ -31,6 +31,7 @@ export type CollectionStackedTimelineProps = { isChildActive: () => boolean; startTag: string; endTag: string; + fieldKeySuffix?: string; }; @observer @@ -46,7 +47,7 @@ export class CollectionStackedTimeline extends CollectionSubView NumCast(anchor._timecodeToShow, NumCast(anchor[this.props.startTag])); - anchorEnd = (anchor: Doc, val: any = null) => NumCast(anchor._timecodeToHide, NumCast(anchor[this.props.endTag], val)); + anchorEnd = (anchor: Doc, val: any = null) => { + const endVal = NumCast(anchor[this.props.endTag], val); + return NumCast(anchor._timecodeToHide, endVal === undefined ? null : endVal); + } toTimeline = (screen_delta: number, width: number) => Math.max(0, Math.min(this.duration, screen_delta / width * this.duration)); rangeClickScript = () => CollectionStackedTimeline.RangeScript; labelClickScript = () => CollectionStackedTimeline.LabelScript; @@ -88,7 +92,7 @@ export class CollectionStackedTimeline extends CollectionSubView 15) && this.createAnchor(this._markerStart, this._markerEnd); + CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15) && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag); } (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined); }), (e, doubleTap) => { this.props.select(false); - e.shiftKey && this.createAnchor(this.currentTime); + e.shiftKey && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, this.currentTime); !wasPlaying && doubleTap && this.props.Play(); }, this.props.isSelected(true) || this.props.isChildActive(), undefined, diff --git a/src/client/views/nodes/AudioBox.tsx b/src/client/views/nodes/AudioBox.tsx index 57b5f3ec7..692eaae66 100644 --- a/src/client/views/nodes/AudioBox.tsx +++ b/src/client/views/nodes/AudioBox.tsx @@ -326,15 +326,16 @@ export class AudioBox extends ViewBoxAnnotatableComponent this.audioState === "playing"; playLink = (link: Doc) => { + const stack = this._stackedTimeline.current; if (link.annotationOn === this.rootDoc) { - if (this.layoutDoc.playOnSelect) this.playFrom(this._stackedTimeline.current?.anchorStart(link) || 0, this._stackedTimeline.current?.anchorEnd(link)); - else this._ele!.currentTime = this.layoutDoc._currentTimecode = (this._stackedTimeline.current?.anchorStart(link) || 0); + if (this.layoutDoc.playOnSelect) this.playFrom(stack?.anchorStart(link) || 0, stack?.anchorEnd(link)); + else this._ele!.currentTime = this.layoutDoc._currentTimecode = (stack?.anchorStart(link) || 0); } else { this.links.filter(l => l.anchor1 === link || l.anchor2 === link).forEach(l => { const { la1, la2 } = this.getLinkData(l); - const startTime = this._stackedTimeline.current?.anchorStart(la1) || this._stackedTimeline.current?.anchorStart(la2); - const endTime = this._stackedTimeline.current?.anchorEnd(la1) || this._stackedTimeline.current?.anchorEnd(la2); + const startTime = stack?.anchorStart(la1) || stack?.anchorStart(la2); + const endTime = stack?.anchorEnd(la1) || stack?.anchorEnd(la2); if (startTime !== undefined) { if (this.layoutDoc.playOnSelect) endTime ? this.playFrom(startTime, endTime) : this.playFrom(startTime); else this._ele!.currentTime = this.layoutDoc._currentTimecode = startTime; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 79d584a1d..8a1cefbd9 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -70,7 +70,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "videoStart", "videoEnd", Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc; + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey + "-timeline", "videoStart", "videoEnd", Cast(this.layoutDoc._currentTimecode, "number", null)) || this.rootDoc; } choosePath(url: string) { @@ -167,7 +167,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent returnedFilename && this.createRealSummaryLink(returnedFilename)); } @@ -179,14 +180,13 @@ export class VideoBox extends ViewBoxAnnotatableComponent (this.props.scaling?.() || 1) * this.heightPercent / 100; + marqueeOffset = () => [this.panelWidth() / 2 * (1 - this.heightPercent / 100) / (this.heightPercent / 100), 0]; render() { const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); @@ -570,7 +573,16 @@ export class VideoBox extends ViewBoxAnnotatableComponent} + }

); } diff --git a/src/client/views/nodes/formattedText/nodes_rts.ts b/src/client/views/nodes/formattedText/nodes_rts.ts index f5bc05a2d..722c0a836 100644 --- a/src/client/views/nodes/formattedText/nodes_rts.ts +++ b/src/client/views/nodes/formattedText/nodes_rts.ts @@ -36,7 +36,7 @@ export const nodes: { [index: string]: NodeSpec } = { "data-audioid": node.attrs.audioId, }, formatAudioTime(node.attrs.timeCode.toString()) - ] + ]; }, parseDOM: [ { -- cgit v1.2.3-70-g09d2 From 41bb365dd4f787aec2262dcb07508e0de3837e10 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 29 Jan 2021 22:19:35 -0500 Subject: fixed dragging on timeline to create anchor --- src/client/views/collections/CollectionStackedTimeline.scss | 1 - src/client/views/collections/CollectionStackedTimeline.tsx | 5 +++-- 2 files changed, 3 insertions(+), 3 deletions(-) (limited to 'src') diff --git a/src/client/views/collections/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index 9d1b46f27..cc56831f3 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -36,7 +36,6 @@ top: 2.5%; height: 95%; border-radius: 4px; - opacity: 0.3; &:hover { opacity: 1; } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index a59ac109f..d4eb66fcc 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -135,8 +135,9 @@ export class CollectionStackedTimeline extends CollectionSubView 15) && CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag); + if (!isClick && CollectionStackedTimeline.SelectingRegion === this && (Math.abs(movement[0]) > 15)) { + CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.props.fieldKey, this.props.startTag, this.props.endTag, + this._markerStart, this._markerEnd); } (!isClick || !wasSelecting) && (CollectionStackedTimeline.SelectingRegion = undefined); }), -- cgit v1.2.3-70-g09d2