From f8008ec8d21f48591ba54a934022278c63852dba Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 11 Feb 2021 17:07:07 -0500 Subject: now pdf/web/text scroll to the target and then pan the document to be in view. --- src/client/views/nodes/formattedText/FormattedTextBox.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 183719e31..60eae2a55 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -868,7 +868,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; } - scrollFocus = (doc: Doc, smooth: boolean, afterFocus?: DocAfterFocusFunc) => { + scrollFocus = (doc: Doc, smooth: boolean, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => { const anchorId = doc[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; @@ -894,7 +894,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }; let start = 0; + let focusSpeed = 0; + let endFocus = afterFocus; if (this._editorView && anchorId) { + focusSpeed = 1500; const editor = this._editorView; const ret = findAnchorFrag(editor.state.doc.content, editor); @@ -909,11 +912,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp setTimeout(() => { clearStyleSheetRules(FormattedTextBox._highlightStyleSheet); afterFocus?.(true); - }, 1500); + }, focusSpeed); + endFocus = async (moved: boolean) => afterFocus ? await afterFocus(true) : false; } - } else { - afterFocus?.(false); } + (this.props as any).DocumentView().props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) => + new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed))); } componentDidMount() { -- cgit v1.2.3-70-g09d2 From 7dbd1417fe12b54cdab44fd25adb6c5a9c52bac9 Mon Sep 17 00:00:00 2001 From: bobzel Date: Thu, 11 Feb 2021 18:11:57 -0500 Subject: more focus refactoring and some bug fixes - resetting target document when recursively focusing. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 12 ++++++------ src/client/views/nodes/LinkDocPreview.tsx | 1 + src/client/views/nodes/WebBox.tsx | 10 ++++------ .../views/nodes/formattedText/FormattedTextBox.tsx | 20 +++++++++----------- src/client/views/pdf/PDFViewer.tsx | 10 +++------- 6 files changed, 24 insertions(+), 31 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 8835782f9..c18ef7a3b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -951,7 +951,7 @@ export class CollectionFreeFormView extends CollectionSubView - new Promise(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), focusSpeed)), xf.transform(docTransform ?? Transform.Identity())); + new Promise(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), focusSpeed)), xf); } } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 82bcc3993..40dfd1643 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -47,7 +47,7 @@ export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterF export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; export interface DocComponentView { getAnchor: () => Doc; - scrollFocus?: (doc: Doc, smooth: boolean, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => void; + scrollFocus?: (doc: Doc, smooth: boolean) => Opt; // returns the duration of the focus back?: () => boolean; forward?: () => boolean; url?: () => string; @@ -376,11 +376,11 @@ export class DocumentViewInternal extends DocComponent { - if (this._componentView?.scrollFocus) { - this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo, willZoom, scale, afterFocus); // bcz: smooth parameter should really be passed into focus() instead of inferred here - } else { - this.props.focus(doc, willZoom, scale, afterFocus, docTransform); - } + const focusSpeed = this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here + const endFocus = focusSpeed === undefined ? afterFocus : async (moved: boolean) => afterFocus ? await afterFocus(true) : false; + this.props.focus(docTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) => + new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0)), docTransform); + } onClick = action((e: React.MouseEvent | React.PointerEvent) => { if (!e.nativeEvent.cancelBubble && !this.Document.ignoreClick && this.props.renderDepth >= 0 && diff --git a/src/client/views/nodes/LinkDocPreview.tsx b/src/client/views/nodes/LinkDocPreview.tsx index 6155ed493..150a549cb 100644 --- a/src/client/views/nodes/LinkDocPreview.tsx +++ b/src/client/views/nodes/LinkDocPreview.tsx @@ -99,6 +99,7 @@ export class LinkDocPreview extends React.Component { followLink = (e: React.PointerEvent) => { if (this._linkDoc && this._linkSrc) { + LinkDocPreview.Clear(); LinkManager.FollowLink(this._linkDoc, this._linkSrc, this.props.docProps, false); } else if (this.props.hrefs?.length) { this.props.docProps?.addDocTab(Docs.Create.WebDocument(this.props.hrefs[0], { title: this.props.hrefs[0], _width: 200, _height: 400, useCors: true }), "add:right"); diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 4dbf78e35..662477c05 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -111,9 +111,8 @@ export class WebBox extends ViewBoxAnnotatableComponent this.rootDoc; - scrollFocus = (doc: Doc, smooth: boolean, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => { - let focusSpeed = 0; - let endFocus = afterFocus; + scrollFocus = (doc: Doc, smooth: boolean) => { + let focusSpeed: Opt; if (doc !== this.rootDoc && this.webpage && this._outerRef.current) { const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1)); if (scrollTo !== undefined) { @@ -122,13 +121,12 @@ export class WebBox extends ViewBoxAnnotatableComponent afterFocus ? await afterFocus(true) : false; } } else { this._initialScroll = NumCast(doc.y); } - (this.props as any).DocumentView().props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) => - new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed))); + + return focusSpeed; } async componentDidMount() { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 60eae2a55..646d98347 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -868,7 +868,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp return this.active();//this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; } - scrollFocus = (doc: Doc, smooth: boolean, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => { + focusSpeed: Opt; + scrollFocus = (doc: Doc, smooth: boolean) => { const anchorId = doc[Id]; const findAnchorFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; @@ -894,14 +895,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp }; let start = 0; - let focusSpeed = 0; - let endFocus = afterFocus; if (this._editorView && anchorId) { - focusSpeed = 1500; const editor = this._editorView; const ret = findAnchorFrag(editor.state.doc.content, editor); if (ret.frag.size > 2 && ret.start >= 0) { + smooth && (this.focusSpeed = 500); let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected @@ -911,13 +910,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" }); setTimeout(() => { clearStyleSheetRules(FormattedTextBox._highlightStyleSheet); - afterFocus?.(true); - }, focusSpeed); - endFocus = async (moved: boolean) => afterFocus ? await afterFocus(true) : false; + this.focusSpeed = undefined; + }, this.focusSpeed); } } - (this.props as any).DocumentView().props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) => - new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed))); + + return this.focusSpeed; } componentDidMount() { @@ -1228,8 +1226,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp const scrollRef = self._scrollRef.current; if ((docPos.top < viewRect.top || docPos.top > viewRect.bottom) && scrollRef) { const scrollPos = scrollRef.scrollTop + (docPos.top - viewRect.top) * self.props.ScreenToLocalTransform().Scale; - if (!LinkDocPreview.LinkInfo) { - scrollPos && smoothScroll(500, scrollRef, scrollPos); + if (this.focusSpeed !== undefined) { + scrollPos && smoothScroll(this.focusSpeed, scrollRef, scrollPos); } else { scrollRef.scrollTo({ top: scrollPos }); } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 0429b61dc..c1ec7fcbb 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -180,10 +180,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent { + scrollFocus = (doc: Doc, smooth: boolean) => { const mainCont = this._mainCont.current; - let focusSpeed = 0; - let endFocus = afterFocus; + let focusSpeed: Opt; if (doc !== this.rootDoc && mainCont) { const scrollTo = Utils.scrollIntoView(NumCast(doc.y), doc[HeightSym](), NumCast(this.layoutDoc._scrollTop), this.props.PanelHeight() / (this.props.scaling?.() || 1)); if (scrollTo !== undefined) { @@ -191,12 +190,9 @@ export class PDFViewer extends ViewBoxAnnotatableComponent afterFocus ? await afterFocus(true) : false; } } - (this.props as any).DocumentView().props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) => - new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed))); + return focusSpeed; } @action -- cgit v1.2.3-70-g09d2 From d30f5b4855a0ad500cc5784260128ecc269f6450 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 12 Feb 2021 13:33:36 -0500 Subject: streamlined double-clicking to always open lightbox no matter what document you click on (except webbox's in native selection mode). shift -double-click re-centers a freeform view. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 6 ++-- src/client/views/nodes/DocumentView.tsx | 41 +++++++++++----------- .../views/nodes/formattedText/FormattedTextBox.tsx | 20 +++++++---- .../formattedText/FormattedTextBoxComment.tsx | 7 ++-- src/client/views/pdf/PDFViewer.tsx | 13 ++++--- 5 files changed, 49 insertions(+), 38 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1a6c6bf0c..7aecf12b0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -608,13 +608,13 @@ export class CollectionFreeFormView extends CollectionSubView { if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) { - if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a double click + if (Date.now() - this._lastTap < 300) { // reset zoom of freeform view to 1-to-1 on a double click if (e.shiftKey) { - LightboxView.SetLightboxDoc(this.rootDoc, this.childDocs); - } else { this.scaleAtPt(this.getTransform().transformPoint(e.clientX, e.clientY), 1); e.stopPropagation(); e.preventDefault(); + } else { + LightboxView.SetLightboxDoc(this.rootDoc, this.childDocs); } } this._lastTap = Date.now(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 66436fa75..bcf12c93c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -393,26 +393,24 @@ export class DocumentViewInternal extends DocComponent this.onDoubleClickHandler.script.run({ - this: this.layoutDoc, - self: this.rootDoc, - scriptContext: this.props.scriptContext, - thisContainer: this.props.ContainingCollectionDoc, - documentView: this.props.DocumentView(), - clientX: e.clientX, - clientY: e.clientY, - shiftKey: e.shiftKey - }, console.log); - UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click"); - } else if (!Doc.IsSystem(this.props.Document)) { - if (this.props.Document.type !== DocumentType.LABEL) { - UndoManager.RunInBatch(() => this.props.addDocTab((this.rootDoc._fullScreenView as Doc) || this.rootDoc, "lightbox"), "double tap"); - SelectionManager.DeselectAll(); - } - Doc.UnBrushDoc(this.props.Document); + if (this.onDoubleClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself + const func = () => this.onDoubleClickHandler.script.run({ + this: this.layoutDoc, + self: this.rootDoc, + scriptContext: this.props.scriptContext, + thisContainer: this.props.ContainingCollectionDoc, + documentView: this.props.DocumentView(), + clientX: e.clientX, + clientY: e.clientY, + shiftKey: e.shiftKey + }, console.log); + UndoManager.RunInBatch(() => func().result?.select === true ? this.props.select(false) : "", "on double click"); + } else if (!Doc.IsSystem(this.props.Document)) { + if (this.props.Document.type !== DocumentType.LABEL) { + UndoManager.RunInBatch(() => this.props.addDocTab((this.rootDoc._fullScreenView as Doc) || this.rootDoc, "lightbox"), "double tap"); + SelectionManager.DeselectAll(); } + Doc.UnBrushDoc(this.props.Document); } } else if (this.onClickHandler?.script && !StrCast(Doc.LayoutField(this.layoutDoc))?.includes("ScriptingBox")) { // bcz: hack? don't execute script if you're clicking on a scripting box itself const shiftKey = e.shiftKey; @@ -475,7 +473,8 @@ export class DocumentViewInternal extends DocComponent { return json?.indexOf("\"storedMarks\"") === -1 ? json?.replace(/"selection":.*/, "") : json?.replace(/"selection":"\"storedMarks\""/, "\"storedMarks\""); }; @@ -1337,24 +1335,34 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBoxComment.textBox = this; if (e.button === 0 && (this.props.rootSelected(true) || this.props.isSelected(true)) && !e.altKey && !e.ctrlKey && !e.metaKey) { if (e.clientX < this.ProseRef!.getBoundingClientRect().right) { // stop propagation if not in sidebar - e.stopPropagation(); // if the text box is selected, then it consumes all down events + // bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView, + // but that's changed, so this shouldn't be needed. + //e.stopPropagation(); // if the text box is selected, then it consumes all down events + document.addEventListener("pointerup", this.onSelectEnd); + document.addEventListener("pointermove", this.onSelectMove); } } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { e.preventDefault(); } } + onSelectMove = (e: PointerEvent) => e.stopPropagation(); + onSelectEnd = (e: PointerEvent) => { + document.removeEventListener("pointerup", this.onSelectEnd); + document.removeEventListener("pointermove", this.onSelectMove); + } onPointerUp = (e: React.PointerEvent): void => { + FormattedTextBox.CanAnnotate = true; + if (!this._editorView?.state.selection.empty && FormattedTextBox.CanAnnotate) this.setupAnchorMenu(); if (!this._downEvent) return; this._downEvent = false; if (!(e.nativeEvent as any).formattedHandled && this.active(true)) { const editor = this._editorView!; - FormattedTextBoxComment.textBox = this; const pcords = editor.posAtCoords({ left: e.clientX, top: e.clientY }); !this.props.isSelected(true) && editor.dispatch(editor.state.tr.setSelection(new TextSelection(editor.state.doc.resolve(pcords?.pos || 0)))); const target = (e.target as any).parentElement; // hrefs are store don the database of the node that wraps the hyerlink - FormattedTextBoxComment.update(editor, undefined, target?.dataset?.targethrefs); + FormattedTextBoxComment.update(this, editor, undefined, target?.dataset?.targethrefs); } (e.nativeEvent as any).formattedHandled = true; @@ -1451,7 +1459,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp (e.nativeEvent as any).formattedHandled = true; if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events - e.stopPropagation(); + // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks this.hitBulletTargets(e.clientX, e.clientY, !this._editorView?.state.selection.empty || this._forceUncollapse, false, this._forceDownNode, e.shiftKey); } this._forceUncollapse = !(this._editorView!.root as any).getSelection().isCollapsed; diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 89df5e246..900b702c6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -82,9 +82,10 @@ export class FormattedTextBoxComment { FormattedTextBoxComment.tooltip.style.display = ""; } - static update(view: EditorView, lastState?: EditorState, hrefs: string = "") { - if (FormattedTextBoxComment.textBox && (hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { - FormattedTextBoxComment.setupPreview(view, FormattedTextBoxComment.textBox, hrefs ? hrefs.trim().split(" ") : undefined); + static update(textBox: FormattedTextBox, view: EditorView, lastState?: EditorState, hrefs: string = "") { + FormattedTextBoxComment.textBox = textBox; + if ((hrefs || !lastState?.doc.eq(view.state.doc) || !lastState?.selection.eq(view.state.selection))) { + FormattedTextBoxComment.setupPreview(view, textBox, hrefs ? hrefs.trim().split(" ") : undefined); } } diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index c1ec7fcbb..0ba08685a 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -363,9 +363,11 @@ export class PDFViewer extends ViewBoxAnnotatableComponent { const hit = document.elementFromPoint(e.clientX, e.clientY); - if (hit && hit.localName === "span" && this.annotationsActive(true)) { // drag selecting text stops propagation - e.button === 0 && e.stopPropagation(); - } + // bcz: Change. drag selecting requires that preventDefault is NOT called. This used to happen in DocumentView, + // but that's changed, so this shouldn't be needed. + // if (hit && hit.localName === "span" && this.annotationsActive(true)) { // drag selecting text stops propagation + // e.button === 0 && e.stopPropagation(); + // } // if alt+left click, drag and annotate this._downX = e.clientX; this._downY = e.clientY; @@ -376,6 +378,7 @@ export class PDFViewer extends ViewBoxAnnotatableComponent void) => this._setPreviewCursor = func; -- cgit v1.2.3-70-g09d2 From d9ec489c2f549c4aef0b3f458abfda5bb11e7188 Mon Sep 17 00:00:00 2001 From: bobzel Date: Fri, 12 Feb 2021 22:25:40 -0500 Subject: changed audio video annotation to use only timecodeToShow/timecodeToHide. distinguish timeline annotation from video frame annotations with _timelineLabel field. Lightbox improvements to restore original camera values and to use fitContentsToDoc for collections --- src/client/documents/Documents.ts | 1 + src/client/views/LightboxView.tsx | 36 +++++++++++++++++----- .../views/collections/CollectionSchemaView.tsx | 4 +-- .../collections/CollectionStackedTimeline.tsx | 7 ++--- .../views/collections/CollectionTimeView.tsx | 4 +-- src/client/views/collections/TabDocView.tsx | 4 +-- src/client/views/collections/TreeView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 32 ++++++++++--------- src/client/views/nodes/AudioBox.tsx | 6 ++-- src/client/views/nodes/DocumentView.tsx | 2 +- src/client/views/nodes/ImageBox.tsx | 5 ++- src/client/views/nodes/PresBox.tsx | 4 +-- src/client/views/nodes/VideoBox.tsx | 14 ++++----- .../views/nodes/formattedText/FormattedTextBox.tsx | 6 ++-- src/fields/documentSchemas.ts | 1 + 15 files changed, 77 insertions(+), 51 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index aafa58028..d72efb1b4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -155,6 +155,7 @@ 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 + _timelineLabel?: boolean; // whether the document exists 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/LightboxView.tsx b/src/client/views/LightboxView.tsx index 4c6592e58..0957fc94d 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -13,6 +13,7 @@ import { DocUtils } from '../documents/Documents'; import { DocumentManager } from '../util/DocumentManager'; import { SelectionManager } from '../util/SelectionManager'; import { TabDocView } from './collections/TabDocView'; +import { Cast } from '../../fields/Types'; interface LightboxViewProps { PanelWidth: number; @@ -22,14 +23,31 @@ interface LightboxViewProps { @observer export class LightboxView extends React.Component { + public static SavedState: Opt<{ panX: Opt, panY: Opt, scale: Opt, transition: Opt }>; @observable static LightboxDoc: Opt; + @observable static LightboxDocTarget: Opt; @action public static SetLightboxDoc(doc: Opt, future?: Doc[]) { - LightboxView.LightboxDoc = doc; if (!doc) { + if (this.LightboxDoc) { + this.LightboxDoc._panX = this.SavedState?.panX; + this.LightboxDoc._panY = this.SavedState?.panY; + this.LightboxDoc._viewScale = this.SavedState?.scale; + this.LightboxDoc._viewTransition = this.SavedState?.transition; + } LightboxView.LightboxFuture = LightboxView.LightboxHistory = []; - } else if (future) { + } else { + LightboxView.SavedState = { + panX: Cast(doc._panX, "number", null), + panY: Cast(doc._panY, "number", null), + scale: Cast(doc._viewScale, "number", null), + transition: Cast(doc._viewTransition, "string", null) + }; + } + if (future) { LightboxView.LightboxFuture = future.slice(); } + LightboxView.LightboxDoc = LightboxView.LightboxDocTarget = doc; + return true; } public static IsLightboxDocView(path: DocumentView[]) { return path.includes(LightboxView.LightboxDocView.current!); } @@ -58,6 +76,7 @@ export class LightboxView extends React.Component { return LightboxView.SetLightboxDoc(doc); } + fitToBox = () => LightboxView.LightboxDocTarget === LightboxView.LightboxDoc; render() { if (LightboxView.LightboxHistory.lastElement() !== LightboxView.LightboxDoc) LightboxView.LightboxHistory.push(LightboxView.LightboxDoc); let downx = 0, downy = 0; @@ -79,6 +98,7 @@ export class LightboxView extends React.Component { Document={LightboxView.LightboxDoc} DataDoc={undefined} addDocument={undefined} + fitContentsToDoc={this.fitToBox} addDocTab={this.addDocTab} pinToPres={TabDocView.PinDoc} rootSelected={returnTrue} @@ -102,10 +122,10 @@ export class LightboxView extends React.Component { {this.navBtn(undefined, "chevron-left", () => LightboxView.LightboxDoc && LightboxView.LightboxHistory.length ? "" : "none", - e => { + action(e => { e.stopPropagation(); const previous = LightboxView.LightboxHistory.pop(); - const target = LightboxView.LightboxHistory.lastElement(); + const target = LightboxView.LightboxDocTarget = LightboxView.LightboxHistory.lastElement(); const docView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (docView && target) { if (LightboxView.LightboxFuture.lastElement() !== previous) LightboxView.LightboxFuture.push(previous); @@ -113,12 +133,12 @@ export class LightboxView extends React.Component { } else { LightboxView.SetLightboxDoc(target); } - })} + }))} {this.navBtn(this.props.PanelWidth - Math.min(this.props.PanelWidth / 4, this.props.maxBorder[0]), "chevron-right", () => LightboxView.LightboxDoc && LightboxView.LightboxFuture.length ? "" : "none", - e => { + action(e => { e.stopPropagation(); - const target = LightboxView.LightboxFuture.pop(); + const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture.pop(); const docView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (docView && target) { docView.focus(target, true, 0.9); @@ -126,7 +146,7 @@ export class LightboxView extends React.Component { } else { LightboxView.SetLightboxDoc(target); } - })} + }))} ; } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index d2ed5427b..528cdc8b7 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -11,7 +11,7 @@ import { listSpec } from "../../../fields/Schema"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../fields/SchemaHeaderField"; import { Cast, NumCast } from "../../../fields/Types"; import { TraceMobx } from "../../../fields/util"; -import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist } from "../../../Utils"; +import { emptyFunction, emptyPath, returnFalse, setupMoveUpEvents, returnEmptyDoclist, returnTrue } from "../../../Utils"; import { SelectionManager } from "../../util/SelectionManager"; import { SnappingManager } from "../../util/SnappingManager"; import { Transform } from "../../util/Transform"; @@ -402,7 +402,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) { boolean; startTag: string; endTag: string; - fieldKeySuffix?: string; }; @observer @@ -47,7 +46,6 @@ export class CollectionStackedTimeline extends CollectionSubView ({ level: this.getLevel(anchor, overlaps), anchor })); + const drawAnchors = this.childDocs.map(anchor => ({ level: this.getLevel(anchor, overlaps), anchor })); const maxLevel = overlaps.reduce((m, o) => Math.max(m, o.level), 0) + 2; const isActive = this.props.isChildActive() || this.props.isSelected(false); return
this._timeline = timeline} diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index cc625e12e..869e01fea 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -6,7 +6,7 @@ import { ObjectField } from "../../../fields/ObjectField"; import { RichTextField } from "../../../fields/RichTextField"; import { ComputedField, ScriptField } from "../../../fields/ScriptField"; import { NumCast, StrCast, BoolCast, Cast } from "../../../fields/Types"; -import { emptyFunction, returnFalse, setupMoveUpEvents } from "../../../Utils"; +import { emptyFunction, returnFalse, setupMoveUpEvents, returnTrue } from "../../../Utils"; import { Scripting } from "../../util/Scripting"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; @@ -86,7 +86,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { @computed get contents() { return
{ case "close": return CollectionDockingView.CloseSplit(doc, locationParams); case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); - case "lightbox": return LightboxView.SetLightboxDoc(doc); + case "lightbox": return LightboxView.SetLightboxDoc(doc, DocListCast(doc[Doc.LayoutFieldKey(doc) + "-annotations"]).sort((a: Doc, b: Doc) => NumCast(b._timecodeToShow) - NumCast(a._timecodeToShow))); case "inPlace": case "add": default: @@ -342,7 +342,7 @@ export class TabDocView extends React.Component { docFilters={CollectionDockingView.Instance.docFilters} docRangeFilters={CollectionDockingView.Instance.docRangeFilters} searchFilterDocs={CollectionDockingView.Instance.searchFilterDocs} - fitContentsToDoc={true} + fitContentsToDoc={returnTrue} />
diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 14075db1f..d7198ee7c 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -632,7 +632,7 @@ export class TreeView extends React.Component { PanelHeight={panelHeight} NativeWidth={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfWidth : undefined} NativeHeight={!asText && (this.layoutDoc.type === DocumentType.RTF || this.layoutDoc.type === DocumentType.SLIDER) ? this.rtfHeight : undefined} - fitContentsToDoc={true} + fitContentsToDoc={returnTrue} hideTitle={asText} LayoutTemplateString={asText ? FormattedTextBox.LayoutString("text") : undefined} focus={asText ? this.refocus : returnFalse} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index eb99702a1..1f65cf1ea 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -118,7 +118,7 @@ export class CollectionFreeFormView extends CollectionSubView e.bounds && !e.bounds.z).map(e => e.bounds!), NumCast(this.layoutDoc._xPadding, 10), NumCast(this.layoutDoc._yPadding, 10)); } @computed get nativeWidth() { return this.fitToContent ? 0 : Doc.NativeWidth(this.Document); } @@ -864,8 +864,8 @@ export class CollectionFreeFormView extends CollectionSubView { - 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; + return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow" /* audioStart */, "_timecodeToHide" /* audioEnd */, this._ele?.currentTime || Cast(this.props.Document._currentTimecode, "number", null) || (this.audioState === "recording" ? (Date.now() - (this.recordingStart || 0)) / 1000 : undefined)) || this.rootDoc; } componentWillUnmount() { @@ -354,8 +354,8 @@ export class AudioBox extends ViewBoxAnnotatableComponent boolean; // used by freeformview to fit its contents to its panel. corresponds to _fitToBox property on a Document ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; setContentView?: (view: DocComponentView) => any; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 67397dc51..441d6232a 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -151,6 +151,8 @@ export class ImageBox extends ViewBoxAnnotatableComponent this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes; @undoBatch rotate = action(() => { @@ -170,6 +172,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent GooglePhotos.Transactions.UploadImages([this.props.Document]), icon: "caret-square-right" }); funcs.push({ description: "Copy path", event: () => Utils.CopyText(field.url.href), icon: "expand-arrows-alt" }); @@ -223,7 +226,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent if (audio) { audio.mediaStart = "manual"; audio.mediaStop = "manual"; - audio.presStartTime = NumCast(doc.audioStart, NumCast(doc.videoStart)); - audio.presEndTime = NumCast(doc.audioEnd, NumCast(doc.videoEnd)); + audio.presStartTime = NumCast(doc._timecodeToShow /* audioStart */, NumCast(doc._timecodeToShow /* videoStart */)); + audio.presEndTime = NumCast(doc._timecodeToHide /* audioEnd */, NumCast(doc._timecodeToHide /* videoEnd */)); audio.presDuration = audio.presStartTime - audio.presEndTime; TabDocView.PinDoc(audio, { audioRange: true }); setTimeout(() => this.removeDocument(doc), 0); diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index a99853aac..6d0a200cb 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction, import { observer } from "mobx-react"; import * as rp from 'request-promise'; import { Dictionary } from "typescript-collections"; -import { Doc, DocListCast } from "../../../fields/Doc"; +import { Doc, DocListCast, StrListCast } from "../../../fields/Doc"; import { documentSchema } from "../../../fields/documentSchemas"; import { InkTool } from "../../../fields/InkField"; import { makeInterface } from "../../../fields/Schema"; @@ -71,7 +71,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent { const timecode = Cast(this.layoutDoc._currentTimecode, "number", null); - return CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey + "-timeline", "videoStart", "videoEnd", timecode ? timecode : undefined) || this.rootDoc; + const anchor = CollectionStackedTimeline.createAnchor(this.rootDoc, this.dataDoc, this.annotationKey, "_timecodeToShow"/* videoStart */, "_timecodeToHide" /* videoEnd */, timecode ? timecode : undefined) || this.rootDoc; + return anchor; } choosePath(url: string) { @@ -202,7 +203,6 @@ export class VideoBox extends ViewBoxAnnotatableComponent this.props.isSelected(), selected => { if (!selected) { @@ -494,9 +494,8 @@ export class VideoBox extends ViewBoxAnnotatableComponent (this.props.scaling?.() || 1) * this.heightPercent / 100; marqueeOffset = () => [this.panelWidth() / 2 * (1 - this.heightPercent / 100) / (this.heightPercent / 100), 0]; - + timelineDocFilter = () => ["_timelineLabel:true:x"]; render() { const borderRad = this.props.styleProvider?.(this.layoutDoc, this.props, StyleProp.BorderRounding); const borderRadius = borderRad?.includes("px") ? `${Number(borderRad.split("px")[0]) / this.scaling()}px` : borderRad; @@ -558,6 +557,7 @@ export class VideoBox extends ViewBoxAnnotatableComponent { 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); + linkTime = NumCast(anchor._timecodeToShow /* audioStart */); linkAnchor = anchor; } }); @@ -1669,8 +1669,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } sidebarContentScaling = () => (this.props.scaling?.() || 1) * NumCast(this.layoutDoc._viewScale, 1); + fitToBox = () => this.props.Document._fitToBox; @computed get sidebarCollection() { - const fitToBox = this.props.Document._fitToBox; const collectionProps: SubCollectionViewProps & collectionFreeformViewProps = { ...OmitKeys(this.props, ["NativeWidth", "NativeHeight"]).omit, NativeWidth: returnZero, @@ -1683,7 +1683,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp scaleField: this.SidebarKey + "-scale", isAnnotationOverlay: true, fieldKey: this.SidebarKey, - fitContentsToDoc: fitToBox, + fitContentsToDoc: this.fitToBox, select: emptyFunction, active: this.annotationsActive, scaling: this.sidebarContentScaling, diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index 056243953..bdc498c97 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -20,6 +20,7 @@ export const documentSchema = createSchema({ activeFrame: "number", // the active frame of a frame based animated document _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) + _timecodeToHIde: "number", // the time that a document should be hidden isLabel: "boolean", // whether the document is a label or not (video / audio) markers: listSpec(Doc), // list of markers for audio / video x: "number", // x coordinate when in a freeform view -- cgit v1.2.3-70-g09d2 From 6fcef3d8dfdbe373724d2f11c9df8f350bd8950c Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 14 Feb 2021 18:29:00 -0500 Subject: fixes to focus()'s api and extension to allow focusing on a Tab's contents. --- src/client/documents/Documents.ts | 7 +-- src/client/util/DocumentManager.ts | 38 ++++++------ src/client/util/LinkManager.ts | 28 +++++---- src/client/views/LightboxView.tsx | 5 +- .../collections/CollectionStackedTimeline.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 12 ++-- src/client/views/collections/TabDocView.tsx | 29 +++++++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 71 ++++++++++++---------- .../views/nodes/CollectionFreeFormDocumentView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 35 +++++++---- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 2 +- 13 files changed, 139 insertions(+), 98 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index d72efb1b4..fee2679b6 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -35,6 +35,7 @@ import { AudioBox } from "../views/nodes/AudioBox"; import { ColorBox } from "../views/nodes/ColorBox"; import { ComparisonBox } from "../views/nodes/ComparisonBox"; import { DocHolderBox } from "../views/nodes/DocHolderBox"; +import { DocFocusOptions } from "../views/nodes/DocumentView"; import { FilterBox } from "../views/nodes/FilterBox"; import { FontIconBox } from "../views/nodes/FontIconBox"; import { FormattedTextBox } from "../views/nodes/formattedText/FormattedTextBox"; @@ -55,8 +56,6 @@ import { PresElementBox } from "../views/presentationview/PresElementBox"; import { SearchBox } from "../views/search/SearchBox"; import { DashWebRTCVideo } from "../views/webcam/DashWebRTCVideo"; import { DocumentType } from "./DocumentTypes"; -import { DocAfterFocusFunc } from "../views/nodes/DocumentView"; -import { Transform } from "../util/Transform"; const path = require('path'); const defaultNativeImageDim = Number(DFLT_IMAGE_NATIVE_DIM.replace("px", "")); @@ -1020,8 +1019,8 @@ export namespace DocUtils { }); } - export function DefaultFocus(doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) { - afterFocus?.(false); + export function DefaultFocus(doc: Doc, options?: DocFocusOptions) { + options?.afterFocus?.(false); } export let ActiveRecordings: AudioBox[] = []; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index d2251583c..dd59efa41 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -139,9 +139,11 @@ export class DocumentManager { closeContextIfNotFound: boolean = false, // after opening a context where the document should be, this determines whether the context should be closed if the Doc isn't actually there originatingDoc: Opt = undefined, // doc that initiated the display of the target odoc finished?: () => void, + originalTarget?: Doc ): Promise => { + originalTarget = originalTarget ?? targetDoc; const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; - let docView = getFirstDocView(targetDoc, originatingDoc); + const docView = getFirstDocView(targetDoc, originatingDoc); const highlight = () => { const finalDocView = getFirstDocView(targetDoc); finalDocView && Doc.linkFollowHighlight(finalDocView.rootDoc); @@ -166,15 +168,16 @@ export class DocumentManager { const targetDocContext = contextDoc || annotatedDoc; const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext); if (!docView && targetDoc.type === DocumentType.TEXTANCHOR && rtfView) { - rtfView.focus(targetDoc, false); + rtfView.focus(targetDoc); } else if (docView) { - docView.props.focus(targetDoc, willZoom, undefined, (didFocus: boolean) => - new Promise(res => { - focusAndFinish(didFocus); - res(); - }) - ); + docView.props.focus(targetDoc, { + originalTarget, willZoom, afterFocus: (didFocus: boolean) => + new Promise(res => { + focusAndFinish(didFocus); + res(); + }) + }); } else { if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); @@ -182,7 +185,7 @@ export class DocumentManager { } else { // otherwise try to get a view of the context of the target if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. targetDocContext._viewTransition = "transform 500ms"; - targetDocContextView.props.focus(targetDocContextView.rootDoc, willZoom); + targetDocContextView.props.focus(targetDocContextView.rootDoc, { willZoom }); // now find the target document within the context if (targetDoc._timecodeToShow) { // if the target has a timecode, it should show up once the (presumed) video context scrubs to the display timecode; @@ -192,12 +195,13 @@ export class DocumentManager { const findView = (delay: number) => { const retryDocView = getFirstDocView(targetDoc); // test again for the target view snce we presumably created the context above by focusing on it if (retryDocView) { // we found the target in the context - retryDocView.props.focus(targetDoc, willZoom, undefined, (didFocus: boolean) => - new Promise(res => { - focusAndFinish(didFocus); - res(); - }) - ); // focus on the target in the context + retryDocView.props.focus(targetDoc, { + willZoom, afterFocus: (didFocus: boolean) => + new Promise(res => { + focusAndFinish(didFocus); + res(); + }) + }); // focus on the target in the context highlight(); } else if (delay > 1500) { // we didn't find the target, so it must have moved out of the context. Go back to just creating it. @@ -213,7 +217,7 @@ export class DocumentManager { findView(0); } } else { // there's no context view so we need to create one first and try again when that finishes - const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished); + const finishFunc = () => this.jumpToDocument(targetDoc, true, createViewFunc, docContext, linkDoc, true /* if we don't find the target, we want to get rid of the context just created */, undefined, finished, originalTarget); createViewFunc(targetDocContext, // after creating the context, this calls the finish function that will retry looking for the target finishFunc); } @@ -222,4 +226,4 @@ export class DocumentManager { } } -Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, true)); }); \ No newline at end of file +Scripting.addGlobal(function DocFocus(doc: any) { DocumentManager.Instance.getDocumentViews(Doc.GetProto(doc)).map(view => view.props.focus(doc, { willZoom: true })); }); \ No newline at end of file diff --git a/src/client/util/LinkManager.ts b/src/client/util/LinkManager.ts index 1e6e02e55..f741d9c2c 100644 --- a/src/client/util/LinkManager.ts +++ b/src/client/util/LinkManager.ts @@ -111,10 +111,12 @@ export class LinkManager { setTimeout(() => { const targDocView = DocumentManager.Instance.getFirstDocumentView(doc); if (targDocView) { - targDocView.props.focus(doc, BoolCast(sourceDoc.followLinkZoom, false), undefined, (didFocus: boolean) => { - finished?.(); - res(true); - return new Promise(res2 => res2()); + targDocView.props.focus(doc, { + willZoom: BoolCast(sourceDoc.followLinkZoom, false), afterFocus: (didFocus: boolean) => { + finished?.(); + res(true); + return new Promise(res2 => res2()); + } }); } else { res(where !== "inPlace"); // return true to reset the initial focus&zoom (return false for 'inPlace' since resetting the initial focus&zoom will negate the zoom into the target) @@ -126,30 +128,30 @@ export class LinkManager { createTabForTarget(false); } else { // first focus & zoom onto this (the clicked document). Then execute the function to focus on the target - docViewProps.focus(sourceDoc, BoolCast(sourceDoc.followLinkZoom, true), 1, createTabForTarget); + docViewProps.focus(sourceDoc, { willZoom: BoolCast(sourceDoc.followLinkZoom, true), scale: 1, afterFocus: createTabForTarget }); } }; LinkManager.traverseLink(linkDoc, sourceDoc, createViewFunc, BoolCast(sourceDoc.followLinkZoom, false), docViewProps.ContainingCollectionDoc, batch.end, altKey ? true : undefined); } - public static traverseLink(link: Opt, doc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { - const linkDocs = link ? [link] : DocListCast(doc.links); - const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor1 - const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, doc)); // link docs where 'doc' is anchor2 + public static traverseLink(link: Opt, sourceDoc: Doc, createViewFunc: CreateViewFunc, zoom = false, currentContext?: Doc, finished?: () => void, traverseBacklink?: boolean) { + const linkDocs = link ? [link] : DocListCast(sourceDoc.links); + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor1 + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, sourceDoc) || Doc.AreProtosEqual((linkDoc.anchor2 as Doc).annotationOn as Doc, sourceDoc)); // link docs where 'doc' is anchor2 const fwdLinkWithoutTargetView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); const backLinkWithoutTargetView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); const linkWithoutTargetDoc = traverseBacklink === undefined ? fwdLinkWithoutTargetView || backLinkWithoutTargetView : traverseBacklink ? backLinkWithoutTargetView : fwdLinkWithoutTargetView; const linkDocList = linkWithoutTargetDoc ? [linkWithoutTargetDoc] : (traverseBacklink === undefined ? firstDocs.concat(secondDocs) : traverseBacklink ? secondDocs : firstDocs); - const followLinks = linkDocList.length ? (doc.isPushpin ? linkDocList : [linkDocList[0]]) : []; + const followLinks = linkDocList.length ? (sourceDoc.isPushpin ? linkDocList : [linkDocList[0]]) : []; followLinks.forEach(async linkDoc => { if (linkDoc) { - const target = (doc === linkDoc.anchor1 ? linkDoc.anchor2 : doc === linkDoc.anchor2 ? linkDoc.anchor1 : - (Doc.AreProtosEqual(doc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, doc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; + const target = (sourceDoc === linkDoc.anchor1 ? linkDoc.anchor2 : sourceDoc === linkDoc.anchor2 ? linkDoc.anchor1 : + (Doc.AreProtosEqual(sourceDoc, linkDoc.anchor1 as Doc) || Doc.AreProtosEqual((linkDoc.anchor1 as Doc).annotationOn as Doc, sourceDoc) ? linkDoc.anchor2 : linkDoc.anchor1)) as Doc; if (target) { const containerDoc = Cast(target.annotationOn, Doc, null) || target; const targetContext = Cast(containerDoc?.context, Doc, null); const targetNavContext = !Doc.AreProtosEqual(targetContext, currentContext) ? targetContext : undefined; - DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, doc, finished); + DocumentManager.Instance.jumpToDocument(target, zoom, (doc, finished) => createViewFunc(doc, StrCast(linkDoc.followLinkLocation, "add:right"), finished), targetNavContext, linkDoc, undefined, sourceDoc, finished); } else { finished?.(); } diff --git a/src/client/views/LightboxView.tsx b/src/client/views/LightboxView.tsx index eb3d62779..a2088a8fc 100644 --- a/src/client/views/LightboxView.tsx +++ b/src/client/views/LightboxView.tsx @@ -89,7 +89,7 @@ export class LightboxView extends React.Component { const target = LightboxView.LightboxDocTarget = LightboxView.LightboxFuture?.pop(); const docView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (docView && target) { - docView.focus(target, true, 0.9); + docView.focus(target, { willZoom: true, scale: 0.9 }); if (LightboxView.LightboxHistory?.lastElement() !== target) LightboxView.LightboxHistory?.push(target); } else { if (!target && LightboxView.path.length) { @@ -120,7 +120,7 @@ export class LightboxView extends React.Component { const docView = target && DocumentManager.Instance.getLightboxDocumentView(target); if (docView && target) { if (LightboxView.LightboxFuture?.lastElement() !== previous) LightboxView.LightboxFuture?.push(previous!); - docView.focus(target, true, 0.9); + docView.focus(target, { willZoom: true, scale: 0.9 }); } else { LightboxView.SetLightboxDoc(target); } @@ -138,6 +138,7 @@ export class LightboxView extends React.Component { if (coll) { const fieldKey = Doc.LayoutFieldKey(coll); LightboxView.SetLightboxDoc(coll, [...DocListCast(coll[fieldKey]), ...DocListCast(coll[fieldKey + "-annotations"])]); + TabDocView.PinDoc(coll, { hidePresBox: true }); } setTimeout(() => this.stepForward()); } diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index efbab3ede..e3e753902 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -259,8 +259,8 @@ export class CollectionStackedTimeline extends CollectionSubView Promise, docTransform?: Transform) => { this.props.playLink(mark); - this.props.focus(doc, willZoom, scale, afterFocus, docTransform); - } + this.props.focus(doc, { willZoom, scale, afterFocus, docTransform }); + }; return { anchor, view: anchor.view = r)} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 2d03c5279..1d6112745 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -21,7 +21,7 @@ import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { EditableView } from "../EditableView"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; -import { DocumentView, DocAfterFocusFunc, DocumentViewProps } from "../nodes/DocumentView"; +import { DocumentView, DocumentViewProps, DocFocusOptions } from "../nodes/DocumentView"; import { FieldViewProps } from "../nodes/FieldView"; import { StyleProp } from "../StyleProvider"; import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; @@ -168,7 +168,7 @@ export class CollectionStackingView extends CollectionSubView { + focusDocument = (doc: Doc, options?: DocFocusOptions) => { Doc.BrushDoc(doc); Doc.linkFollowHighlight(doc); @@ -181,9 +181,11 @@ export class CollectionStackingView extends CollectionSubView afterFocus ? afterFocus(moved) : false; - this.props.focus(this.rootDoc, willZoom, scale, (didFocus: boolean) => - new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed))); + const endFocus = async (moved: boolean) => options?.afterFocus ? options?.afterFocus(moved) : false; + this.props.focus(this.rootDoc, { + willZoom: options?.willZoom, scale: options?.scale, afterFocus: (didFocus: boolean) => + new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)) + }); } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index d62b8b6f6..4c792186a 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -14,6 +14,7 @@ import { Cast, NumCast, StrCast } from "../../../fields/Types"; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, returnEmptyDoclist, returnFalse, returnTrue, setupMoveUpEvents, Utils } from "../../../Utils"; import { DocServer } from "../../DocServer"; +import { DocUtils } from '../../documents/Documents'; import { DocumentType } from '../../documents/DocumentTypes'; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from '../../util/DocumentManager'; @@ -23,7 +24,7 @@ import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { LightboxView } from '../LightboxView'; -import { DocAfterFocusFunc, DocumentView, DocumentViewProps } from "../nodes/DocumentView"; +import { DocFocusOptions, DocumentView, DocumentViewProps } from "../nodes/DocumentView"; import { FieldViewProps } from '../nodes/FieldView'; import { PinProps, PresBox, PresMovement } from '../nodes/PresBox'; import { DefaultLayerProvider, DefaultStyleProvider, StyleLayers, StyleProp } from '../StyleProvider'; @@ -34,7 +35,6 @@ import { CollectionViewType } from './CollectionView'; import "./TabDocView.scss"; import React = require("react"); import Color = require('color'); -import { DocUtils } from '../../documents/Documents'; const _global = (window /* browser */ || global /* node */) as any; interface TabDocViewProps { @@ -209,7 +209,7 @@ export class TabDocView extends React.Component { const tabs = Cast(sublists[0], Doc, null); const tabdocs = await DocListCastAsync(tabs.data); runInAction(() => { - if (!tabdocs?.includes(curPres)) { + if (!pinProps?.hidePresBox && !tabdocs?.includes(curPres)) { tabdocs?.push(curPres); // bcz: Argh! this is annoying. if multiple documents are pinned, this will get called multiple times before the presentation view is drawn. Thus it won't be in the tabdocs list and it will get created multple times. so need to explicilty add the presbox to the list of open tabs CollectionDockingView.AddSplit(curPres, "right"); } @@ -279,7 +279,10 @@ export class TabDocView extends React.Component { case "close": return CollectionDockingView.CloseSplit(doc, locationParams); case "fullScreen": return CollectionDockingView.OpenFullScreen(doc); case "replace": return CollectionDockingView.ReplaceTab(doc, locationParams, this.stack); - case "lightbox": return LightboxView.AddDocTab(doc, location); + case "lightbox": { + // TabDocView.PinDoc(doc, { hidePresBox: true }); + return LightboxView.AddDocTab(doc, location); + } case "inPlace": case "add": default: @@ -357,11 +360,25 @@ export class TabDocView extends React.Component { ; } - focusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => { + @action + focusFunc = (doc: Doc, options?: DocFocusOptions) => { + const vals = (!options?.originalTarget || options?.originalTarget === this._document) && this.view?.ComponentView?.freeformData?.(true); + if (vals && this._document) { + const focusSpeed = 1000; + this._document._panX = vals.panX; + this._document._panY = vals.panY; + this._document._viewScale = vals.scale; + this._document._viewTrasition = `transform ${focusSpeed}ms`; + setTimeout(action(() => { + this._document!._viewTransition = undefined; + options?.afterFocus?.(false); + }), focusSpeed); + } else { + options?.afterFocus?.(false); + } if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) } - afterFocus?.(false); } active = () => this._isActive; ScreenToLocalTransform = () => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 954b6478f..92916b5db 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,22 +1,23 @@ -import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction, trace } from "mobx"; +import { action, computed, IReactionDisposer, observable, ObservableMap, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { computedFn } from "mobx-utils"; -import { Doc, DocListCast, HeightSym, Opt, WidthSym, StrListCast } from "../../../../fields/Doc"; +import { Doc, HeightSym, Opt, StrListCast, WidthSym } from "../../../../fields/Doc"; import { collectionSchema, documentSchema } from "../../../../fields/documentSchemas"; import { Id } from "../../../../fields/FieldSymbols"; import { InkData, InkField, InkTool } from "../../../../fields/InkField"; import { List } from "../../../../fields/List"; import { RichTextField } from "../../../../fields/RichTextField"; -import { createSchema, makeInterface, listSpec } from "../../../../fields/Schema"; +import { createSchema, listSpec, makeInterface } from "../../../../fields/Schema"; import { ScriptField } from "../../../../fields/ScriptField"; import { BoolCast, Cast, FieldValue, NumCast, ScriptCast, StrCast } from "../../../../fields/Types"; import { TraceMobx } from "../../../../fields/util"; import { GestureUtils } from "../../../../pen-gestures/GestureUtils"; -import { aggregateBounds, intersectRect, returnFalse, returnOne, returnZero, setupMoveUpEvents, Utils, returnVal, returnTrue } from "../../../../Utils"; +import { aggregateBounds, intersectRect, returnFalse, setupMoveUpEvents, Utils } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; import { Docs, DocUtils } from "../../../documents/Documents"; import { DocumentType } from "../../../documents/DocumentTypes"; +import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager, dropActionType } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; @@ -30,13 +31,16 @@ import { undoBatch } from "../../../util/UndoManager"; import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"; import { Timeline } from "../../animationtimeline/Timeline"; import { ContextMenu } from "../../ContextMenu"; +import { DocumentDecorations } from "../../DocumentDecorations"; import { ActiveArrowEnd, ActiveArrowStart, ActiveDash, ActiveFillColor, ActiveInkBezierApprox, ActiveInkColor, ActiveInkWidth } from "../../InkingStroke"; -import { CollectionFreeFormDocumentView, CollectionFreeFormDocumentViewProps } from "../../nodes/CollectionFreeFormDocumentView"; -import { DocumentLinksButton } from "../../nodes/DocumentLinksButton"; -import { DocumentViewProps, DocAfterFocusFunc, DocumentView } from "../../nodes/DocumentView"; +import { LightboxView } from "../../LightboxView"; +import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; +import { DocFocusOptions, DocumentView, DocumentViewProps } from "../../nodes/DocumentView"; +import { FieldViewProps } from "../../nodes/FieldView"; import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import { PresBox } from "../../nodes/PresBox"; +import { StyleLayers, StyleProp } from "../../StyleProvider"; import { CollectionDockingView } from "../CollectionDockingView"; import { CollectionSubView } from "../CollectionSubView"; import { CollectionViewType } from "../CollectionView"; @@ -45,12 +49,6 @@ import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCurso import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; -import { StyleProp, StyleLayers } from "../../StyleProvider"; -import { DocumentDecorations } from "../../DocumentDecorations"; -import { FieldViewProps } from "../../nodes/FieldView"; -import { reset } from "colors"; -import { LightboxView } from "../../LightboxView"; export const panZoomSchema = createSchema({ _panX: "number", @@ -118,8 +116,7 @@ export class CollectionFreeFormView extends CollectionSubView this.fitToContentVals?.panX ?? NumCast(this.Document._panX); - private panY = () => this.fitToContentVals?.panY ?? NumCast(this.Document._panY); - private zoomScaling = () => (this.fitToContentVals?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)) / this.parentScaling; + private panX = () => this.freeformData()?.panX ?? NumCast(this.Document._panX); + private panY = () => this.freeformData()?.panY ?? NumCast(this.Document._panY); + private zoomScaling = () => (this.freeformData()?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1)) / this.parentScaling; @computed get cachedCenteringShiftX(): number { const scaling = this.fitToContent || !this.contentScaling ? 1 : this.contentScaling; return this.props.isAnnotationOverlay ? 0 : this.props.PanelWidth() / 2 / this.parentScaling / scaling; // shift so pan position is at center of window for non-overlay collections @@ -860,7 +857,7 @@ export class CollectionFreeFormView extends CollectionSubView { + focusDocument = (doc: Doc, options?: DocFocusOptions) => { const state = HistoryUtil.getState(); // TODO This technically isn't correct if type !== "doc", as @@ -918,23 +915,26 @@ export class CollectionFreeFormView extends CollectionSubView { doc.hidden && Doc.UnHighlightDoc(doc); - const resetView = afterFocus ? await afterFocus(moved) : false; + const resetView = options?.afterFocus ? await options?.afterFocus(moved) : false; if (resetView) { const restoreState = LightboxView.LightboxDoc !== this.props.Document && LightboxState ? LightboxState : savedState; this.Document._panX = restoreState.panX; @@ -959,8 +959,11 @@ export class CollectionFreeFormView extends CollectionSubView - new Promise(res => setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))), xf); + this.props.focus(cantTransform ? doc : this.rootDoc, { + ...options, docTransform: xf, + afterFocus: (didFocus: boolean) => new Promise(res => + setTimeout(async () => res(await endFocus(didMove || didFocus)), Math.max(0, focusSpeed - (Date.now() - startTime)))) + }); } } @@ -973,8 +976,12 @@ export class CollectionFreeFormView extends CollectionSubView { return this.fitToContentVals; } + freeformData = (force?: boolean) => this.fitToContent || force ? this.fitToContentVals : undefined; @action componentDidMount() { super.componentDidMount?.(); diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 4b0422ed3..e93d7ff72 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -153,7 +153,7 @@ export class CollectionFreeFormDocumentView extends DocComponent (this.sizeProvider?.width || this.props.PanelWidth?.()); panelHeight = () => (this.sizeProvider?.height || this.props.PanelHeight?.()); screenToLocalTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.X, -this.Y); - focusDoc = (doc: Doc) => this.props.focus(doc, false); + focusDoc = (doc: Doc) => this.props.focus(doc); returnThis = () => this; render() { TraceMobx(); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9801de1a2..9d91311f6 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,6 +1,6 @@ import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast, HeightSym } from "../../../fields/Doc"; +import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; @@ -37,13 +37,20 @@ import { DocumentLinksButton } from './DocumentLinksButton'; import "./DocumentView.scss"; import { FieldViewProps } from "./FieldView"; import { LinkAnchorBox } from './LinkAnchorBox'; +import { LinkDocPreview } from "./LinkDocPreview"; import { PresBox } from './PresBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); -import { LinkDocPreview } from "./LinkDocPreview"; +export interface DocFocusOptions { + originalTarget?: Doc; + willZoom?: boolean; + scale?: number; + afterFocus?: DocAfterFocusFunc; + docTransform?: Transform; +} export type DocAfterFocusFunc = (notFocused: boolean) => Promise; -export type DocFocusFunc = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc, docTransform?: Transform) => void; +export type DocFocusFunc = (doc: Doc, options?: DocFocusOptions) => void; export type StyleProviderFunc = (doc: Opt, props: Opt, property: string) => any; export interface DocComponentView { getAnchor?: () => Doc; @@ -52,7 +59,7 @@ export interface DocComponentView { forward?: () => boolean; url?: () => string; submitURL?: (url: string) => boolean; - freeformData?: () => Opt<{ panX: number, panY: number, scale: number }>; + freeformData?: (force?: boolean) => Opt<{ panX: number, panY: number, scale: number }>; } export interface DocumentViewSharedProps { renderDepth: number; @@ -376,11 +383,13 @@ export class DocumentViewInternal extends DocComponent { + focus = (doc: Doc, options?: DocFocusOptions) => { const focusSpeed = this._componentView?.scrollFocus?.(doc, !LinkDocPreview.LinkInfo); // bcz: smooth parameter should really be passed into focus() instead of inferred here - const endFocus = focusSpeed === undefined ? afterFocus : async (moved: boolean) => afterFocus ? afterFocus(true) : false; - this.props.focus(docTransform ? doc : this.rootDoc, willZoom, scale, (didFocus: boolean) => - new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0)), docTransform); + const endFocus = focusSpeed === undefined ? options?.afterFocus : async (moved: boolean) => options?.afterFocus ? options?.afterFocus(true) : false; + this.props.focus(options?.docTransform ? doc : this.rootDoc, { + ...options, afterFocus: (didFocus: boolean) => + new Promise(res => setTimeout(async () => res(endFocus ? await endFocus(didFocus) : false), focusSpeed ?? 0)) + }); } onClick = action((e: React.MouseEvent | React.PointerEvent) => { @@ -439,7 +448,7 @@ export class DocumentViewInternal extends DocComponent 0)) { // onDragStart implies a button doc that we don't want to select when clicking. RootDocument & isTemplaetForField implies we're clicking on part of a template instance and we want to select the whole template, not the part @@ -575,7 +584,7 @@ export class DocumentViewInternal extends DocComponent this.props.ScreenToLocalTransform().translate(0, -this.headerMargin); contentScaling = () => this.ContentScale; onClickFunc = () => this.onClickHandler; - setContentView = (view: { getAnchor: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view; + setContentView = (view: { getAnchor?: () => Doc, forward?: () => boolean, back?: () => boolean }) => this._componentView = view; @observable contentsActive: () => boolean = returnFalse; @action setContentsActive = (setActive: () => boolean) => this.contentsActive = setActive; @computed get contents() { @@ -918,8 +927,8 @@ export class DocumentView extends React.Component { toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); contentsActive = () => this.docView?.contentsActive(); - focus = (doc: Doc, willZoom?: boolean, scale?: number, afterFocus?: DocAfterFocusFunc) => { - return this.docView?.focus(doc, willZoom, scale, afterFocus); + focus = (doc: Doc, options?: DocFocusOptions) => { + return this.docView?.focus(doc, options); } getBounds = () => { if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 441d6232a..74bff2bfe 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -152,7 +152,7 @@ export class ImageBox extends ViewBoxAnnotatableComponent this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes; + resolution = () => this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes @undoBatch rotate = action(() => { diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index b941c07f6..33147e7f3 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -138,7 +138,7 @@ export class PDFBox extends ViewBoxAnnotatableComponent) => this._searchString = e.currentTarget.value; settingsPanel() { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9f9f11ab5..a63e0daef 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1456,7 +1456,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } - (e.nativeEvent as any).formattedHandled = true; + this.props.isSelected(true) && ((e.nativeEvent as any).formattedHandled = true); if (this.props.isSelected(true)) { // if text box is selected, then it consumes all click events // e.stopPropagation(); // bcz: not sure why this was here. We need to allow the DocumentView to get clicks to process doubleClicks -- cgit v1.2.3-70-g09d2 From 690b803b9793659ebaebffc79c98dce71f4cf7ec Mon Sep 17 00:00:00 2001 From: bobzel Date: Sun, 14 Feb 2021 23:37:00 -0500 Subject: mae RichTextField parseable from keyvalue. got rid of pause to insert text timestamps for simplicity. fixed transition typo. --- src/client/util/CurrentUserUtils.ts | 2 +- src/client/util/type_decls.d | 4 ++++ src/client/views/collections/TabDocView.tsx | 2 +- .../views/nodes/formattedText/FormattedTextBox.tsx | 19 +++---------------- 4 files changed, 9 insertions(+), 18 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index f683ac2a0..db1b13f4e 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -192,7 +192,7 @@ export class CurrentUserUtils { details.text = new RichTextField(JSON.stringify(detailedTemplate), buxtonFieldKeys.join(" ")); const shared = { _chromeStatus: "disabled", _autoHeight: true, _xMargin: 0 }; - const detailViewOpts = { title: "detailView", _width: 300, _fontFamily: "Arial", _fontSize: "12px" }; + const detailViewOpts = { title: "detailview", _width: 300, _fontFamily: "Arial", _fontSize: "12px" }; const descriptionWrapperOpts = { title: "descriptions", _height: 300, _columnWidth: -1, treeViewHideTitle: true, _pivotField: "title", system: true }; const descriptionWrapper = MasonryDocument([details, short, long], { ...shared, ...descriptionWrapperOpts }); diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index ab6c94f83..3b23d5d56 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -158,6 +158,10 @@ declare abstract class URLField extends ObjectField { constructor(url: URL); } +declare class RichTextField extends URLField { + [Copy](): ObjectField; + constructor(data:string, text: string); +} declare class AudioField extends URLField { [Copy](): ObjectField; } declare class VideoField extends URLField { [Copy](): ObjectField; } declare class ImageField extends URLField { [Copy](): ObjectField; } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index d2a83b1dd..c1a1a6f04 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -368,7 +368,7 @@ export class TabDocView extends React.Component { this._document._panX = vals.panX; this._document._panY = vals.panY; this._document._viewScale = vals.scale; - this._document._viewTrasition = `transform ${focusSpeed}ms`; + this._document._viewTransition = `transform ${focusSpeed}ms`; setTimeout(action(() => { this._document!._viewTransition = undefined; options?.afterFocus?.(false); diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index a63e0daef..9e9be74f3 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -155,7 +155,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp FormattedTextBox.Instance = this; this.updateHighlights(); this._recordingStart = Date.now(); - this.layoutDoc._timeStampOnEnter = true; } public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } @@ -311,15 +310,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp (curText !== Cast(this.dataDoc[this.fieldKey], RichTextField)?.Text) && (this.dataDoc[this.props.fieldKey + "-lastModified"] = new DateField(new Date(Date.now()))); if ((!curTemp && !curProto) || curText || json.includes("dash")) { // if no template, or there's text that didn't come from the layout template, write it to the document. (if this is driven by a template, then this overwrites the template text which is intended) if (removeSelection(json) !== removeSelection(curLayout?.Data)) { - if (!this._pause && !this.layoutDoc._timeStampOnEnter) { - timeStamp = setTimeout(() => this.pause(), 10 * 1000); // 10 seconds delay for time stamp - } - - // if 10 seconds have passed, insert time stamp the next time you type - if (this._pause) { - this._pause = false; - this.insertTime(); - } !curText && tx.storedMarks?.filter(m => m.type.name === "pFontSize").map(m => Doc.UserDoc().fontSize = this.layoutDoc._fontSize = (m.attrs.fontSize + "px")); !curText && tx.storedMarks?.filter(m => m.type.name === "pFontFamily").map(m => Doc.UserDoc().fontFamily = this.layoutDoc._fontFamily = m.attrs.fontFamily); this.dataDoc[this.props.fieldKey] = new RichTextField(json, curText); @@ -353,8 +343,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } } - pause = () => this._pause = true; - // for inserting timestamps insertTime = () => { let linkTime; @@ -366,7 +354,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp linkAnchor = anchor; } }); - if (this._editorView) { + if (this._editorView && linkTime) { const state = this._editorView.state; const now = Date.now(); let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.floor(now / 1000) }); @@ -382,7 +370,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp } const path = (this._editorView.state.selection.$from as any).path; - if (linkAnchor && linkTime && path[path.length - 3].type !== this._editorView.state.schema.nodes.code_block) { + if (linkAnchor && 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 from = state.selection.from; @@ -662,7 +650,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp uicontrols.push({ description: `${FormattedTextBox.CanAnnotate ? "Hide" : "Show"} Annotation Bar`, event: () => FormattedTextBox.CanAnnotate = !FormattedTextBox.CanAnnotate, icon: "expand-arrows-alt" }); uicontrols.push({ description: `${this.layoutDoc._showAudio ? "Hide" : "Show"} Dictation Icon`, event: () => this.layoutDoc._showAudio = !this.layoutDoc._showAudio, icon: "expand-arrows-alt" }); uicontrols.push({ description: "Show Highlights...", noexpand: true, subitems: highlighting, icon: "hand-point-right" }); - !Doc.UserDoc().noviceMode && uicontrols.push({ description: `Create TimeStamp When ${this.layoutDoc._timeStampOnEnter ? "Pause" : "Enter"}`, event: () => this.layoutDoc._timeStampOnEnter = !this.layoutDoc._timeStampOnEnter, icon: "expand-arrows-alt" }); !Doc.UserDoc().noviceMode && uicontrols.push({ description: "Broadcast Message", event: () => DocServer.GetRefField("rtfProto").then(proto => proto instanceof Doc && (proto.BROADCAST_MESSAGE = Cast(this.rootDoc[this.fieldKey], RichTextField)?.Text)), icon: "expand-arrows-alt" @@ -1595,7 +1582,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp RichTextMenu.Instance.updateMenu(undefined, undefined, undefined); } else { if (e.key === "Tab" || e.key === "Enter") { - if (e.key === "Enter" && this.layoutDoc._timeStampOnEnter) { + if (e.key === "Enter") { this.insertTime(); } e.preventDefault(); -- cgit v1.2.3-70-g09d2 From bd5ca49636c5ff0d480003f9479cf40a562b4589 Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 15 Feb 2021 15:01:06 -0500 Subject: cleaned up showTitle and showAudio stuff just a bit. --- src/client/documents/Documents.ts | 3 +- src/client/views/DocumentButtonBar.tsx | 10 +-- src/client/views/PropertiesButtons.scss | 4 + src/client/views/PropertiesButtons.tsx | 13 ++- src/client/views/StyleProvider.tsx | 6 +- src/client/views/TemplateMenu.tsx | 4 +- src/client/views/nodes/DocumentView.scss | 19 +++++ src/client/views/nodes/DocumentView.tsx | 92 ++++++++++++++++++++-- src/client/views/nodes/ImageBox.scss | 18 ----- src/client/views/nodes/ImageBox.tsx | 83 +------------------ .../views/nodes/formattedText/FormattedTextBox.tsx | 4 - src/fields/documentSchemas.ts | 3 +- 12 files changed, 127 insertions(+), 132 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index fee2679b6..7502aa685 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -76,8 +76,7 @@ export interface DocumentOptions { _fitWidth?: boolean; _fitToBox?: boolean; // whether a freeformview should zoom/scale to create a shrinkwrapped view of its contents _freeformLOD?: boolean; // whether to use LOD to render a freeform document - _showTitleHover?: string; // - _showTitle?: string; // which field to display in the title area. leave empty to have no title + _showTitle?: string; // field name to display in header (:hover is an optional suffix) _showCaption?: string; // which field to display in the caption area. leave empty to have no caption _scrollTop?: number; // scroll location for pdfs _noAutoscroll?: boolean;// whether collections autoscroll when this item is dragged diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index eeef94d74..209e750d6 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -312,22 +312,20 @@ export class DocumentButtonBar extends React.Component<{ views: () => (DocumentV return false; } - _ref = React.createRef(); + _ref = React.createRef(); @observable _tooltipOpen: boolean = false; @computed get templateButton() { const view0 = this.view0; - const templates: Map = new Map(); const views = this.props.views(); - Array.from(["Caption", "Title", "TitleHover"]).map(template => - templates.set(template, views.reduce((checked, doc) => checked || doc?.props.Document["_show" + template] ? true : false, false as boolean))); return !view0 ? (null) : Tap to Customize Layout. Drag an embeddable alias
} open={this._tooltipOpen} onClose={action(() => this._tooltipOpen = false)} placement="bottom">
!this._ref.current?.getBoundingClientRect().width && (this._tooltipOpen = true))} > + onPointerEnter={action(() => !(this._ref.current as any as HTMLElement)?.getBoundingClientRect().width && (this._tooltipOpen = true))} > this._aliasDown = true)} onClose={action(() => this._aliasDown = false)} - content={!this._aliasDown ? (null) :
v).map(v => v as DocumentView)} templates={templates} />
}> + content={!this._aliasDown ? (null) : + v).map(v => v as DocumentView)} />}>
{}
diff --git a/src/client/views/PropertiesButtons.scss b/src/client/views/PropertiesButtons.scss index d63eb874c..ba212da7f 100644 --- a/src/client/views/PropertiesButtons.scss +++ b/src/client/views/PropertiesButtons.scss @@ -47,6 +47,10 @@ $linkGap : 3px; background-color: white; color: black; } +.propertiesButtons-linkButton-empty.toggle-hover { + background-color: gray; + color: black; +} .propertiesButtons-linkButton-empty.toggle-off { color: white; } diff --git a/src/client/views/PropertiesButtons.tsx b/src/client/views/PropertiesButtons.tsx index 9a836313c..53a017592 100644 --- a/src/client/views/PropertiesButtons.tsx +++ b/src/client/views/PropertiesButtons.tsx @@ -6,7 +6,7 @@ import { observer } from "mobx-react"; import { Doc } from "../../fields/Doc"; import { InkField } from '../../fields/InkField'; import { RichTextField } from '../../fields/RichTextField'; -import { Cast, NumCast } from "../../fields/Types"; +import { Cast, NumCast, StrCast } from "../../fields/Types"; import { ImageField } from '../../fields/URLField'; import { GoogleAuthenticationManager } from '../apis/GoogleAuthenticationManager'; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; @@ -222,10 +222,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { } @undoBatch - @action - setDictation = () => { - SelectionManager.Views().forEach(dv => dv.rootDoc._showAudio = dv.rootDoc._showAudio === !dv.rootDoc._showAudio); - } + setDictation = () => SelectionManager.Views().forEach(dv => dv.rootDoc._showAudio = !dv.rootDoc._showAudio); @computed get dictationButton() { @@ -244,7 +241,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { @undoBatch @action setTitle = () => { - SelectionManager.Views().forEach(dv => dv.rootDoc._showTitle = dv.rootDoc._showTitle === undefined ? "title" : undefined); + SelectionManager.Views().forEach(dv => dv.rootDoc._showTitle = !dv.rootDoc._showTitle ? "title" : dv.rootDoc._showTitle === "title" ? "title:hover" : undefined); } @computed @@ -252,7 +249,7 @@ export class PropertiesButtons extends React.Component<{}, {}> { const targetDoc = this.selectedDoc; return !targetDoc ? (null) : {"Show Title Header"}
} placement="top">
-
+
Title
@@ -502,7 +499,7 @@ export class PropertiesButtons extends React.Component<{}, {}> {
{this.lockButton}
-
+
{this.dictationButton}
diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 058d21c92..0ab8c5dfb 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -36,7 +36,8 @@ export enum StyleProp { PointerEvents = "pointerEvents", // pointer events for DocumentView -- inherits pointer events if not specified Decorations = "decorations", // additional decoration to display above a DocumentView -- currently only used to display a Lock for making things background HeaderMargin = "headerMargin", // margin at top of documentview, typically for displaying a title -- doc contents will start below that - ShowTitle = "showTitle", // whether to display a title on a Document + TitleHeight = "titleHeight", // Height of Title area + ShowTitle = "showTitle", // whether to display a title on a Document (optional :hover suffix) } function darkScheme() { return BoolCast(CurrentUserUtils.ActiveDashboard?.darkScheme); } @@ -86,7 +87,8 @@ export function DefaultStyleProvider(doc: Opt, props: Opt; + templates?: Map; } @@ -115,7 +115,7 @@ export class TemplateMenu extends React.Component { const addedTypes = Doc.UserDoc().noviceMode ? [] : DocListCast(Cast(Doc.UserDoc()["template-buttons"], Doc, null)?.data); const layout = Doc.Layout(firstDoc); const templateMenu: Array = []; - this.props.templates.forEach((checked, template) => + this.props.templates?.forEach((checked, template) => templateMenu.push()); templateMenu.push(); templateMenu.push(); diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 6f041e5ef..749ffa7fd 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -45,6 +45,25 @@ width:10px !important; } } + + .documentView-audioBackground { + display: inline-block; + width: 10%; + position: absolute; + top: 0px; + left: 0px; + border-radius: 25px; + background: white; + opacity: 0.3; + + svg { + width: 90% !important; + height: 70%; + position: absolute; + left: 5%; + top: 15%; + } + } .documentView-treeView { max-height: 1.5em; text-overflow: ellipsis; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 7dea3784e..9c844c51e 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,18 +1,22 @@ +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import { AclAdmin, AclEdit, AclPrivate, DataSym, Doc, DocListCast, Field, Opt, StrListCast } from "../../../fields/Doc"; import { Document } from '../../../fields/documentSchemas'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; +import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; import { ScriptField } from '../../../fields/ScriptField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from "../../../fields/Types"; +import { AudioField } from "../../../fields/URLField"; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { MobileInterface } from '../../../mobile/MobileInterface'; import { emptyFunction, hasDescendantTarget, OmitKeys, returnFalse, returnVal, Utils } from "../../../Utils"; import { GooglePhotos } from '../../apis/google_docs/GooglePhotosClientUtils'; import { Docs, DocUtils } from "../../documents/Documents"; import { DocumentType } from '../../documents/DocumentTypes'; +import { Networking } from "../../Network"; import { CurrentUserUtils } from '../../util/CurrentUserUtils'; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, dropActionType } from "../../util/DragManager"; @@ -41,6 +45,17 @@ import { LinkDocPreview } from "./LinkDocPreview"; import { PresBox } from './PresBox'; import { RadialMenu } from './RadialMenu'; import React = require("react"); +const { Howl } = require('howler'); + +interface Window { + MediaRecorder: MediaRecorder; +} + +declare class MediaRecorder { + // whatever MediaRecorder has + constructor(e: any); +} + export enum ViewAdjustment { resetView = 1, @@ -135,6 +150,7 @@ export interface DocumentViewInternalProps extends DocumentViewProps { @observer export class DocumentViewInternal extends DocComponent(Document) { @observable _animateScalingTo = 0; + @observable _audioState = 0; private _downX: number = 0; private _downY: number = 0; private _firstX: number = -1; @@ -146,8 +162,8 @@ export class DocumentViewInternal extends DocComponent; protected _multiTouchDisposer?: InteractionUtils.MultiTouchEventDisposer; + _componentView: Opt; private get topMost() { return this.props.renderDepth === 0; } private get active() { return this.props.isSelected(true) || this.props.parentActive(true); } @@ -165,6 +181,7 @@ export class DocumentViewInternal extends DocComponent boolean) => this.contentsActive = setActive; @computed get contents() { TraceMobx(); + const audioView = !this.layoutDoc._showAudio ? (null) : +
+ +
; return
} + + {audioView}
; } @@ -790,11 +819,62 @@ export class DocumentViewInternal extends DocComponent); } + @action + onPointerEnter = () => { + const self = this; + const audioAnnos = DocListCast(this.dataDoc[this.LayoutFieldKey + "-audioAnnotations"]); + if (audioAnnos && audioAnnos.length && this._audioState === 0) { + const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)]; + anno.data instanceof AudioField && new Howl({ + src: [anno.data.url.href], + format: ["mp3"], + autoplay: true, + loop: false, + volume: 0.5, + onend: function () { + runInAction(() => self._audioState = 0); + } + }); + this._audioState = 1; + } + } + recordAudioAnnotation = () => { + let gumStream: any; + let recorder: any; + const self = this; + navigator.mediaDevices.getUserMedia({ + audio: true + }).then(function (stream) { + gumStream = stream; + recorder = new MediaRecorder(stream); + recorder.ondataavailable = async (e: any) => { + const [{ result }] = await Networking.UploadFilesToServer(e.data); + if (!(result instanceof Error)) { + const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 }); + audioDoc.treeViewExpandedView = "layout"; + const audioAnnos = Cast(self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"], listSpec(Doc)); + if (audioAnnos === undefined) { + self.dataDoc[self.LayoutFieldKey + "-audioAnnotations"] = new List([audioDoc]); + } else { + audioAnnos.push(audioDoc); + } + } + }; + runInAction(() => self._audioState = 2); + recorder.start(); + setTimeout(() => { + recorder.stop(); + runInAction(() => self._audioState = 0); + gumStream.getAudioTracks()[0].stop(); + }, 5000); + }); + } + captionStyleProvider = (doc: Opt, props: Opt, property: string) => this.props?.styleProvider?.(doc, props, property + ":caption"); @computed get innards() { TraceMobx(); - const showTitle = this.ShowTitle; - const showTitleHover = StrCast(this.layoutDoc._showTitleHover); + const showTitle = this.ShowTitle?.split(":")[0]; + const showTitleHover = this.ShowTitle?.includes(":hover"); const showCaption = StrCast(this.layoutDoc._showCaption); const captionView = !showCaption ? (null) :
users.user.email === this.dataDoc.author)?.userColor || (this.rootDoc.type === DocumentType.RTF ? StrCast(Doc.SharingDoc().userColor) : "rgba(0,0,0,0.4)"), pointerEvents: this.onClickHandler || this.Document.ignoreClick ? "none" : undefined, }}> @@ -932,9 +1012,7 @@ export class DocumentView extends React.Component { toggleNativeDimensions = () => this.docView && Doc.toggleNativeDimensions(this.layoutDoc, this.docView.ContentScale, this.props.PanelWidth(), this.props.PanelHeight()); contentsActive = () => this.docView?.contentsActive(); - focus = (doc: Doc, options?: DocFocusOptions) => { - return this.docView?.focus(doc, options); - } + focus = (doc: Doc, options?: DocFocusOptions) => this.docView?.focus(doc, options); getBounds = () => { if (!this.docView || !this.docView.ContentDiv || this.docView.props.renderDepth === 0 || this.docView.props.treeViewDoc || Doc.AreProtosEqual(this.props.Document, Doc.UserDoc())) { return undefined; diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 41055e2db..6d60c70bf 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -94,24 +94,6 @@ height: 100%; } -.imageBox-audioBackground { - display: inline-block; - width: 10%; - position: absolute; - top: 0px; - left: 0px; - border-radius: 25px; - background: white; - opacity: 0.3; - - svg { - width: 90% !important; - height: 70%; - position: absolute; - left: 5%; - top: 15%; - } -} .imageBox-fader { position: relative; diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 74bff2bfe..728e5e02f 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -1,4 +1,3 @@ -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; import { Dictionary } from 'typescript-collections'; @@ -10,12 +9,11 @@ import { ObjectField } from '../../../fields/ObjectField'; import { createSchema, listSpec, makeInterface } from '../../../fields/Schema'; import { ComputedField } from '../../../fields/ScriptField'; import { Cast, NumCast, StrCast } from '../../../fields/Types'; -import { AudioField, ImageField } from '../../../fields/URLField'; +import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; 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'; import { Networking } from '../../Network'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; @@ -30,8 +28,6 @@ import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); const path = require('path'); -const { Howl } = require('howler'); - export const pageSchema = createSchema({ _curPage: "number", @@ -39,16 +35,6 @@ export const pageSchema = createSchema({ googlePhotosUrl: "string", googlePhotosTags: "string" }); - -interface Window { - MediaRecorder: MediaRecorder; -} - -declare class MediaRecorder { - // whatever MediaRecorder has - constructor(e: any); -} - type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; const ImageDocument = makeInterface(pageSchema, documentSchema); @@ -64,10 +50,9 @@ export class ImageBox extends ViewBoxAnnotatableComponent = React.createRef(); + private _curSuffix = "_m"; private _dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; - @observable private _audioState = 0; - @observable static _showControls: boolean; @observable uploadIcon = uploadIcons.idle; protected createDropTarget = (ele: HTMLDivElement) => { @@ -120,37 +105,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - let gumStream: any; - let recorder: any; - const self = this; - navigator.mediaDevices.getUserMedia({ - audio: true - }).then(function (stream) { - gumStream = stream; - recorder = new MediaRecorder(stream); - recorder.ondataavailable = async (e: any) => { - const [{ result }] = await Networking.UploadFilesToServer(e.data); - if (!(result instanceof Error)) { - const audioDoc = Docs.Create.AudioDocument(Utils.prepend(result.accessPaths.agnostic.client), { title: "audio test", _width: 200, _height: 32 }); - audioDoc.treeViewExpandedView = "layout"; - const audioAnnos = Cast(self.dataDoc[self.fieldKey + "-audioAnnotations"], listSpec(Doc)); - if (audioAnnos === undefined) { - self.dataDoc[self.fieldKey + "-audioAnnotations"] = new List([audioDoc]); - } else { - audioAnnos.push(audioDoc); - } - } - }; - runInAction(() => self._audioState = 2); - recorder.start(); - setTimeout(() => { - recorder.stop(); - runInAction(() => self._audioState = 0); - gumStream.getAudioTracks()[0].stop(); - }, 5000); - }); - } @undoBatch resolution = () => this.layoutDoc._showFullRes = !this.layoutDoc._showFullRes @@ -250,29 +204,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent { - const self = this; - const audioAnnos = DocListCast(this.dataDoc[this.fieldKey + "-audioAnnotations"]); - if (audioAnnos && audioAnnos.length && this._audioState === 0) { - const anno = audioAnnos[Math.floor(Math.random() * audioAnnos.length)]; - anno.data instanceof AudioField && new Howl({ - src: [anno.data.url.href], - format: ["mp3"], - autoplay: true, - loop: false, - volume: 0.5, - onend: function () { - runInAction(() => self._audioState = 0); - } - }); - this._audioState = 1; - } - } - - audioDown = () => this.recordAudioAnnotation(); considerGooglePhotosLink = () => { const remoteUrl = this.dataDoc.googlePhotosUrl; @@ -387,16 +318,6 @@ export class ImageBox extends ViewBoxAnnotatableComponent
}
- {!this.layoutDoc._showAudio ? (null) : -
- -
} {this.considerDownloadIcon} {this.considerGooglePhotosLink()} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 9e9be74f3..779da91e6 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -258,13 +258,9 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp _lastText = ""; dispatchTransaction = (tx: Transaction) => { - let timeStamp; - clearTimeout(timeStamp); if (this._editorView) { - const metadata = tx.selection.$from.marks().find((m: Mark) => m.type === schema.marks.metadata); if (metadata) { - const range = tx.selection.$from.blockRange(tx.selection.$to); let text = range ? tx.doc.textBetween(range.start, range.end) : ""; let textEndSelection = tx.selection.to; diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index bdc498c97..df3dd1eb4 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -42,8 +42,7 @@ export const documentSchema = createSchema({ _yMargin: "number", // margin added on top/bottom of most documents to add separation from their container _overflow: "string", // sets overflow behvavior for CollectionFreeForm views _showCaption: "string", // whether editable caption text is overlayed at the bottom of the document - _showTitle: "string", // the fieldkey whose contents should be displayed at the top of the document - _showTitleHover: "string", // the showTitle should be shown only on hover + _showTitle: "string", // the fieldkey(s) whose contents should be displayed at the top of the document. separate multiple keys with ";". Use :hover suffix to indicate title should be shown on hover _showAudio: "boolean", // whether to show the audio record icon on documents _freeformLOD: "boolean", // whether to enable LOD switching for CollectionFreeFormViews _pivotField: "string", // specifies which field key should be used as the timeline/pivot axis -- cgit v1.2.3-70-g09d2 From 53b13f534431266e6efc856c8b5dc6e886cd171d Mon Sep 17 00:00:00 2001 From: bobzel Date: Mon, 15 Feb 2021 17:32:04 -0500 Subject: updated forward hypertext links using [[:]] notation. --- .../CollectionFreeFormLayoutEngines.tsx | 12 ++++-------- src/client/views/nodes/DocumentView.tsx | 1 + src/client/views/nodes/formattedText/RichTextRules.ts | 17 ++++++++++++----- 3 files changed, 17 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index edf473a90..5cf758e9b 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -1,16 +1,12 @@ -import { Doc, Field, FieldResult, WidthSym, HeightSym } from "../../../../fields/Doc"; -import { NumCast, StrCast, Cast } from "../../../../fields/Types"; -import { ScriptBox } from "../../ScriptBox"; -import { CompileScript } from "../../../util/Scripting"; -import { ScriptField } from "../../../../fields/ScriptField"; -import { OverlayView, OverlayElementOptions } from "../../OverlayView"; -import { emptyFunction, aggregateBounds } from "../../../../Utils"; -import React = require("react"); +import { Doc, Field, FieldResult, HeightSym, WidthSym } from "../../../../fields/Doc"; import { Id, ToString } from "../../../../fields/FieldSymbols"; import { ObjectField } from "../../../../fields/ObjectField"; import { RefField } from "../../../../fields/RefField"; import { listSpec } from "../../../../fields/Schema"; +import { Cast, NumCast, StrCast } from "../../../../fields/Types"; +import { aggregateBounds } from "../../../../Utils"; import { CurrentUserUtils } from "../../../util/CurrentUserUtils"; +import React = require("react"); export interface ViewDefBounds { type: string; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9c844c51e..4aa424af3 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -661,6 +661,7 @@ export class DocumentViewInternal extends DocComponent this.props.addDocTab(templateDoc, "add:right"), icon: "eye" }); DocListCast(this.Document.links).length && appearanceItems.splice(0, 0, { description: `${this.layoutDoc.hideLinkButton ? "Show" : "Hide"} Link Button`, event: action(() => this.layoutDoc.hideLinkButton = !this.layoutDoc.hideLinkButton), icon: "eye" }); + !Doc.UserDoc().noviceMode && appearanceItems.splice(0, 0, { description: `${!this.layoutDoc._showAudio ? "Show" : "Hide"} Audio Button`, event: action(() => this.layoutDoc._showAudio = !this.layoutDoc._showAudio), icon: "microphone" }); !appearance && cm.addItem({ description: "UI Controls...", subitems: appearanceItems, icon: "compass" }); if (!Doc.IsSystem(this.rootDoc) && this.props.ContainingCollectionDoc?._viewType !== CollectionViewType.Tree) { diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 99334b6db..9501d8021 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -274,15 +274,22 @@ export class RichTextRules { const docid = rawdocid ? normalizeEmail((!rawdocid.includes("@") ? Doc.CurrentUserEmail + rawdocid : rawdocid.substring(1))) : undefined; const value = match[2]?.substring(1); if (!fieldKey) { - const linkId = Utils.GenerateGuid(); if (docid) { DocServer.GetRefField(docid).then(docx => { + const rstate = this.TextBox.EditorView?.state; + const selection = rstate?.selection.$from.pos; + if (rstate) { + this.TextBox.EditorView?.dispatch(rstate.tr.setSelection(new TextSelection(rstate.doc.resolve(start), rstate.doc.resolve(end - 3)))); + } const target = ((docx instanceof Doc) && docx) || Docs.Create.FreeformDocument([], { title: rawdocid, _width: 500, _height: 500, }, docid); - DocUtils.MakeLink({ doc: this.Document }, { doc: target }, "portal to", undefined, linkId); + DocUtils.MakeLink({ doc: this.TextBox.getAnchor() }, { doc: target }, "portal to", undefined); + + const fstate = this.TextBox.EditorView?.state; + if (fstate && selection) { + this.TextBox.EditorView?.dispatch(fstate.tr.setSelection(new TextSelection(fstate.doc.resolve(selection)))); + } }); - const allLinks = [{ href: Utils.prepend("/doc/" + docid), title: docid, targetId: docid, linkId }]; - const link = state.schema.marks.linkAnchor.create({ allLinks, title: rawdocid, location: "add:right" }); - return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2).addMark(start, end - 3, link); + return state.tr.deleteRange(end - 1, end).deleteRange(start, start + 2); } return state.tr; } -- cgit v1.2.3-70-g09d2 From 05a9a3eed9a1afb55ec0ca55845b33cc745289e0 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 16 Feb 2021 00:59:28 -0500 Subject: updated pivot views to have a getAnchor() method to save the pivotField when linking. added an engineProp to layoutEngiens so that you can preview a change in a LinkDocPreview without modifying the document. --- src/client/util/DocumentManager.ts | 16 +-- src/client/views/StyleProvider.tsx | 3 +- .../views/collections/CollectionDockingView.tsx | 6 +- .../views/collections/CollectionStackingView.tsx | 2 - .../views/collections/CollectionTimeView.scss | 2 +- .../views/collections/CollectionTimeView.tsx | 115 ++++++++++++++++----- .../CollectionFreeFormLayoutEngines.tsx | 14 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 25 +++-- .../views/nodes/formattedText/FormattedTextBox.tsx | 8 +- .../formattedText/FormattedTextBoxComment.tsx | 2 +- .../views/nodes/formattedText/RichTextRules.ts | 3 +- 11 files changed, 128 insertions(+), 68 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 6013a2ef3..d028258b2 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -144,10 +144,6 @@ export class DocumentManager { originalTarget = originalTarget ?? targetDoc; const getFirstDocView = LightboxView.LightboxDoc ? DocumentManager.Instance.getLightboxDocumentView : DocumentManager.Instance.getFirstDocumentView; const docView = getFirstDocView(targetDoc, originatingDoc); - const highlight = () => { - const finalDocView = getFirstDocView(targetDoc); - finalDocView && Doc.linkFollowHighlight(finalDocView.rootDoc); - }; const focusAndFinish = (didFocus: boolean) => { if (originatingDoc?.isPushpin) { if (!didFocus || targetDoc.hidden) { @@ -157,7 +153,6 @@ export class DocumentManager { targetDoc.hidden && (targetDoc.hidden = undefined); docView?.select(false); } - highlight(); finished?.(); return false; }; @@ -167,11 +162,10 @@ export class DocumentManager { const contextDoc = contextDocs?.find(doc => Doc.AreProtosEqual(doc, targetDoc) || Doc.AreProtosEqual(doc, annotatedDoc)) ? docContext : undefined; const targetDocContext = contextDoc || annotatedDoc; const targetDocContextView = targetDocContext && getFirstDocView(targetDocContext); - if (!docView && targetDoc.type === DocumentType.TEXTANCHOR && rtfView) { - rtfView.focus(targetDoc); - } - else if (docView) { - docView.props.focus(targetDoc, { + const focusView = !docView && targetDoc.type === DocumentType.TEXTANCHOR && rtfView ? rtfView : docView; + if (focusView) { + focusView && Doc.linkFollowHighlight(focusView.rootDoc); + focusView.focus(targetDoc, { originalTarget, willZoom, afterFocus: (didFocus: boolean) => new Promise(res => { focusAndFinish(didFocus); @@ -181,7 +175,6 @@ export class DocumentManager { } else { if (!targetDocContext) { // we don't have a view and there's no context specified ... create a new view of the target using the dockFunc or default createViewFunc(Doc.BrushDoc(targetDoc), finished); // bcz: should we use this?: Doc.MakeAlias(targetDoc))); - highlight(); } else { // otherwise try to get a view of the context of the target if (targetDocContextView) { // we found a context view and aren't forced to create a new one ... focus on the context first.. targetDocContext._viewTransition = "transform 500ms"; @@ -202,7 +195,6 @@ export class DocumentManager { res(); }) }); // focus on the target in the context - highlight(); } else if (delay > 1500) { // we didn't find the target, so it must have moved out of the context. Go back to just creating it. if (closeContextIfNotFound) targetDocContextView.props.removeDocument?.(targetDocContextView.rootDoc); diff --git a/src/client/views/StyleProvider.tsx b/src/client/views/StyleProvider.tsx index 0ab8c5dfb..73a82d7ab 100644 --- a/src/client/views/StyleProvider.tsx +++ b/src/client/views/StyleProvider.tsx @@ -76,8 +76,7 @@ export function DefaultStyleProvider(doc: Opt, props: Opt doc) { } } +Scripting.addGlobal(function openInLightbox(doc: any) { LightboxView.AddDocTab(doc, "lightbox"); }, + "opens up document in a lightbox", "(doc: any)"); Scripting.addGlobal(function openOnRight(doc: any) { CollectionDockingView.AddSplit(doc, "right"); }, - "opens up the inputted document on the right side of the screen", "(doc: any)"); + "opens up document in tab on right side of the screen", "(doc: any)"); Scripting.addGlobal(function useRightSplit(doc: any, shiftKey?: boolean) { CollectionDockingView.ReplaceTab(doc, "right", undefined, shiftKey); }); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 0ac1cdb28..77046f5ea 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -170,7 +170,6 @@ export class CollectionStackingView extends CollectionSubView { Doc.BrushDoc(doc); - Doc.linkFollowHighlight(doc); let focusSpeed = 0; const found = this._mainCont && Array.from(this._mainCont.getElementsByClassName("documentView-node")).find((node: any) => node.id === doc[Id]); @@ -186,7 +185,6 @@ export class CollectionStackingView extends CollectionSubView new Promise(res => setTimeout(async () => res(await endFocus(didFocus)), focusSpeed)) }); - } styleProvider = (doc: Doc | undefined, props: Opt, property: string) => { diff --git a/src/client/views/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss index fa7c87f4e..d995cbcd2 100644 --- a/src/client/views/collections/CollectionTimeView.scss +++ b/src/client/views/collections/CollectionTimeView.scss @@ -75,7 +75,7 @@ .collectionTimeView-innards { display: inline-block; - width: calc(100% - 200px); + width: 100%; height: 100%; } } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 869e01fea..8c3e53dfd 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -1,6 +1,6 @@ import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, Opt, DocCastAsync } from "../../../fields/Doc"; +import { Doc, Opt, DocCastAsync, StrListCast } from "../../../fields/Doc"; import { List } from "../../../fields/List"; import { ObjectField } from "../../../fields/ObjectField"; import { RichTextField } from "../../../fields/RichTextField"; @@ -19,7 +19,12 @@ const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; import React = require("react"); -import { DocUtils } from "../../documents/Documents"; +import { DocUtils, Docs } from "../../documents/Documents"; +import { toUpper } from "lodash"; +import { DocumentManager } from "../../util/DocumentManager"; +import { listSpec } from "../../../fields/Schema"; +import { LinkManager } from "../../util/LinkManager"; +import { LinkDocPreview } from "../nodes/LinkDocPreview"; @observer export class CollectionTimeView extends CollectionSubView(doc => doc) { @@ -28,15 +33,56 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { @observable _collapsed: boolean = false; @observable _childClickedScript: Opt; @observable _viewDefDivClick: Opt; + + getAnchor = () => { + const anchor = Docs.Create.TextanchorDocument({ + title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, + useLinkSmallAnchor: true, + hideLinkButton: true, + annotationOn: this.rootDoc + }); + Doc.GetProto(anchor).pivotField = this.pivotField; + Doc.GetProto(anchor).docFilters = ObjectField.MakeCopy(this.layoutDoc._docFilters as ObjectField); + Doc.GetProto(anchor)._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc._docRangeFilters as ObjectField); + if (Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), null) !== undefined) { + Cast(this.dataDoc[this.props.fieldKey + "-annotations"], listSpec(Doc), []).push(anchor); + } else { + this.dataDoc[this.props.fieldKey + "-annotations"] = new List([anchor]); + } + return anchor; + } + async componentDidMount() { - const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.props.Document.type), ""); - const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List(['dropAction']); useRightSplit(alias, shiftKey); "; + this.props.setContentView?.(this); + const detailView = (await DocCastAsync(this.props.Document.childClickedOpenTemplateView)) || DocUtils.findTemplate("detailView", StrCast(this.rootDoc.type), ""); + ///const childText = "const alias = getAlias(self); switchView(alias, detailView); alias.dropAction='alias'; alias.removeDropProperties=new List(['dropAction']); useRightSplit(alias, shiftKey); "; + const childText = "openInLightbox(self, shiftKey); "; runInAction(() => { this._childClickedScript = ScriptField.MakeScript(childText, { this: Doc.name, shiftKey: "boolean" }, { detailView: detailView! }); this._viewDefDivClick = ScriptField.MakeScript("pivotColumnClick(this,payload)", { payload: "any" }); }); } + @observable _scrollFilters: Opt; + @observable _scrollPivotField: Opt; + timeDocFilters = () => this._scrollFilters || ([] as string[]); + get pivotField() { return this._scrollPivotField || StrCast(this.layoutDoc._pivotField); } + @action + scrollFocus = (anchor: Doc, smooth: boolean) => { + if (!LinkDocPreview.LinkInfo) { + if (anchor.pivotField !== undefined) { + this.layoutDoc._prevFilterIndex = 1; + this.layoutDoc._pivotField = StrCast(anchor.pivotField); + this.layoutDoc._docFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docFilters as ObjectField); + this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(Doc.GetProto(anchor).docRangeFilters as ObjectField); + } + } else { + this._scrollPivotField = StrCast(anchor.pivotField); + this._scrollFilters = StrListCast(Doc.GetProto(anchor).docFilters); + } + return 0; + } + layoutEngine = () => this._layoutEngine; toggleVisibility = action(() => this._collapsed = !this._collapsed); @@ -69,24 +115,30 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { }), returnFalse, emptyFunction); } - contentsDown = (e: React.PointerEvent) => { - setupMoveUpEvents(this, e, returnFalse, returnFalse, action(() => { - let prevFilterIndex = NumCast(this.props.Document._prevFilterIndex); - if (prevFilterIndex > 0) { - prevFilterIndex--; - this.props.Document._docFilters = ObjectField.MakeCopy(this.props.Document["_prevDocFilter" + prevFilterIndex] as ObjectField); - this.props.Document._docRangeFilters = ObjectField.MakeCopy(this.props.Document["_prevDocRangeFilters" + prevFilterIndex] as ObjectField); - this.props.Document._prevFilterIndex = prevFilterIndex; - } else { - this.props.Document._docFilters = new List([]); - } - }), false); + goTo = (prevFilterIndex: number) => { + this.layoutDoc._pivotField = this.layoutDoc["_prevPivotFields" + prevFilterIndex]; + this.layoutDoc._docFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocFilter" + prevFilterIndex] as ObjectField); + this.layoutDoc._docRangeFilters = ObjectField.MakeCopy(this.layoutDoc["_prevDocRangeFilters" + prevFilterIndex] as ObjectField); + this.layoutDoc._prevFilterIndex = prevFilterIndex; + } + + @action + contentsDown = (e: React.MouseEvent) => { + let prevFilterIndex = NumCast(this.layoutDoc._prevFilterIndex); + if (prevFilterIndex > 0) { + this.goTo(prevFilterIndex - 1); + } else { + this.layoutDoc._docFilters = new List([]); + } } @computed get contents() { - return
+ return
doc) { } specificMenu = (e: React.MouseEvent) => { const layoutItems: ContextMenuProps[] = []; - const doc = this.props.Document; + const doc = this.layoutDoc; layoutItems.push({ description: "Force Timeline", event: () => { doc._forceRenderEngine = "timeline"; }, icon: "compress-arrows-alt" }); layoutItems.push({ description: "Force Pivot", event: () => { doc._forceRenderEngine = "pivot"; }, icon: "compress-arrows-alt" }); @@ -123,10 +175,10 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { this.childLayoutPairs.map(pair => this._allFacets.filter(fieldKey => pair.layout[fieldKey] instanceof RichTextField || typeof (pair.layout[fieldKey]) === "number" || - typeof (pair.layout[fieldKey]) === "string").map(fieldKey => keySet.add(fieldKey))); + typeof (pair.layout[fieldKey]) === "string").filter(fieldKey => fieldKey[0] !== "_" && (fieldKey[0] === "#" || fieldKey[0] === toUpper(fieldKey)[0])).map(fieldKey => keySet.add(fieldKey))); Array.from(keySet).map(fieldKey => - docItems.push({ description: ":" + fieldKey, event: () => this.props.Document._pivotField = fieldKey, icon: "compress-arrows-alt" })); - docItems.push({ description: ":(null)", event: () => this.props.Document._pivotField = undefined, icon: "compress-arrows-alt" }); + docItems.push({ description: ":" + fieldKey, event: () => this.layoutDoc._pivotField = fieldKey, icon: "compress-arrows-alt" })); + docItems.push({ description: ":(null)", event: () => this.layoutDoc._pivotField = undefined, icon: "compress-arrows-alt" }); ContextMenu.Instance.addItem({ description: "Pivot Fields ...", subitems: docItems, icon: "eye" }); const pt = this.props.ScreenToLocalTransform().inverse().transformPoint(x, y); ContextMenu.Instance.displayMenu(x, y, ":"); @@ -137,13 +189,13 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { GetValue: () => "", SetValue: (value: any) => { if (value?.length) { - this.props.Document._pivotField = value; + this.layoutDoc._pivotField = value; return true; } return false; }, showMenuOnLoad: true, - contents: ":" + StrCast(this.props.Document._pivotField), + contents: ":" + StrCast(this.layoutDoc._pivotField), toggle: this.toggleVisibility, color: "#f1efeb" // this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; }; @@ -155,12 +207,12 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { render() { let nonNumbers = 0; this.childDocs.map(doc => { - const num = NumCast(doc[StrCast(this.props.Document._pivotField)], Number(StrCast(doc[StrCast(this.props.Document._pivotField)]))); + const num = NumCast(doc[this.pivotField], Number(StrCast(doc[this.pivotField]))); if (Number.isNaN(num)) { nonNumbers++; } }); - const forceLayout = StrCast(this.props.Document._forceRenderEngine); + const forceLayout = StrCast(this.layoutDoc._forceRenderEngine); const doTimeline = forceLayout ? (forceLayout === "timeline") : nonNumbers / this.childDocs.length < 0.1 && this.props.PanelWidth() / this.props.PanelHeight() > 6; if (doTimeline !== (this._layoutEngine === "timeline")) { if (!this._changing) { @@ -173,7 +225,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { } return
+ style={{ width: this.props.PanelWidth(), height: `calc(100% - ${this.layoutDoc._chromeStatus === "enabled" ? 51 : 0}px)` }}> {this.pivotKeyUI} {this.contents} {!this.props.isSelected() || !doTimeline ? (null) : <> @@ -189,10 +241,17 @@ Scripting.addGlobal(function pivotColumnClick(pivotDoc: Doc, bounds: ViewDefBoun let prevFilterIndex = NumCast(pivotDoc._prevFilterIndex); pivotDoc["_prevDocFilter" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docFilters as ObjectField); pivotDoc["_prevDocRangeFilters" + prevFilterIndex] = ObjectField.MakeCopy(pivotDoc._docRangeFilters as ObjectField); + pivotDoc["_prevPivotFields" + prevFilterIndex] = pivotDoc._pivotField; pivotDoc._prevFilterIndex = ++prevFilterIndex; runInAction(() => { pivotDoc._docFilters = new List(); - (bounds.payload as string[]).map(filterVal => - Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check")); + const filterVals = (bounds.payload as string[]); + filterVals.map(filterVal => Doc.setDocFilter(pivotDoc, StrCast(pivotDoc._pivotField), filterVal, "check")); + const pivotView = DocumentManager.Instance.getDocumentView(pivotDoc); + if (pivotDoc && pivotView?.ComponentView instanceof CollectionTimeView && filterVals.length === 1) { + if (pivotView?.ComponentView.childDocs.length && pivotView.ComponentView.childDocs[0][filterVals[0]]) { + pivotDoc._pivotField = filterVals[0]; + } + } }); }); \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 5cf758e9b..272773d87 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -85,7 +85,8 @@ export function computerPassLayout( pivotDoc: Doc, childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], - viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] + viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], + engineProps: any ) { const docMap = new Map(); childPairs.forEach(({ layout, data }, i) => { @@ -106,7 +107,8 @@ export function computerStarburstLayout( pivotDoc: Doc, childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], - viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] + viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], + engineProps: any ) { const docMap = new Map(); const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])]; @@ -133,14 +135,15 @@ export function computePivotLayout( pivotDoc: Doc, childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], - viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] + viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], + engineProps: any ) { const docMap = new Map(); const fieldKey = "data"; const pivotColumnGroups = new Map, PivotColumn>(); let nonNumbers = 0; - const pivotFieldKey = toLabel(pivotDoc._pivotField); + const pivotFieldKey = toLabel(engineProps?.pivotField ?? pivotDoc._pivotField); childPairs.map(pair => { const lval = pivotFieldKey === "#" ? Array.from(Object.keys(Doc.GetProto(pair.layout))).filter(k => k.startsWith("#")).map(k => k.substring(1)) : Cast(pair.layout[pivotFieldKey], listSpec("string"), null); @@ -274,7 +277,8 @@ export function computeTimelineLayout( pivotDoc: Doc, childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], - viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] + viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], + engineProps?: any ) { const fieldKey = "data"; const pivotDateGroups = new Map(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 038fe8272..b2ee6bbd0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -78,6 +78,7 @@ export type collectionFreeformViewProps = { parentActive: (outsideReaction: boolean) => boolean; scaleField?: string; noOverlay?: boolean; // used to suppress docs in the overlay (z) layer (ie, for minimap since overlay doesn't scale) + engineProps?: any; }; @observer @@ -921,7 +922,7 @@ export class CollectionFreeFormView extends CollectionSubView ViewDefResult[])) => ViewDefResult[] + viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[]), + engineProps: any) => ViewDefResult[] ) { - return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); + return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX, this.props.engineProps); } doFreeformLayout(poolData: Map) { diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 779da91e6..da7ed9ac7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -887,12 +887,10 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<(FieldViewProp selection = TextSelection.between(editor.state.doc.resolve(ret.start), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected } editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); - const escAnchorId = anchorId[0] > '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId; + const escAnchorId = anchorId[0] >= '0' && anchorId[0] <= '9' ? `\\3${anchorId[0]} ${anchorId.substr(1)}` : anchorId; addStyleSheetRule(FormattedTextBox._highlightStyleSheet, `${escAnchorId}`, { background: "yellow" }); - setTimeout(() => { - clearStyleSheetRules(FormattedTextBox._highlightStyleSheet); - this.focusSpeed = undefined; - }, this.focusSpeed); + setTimeout(() => this.focusSpeed = undefined, this.focusSpeed); + setTimeout(() => clearStyleSheetRules(FormattedTextBox._highlightStyleSheet), Math.max(this.focusSpeed || 0, 1500)); } } diff --git a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx index 900b702c6..15c669338 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBoxComment.tsx @@ -115,7 +115,7 @@ export class FormattedTextBoxComment { nbef && naft && LinkDocPreview.SetLinkInfo({ docProps: textBox.props, linkSrc: textBox.rootDoc, - location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - nbef)), + location: ((pos) => [pos.left, pos.top + 25])(view.coordsAtPos(state.selection.from - Math.max(0, nbef - 1))), hrefs, showHeader: true }); diff --git a/src/client/views/nodes/formattedText/RichTextRules.ts b/src/client/views/nodes/formattedText/RichTextRules.ts index 9501d8021..e5d924f42 100644 --- a/src/client/views/nodes/formattedText/RichTextRules.ts +++ b/src/client/views/nodes/formattedText/RichTextRules.ts @@ -262,9 +262,10 @@ export class RichTextRules { }), // create a text display of a metadata field on this or another document, or create a hyperlink portal to another document - // [[ : ]] + // [[ : ]] // [[:Doc]] => hyperlink // [[fieldKey]] => show field + // [[fieldKey=value]] => show field and also set its value // [[fieldKey:Doc]] => show field of doc new InputRule( new RegExp(/\[\[([a-zA-Z_\? \-0-9]*)(=[a-zA-Z_@\? /\-0-9]*)?(:[a-zA-Z_@:\.\? \-0-9]+)?\]\]$/), -- cgit v1.2.3-70-g09d2 From 1a31f3ae5d1b90deb9888bb4df863079f4837f53 Mon Sep 17 00:00:00 2001 From: bobzel Date: Tue, 16 Feb 2021 23:58:54 -0500 Subject: added a file system mode for tree View. ctrl-f to create file system. --- src/client/documents/Documents.ts | 3 +- src/client/util/CurrentUserUtils.ts | 2 +- .../views/collections/CollectionTreeView.tsx | 9 ++++-- src/client/views/collections/TreeView.tsx | 34 ++++++++++++++++------ .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 12 ++++++++ .../formattedText/ProsemirrorExampleTransfer.ts | 2 +- src/fields/Doc.ts | 2 +- src/fields/documentSchemas.ts | 2 +- 9 files changed, 50 insertions(+), 18 deletions(-) (limited to 'src/client/views/nodes/formattedText') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 7502aa685..2d8601dfe 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -144,6 +144,7 @@ export interface DocumentOptions { _layers?: List; _raiseWhenDragged?: boolean; // whether a document is brought to front when dragged. isLinkButton?: boolean; + isFolder?: boolean; _columnWidth?: number; _fontSize?: string; _fontWeight?: number; @@ -211,7 +212,7 @@ export interface DocumentOptions { treeViewExpandedView?: string; // which field/thing is displayed when this item is opened in tree view treeViewChecked?: ScriptField; // script to call when a tree view checkbox is checked treeViewTruncateTitleWidth?: number; - treeViewOutlineMode?: boolean; // whether slide should function as a text outline + treeViewType?: string; // whether treeview is a Slide, file system, or (default) collection hierarchy treeViewLockExpandedView?: boolean; // whether the expanded view can be changed treeViewDefaultExpandedView?: string; // default expanded view sidebarColor?: string; // background color of text sidebar diff --git a/src/client/util/CurrentUserUtils.ts b/src/client/util/CurrentUserUtils.ts index 053b16d76..509f08e9d 100644 --- a/src/client/util/CurrentUserUtils.ts +++ b/src/client/util/CurrentUserUtils.ts @@ -378,7 +378,7 @@ export class CurrentUserUtils { ((doc.emptyPane as Doc).proto as Doc)["dragFactory-count"] = 0; } if (doc.emptySlide === undefined) { - const textDoc = Docs.Create.TreeDocument([], { title: "Slide", _viewType: CollectionViewType.Tree, _fontSize: "20px", treeViewOutlineMode: true, _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, _backgroundColor: "transparent", system: true, cloneFieldFilter: new List(["system"]) }); + const textDoc = Docs.Create.TreeDocument([], { title: "Slide", _viewType: CollectionViewType.Tree, _fontSize: "20px", treeViewType: "outline", _xMargin: 0, _yMargin: 0, _width: 300, _height: 200, _singleLine: true, _backgroundColor: "transparent", system: true, cloneFieldFilter: new List(["system"]) }); Doc.GetProto(textDoc).title = ComputedField.MakeFunction('self.text?.Text'); FormattedTextBox.SelectOnLoad = textDoc[Id]; doc.emptySlide = textDoc; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index bbe6cfdcb..092606fdf 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -140,8 +140,11 @@ export class CollectionTreeView extends CollectionSubView StrCast(this.dataDoc.title)} SetValue={undoBatch((value: string, shift: boolean, enter: boolean) => { - if (this.props.Document.treeViewOutlineMode && enter) { - this.makeTextCollection(childDocs); + if (enter) { + switch (this.props.Document.treeViewType) { + case "outline": this.makeTextCollection(childDocs); break; + case "fileSystem": break; + } } return Doc.SetInPlace(this.dataDoc, "title", value, false); })} />; @@ -209,7 +212,7 @@ export class CollectionTreeView extends CollectionSubView { if (this.props.treeViewPreventOpen) this._overrideTreeViewOpen = c; else this.doc.treeViewOpen = this._overrideTreeViewOpen = c; } - @computed get outlineMode() { return this.props.treeView.doc.treeViewOutlineMode; } + @computed get outlineMode() { return this.props.treeView.doc.treeViewType === "outline"; } + @computed get fileSysMode() { return this.props.treeView.doc.treeViewType === "fileSystem"; } @computed get treeViewOpen() { return (!this.props.treeViewPreventOpen && !this.doc.treeViewPreventOpen && BoolCast(this.doc.treeViewOpen)) || this._overrideTreeViewOpen; } - @computed get treeViewExpandedView() { return StrCast(this.doc.treeViewExpandedView, this.treeViewDefaultExpandedView); } + @computed get treeViewExpandedView() { return this.fileSysMode ? this.fieldKey : StrCast(this.doc.treeViewExpandedView, this.treeViewDefaultExpandedView); } @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.containingCollection.maxEmbedHeight, 200); } @computed get dataDoc() { return this.doc[DataSym]; } @computed get layoutDoc() { return Doc.Layout(this.doc); } @computed get fieldKey() { TraceMobx(); const splits = StrCast(Doc.LayoutField(this.doc)).split("fieldKey={\'"); return splits.length > 1 ? splits[1].split("\'")[0] : "data"; } childDocList(field: string) { + if (this.fileSysMode && !this.doc.isFolder) return [] as Doc[]; const layout = Doc.LayoutField(this.doc) instanceof Doc ? Doc.LayoutField(this.doc) as Doc : undefined; return ((this.props.dataDoc ? DocListCastOrNull(this.props.dataDoc[field]) : undefined) || // if there's a data doc for an expanded template, use it's data field (layout ? DocListCastOrNull(layout[field]) : undefined) || // else if there's a layout doc, display it's fields @@ -138,8 +140,8 @@ export class TreeView extends React.Component { const titleScript = ScriptField.MakeScript(`{setInPlace(self, 'editTitle', '${this._uniqueId}'); documentView.select();} `, { documentView: "any" }); const openScript = ScriptField.MakeScript(`openOnRight(self)`); const treeOpenScript = ScriptField.MakeScript(`self.treeViewOpen = !self.treeViewOpen`); - this._editTitleScript = !Doc.IsSystem(this.props.document) ? titleScript && (() => titleScript) : treeOpenScript && (() => treeOpenScript); - this._openScript = !Doc.IsSystem(this.props.document) ? openScript && (() => openScript) : undefined; + this._editTitleScript = !Doc.IsSystem(this.props.document) || props.document.isFolder ? titleScript && (() => titleScript) : treeOpenScript && (() => treeOpenScript); + this._openScript = !Doc.IsSystem(this.props.document) || props.document.isFolder ? openScript && (() => openScript) : undefined; if (Doc.GetT(this.props.document, "editTitle", "string", true) === "*") Doc.SetInPlace(this.props.document, "editTitle", this._uniqueId, false); } @@ -192,7 +194,7 @@ export class TreeView extends React.Component { const bullet = Docs.Create.TextDocument("-text-", { layout: CollectionView.LayoutString("data"), title: "-title-", "sidebarColor": "transparent", "sidebarViewType": CollectionViewType.Freeform, - _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewOutlineMode: true, + _viewType: CollectionViewType.Tree, hideLinkButton: true, _showSidebar: true, treeViewType: "outline", x: 0, y: 0, _xMargin: 0, _yMargin: 0, _autoHeight: true, _singleLine: true, _backgroundColor: "transparent", _width: 1000, _height: 10 }); Doc.GetProto(bullet).title = ComputedField.MakeFunction('self.text?.Text'); @@ -209,6 +211,13 @@ export class TreeView extends React.Component { bullet.context = this.props.treeView.Document; return added; } + makeFolder = () => { + Doc.SetInPlace(this.doc, "editTitle", undefined, false); + const folder = Docs.Create.FreeformDocument([], { title: "-folder-", _stayInCollection: true, isFolder: true, system: true }); + const added = this.props.addDocument(folder); + folder.context = this.props.treeView.Document; + return added; + } editableView = (key: string, style?: string) => ( { fontStyle={style} fontSize={12} GetValue={() => StrCast(this.doc[key])} + OnFillDown={(value) => { + if (this.fileSysMode) { + this.makeFolder(); + } + }} SetValue={undoBatch((value: string, shiftKey: boolean, enterKey: boolean) => { Doc.SetInPlace(this.doc, key, value, false); if (this.outlineMode && enterKey) { this.makeTextCollection(); + } else if (this.fileSysMode && enterKey) { + // add folder } else { Doc.SetInPlace(this.doc, "editTitle", undefined, false); } @@ -232,7 +248,7 @@ export class TreeView extends React.Component { SelectionManager.DeselectAll(); return false; }} - OnEmpty={undoBatch(() => this.props.treeView.doc.treeViewOutlineMode && this.props.removeDoc?.(this.doc))} + OnEmpty={undoBatch(() => this.outlineMode && this.props.removeDoc?.(this.doc))} OnTab={undoBatch((shift?: boolean) => { shift ? this.props.outdentDocument?.() : this.props.indentDocument?.(); setTimeout(() => Doc.SetInPlace(this.doc, "editTitle", `${this.props.treeView._uniqueId}`, false), 0); @@ -249,7 +265,7 @@ export class TreeView extends React.Component { const pt = [de.x, de.y]; const rect = this._header!.current!.getBoundingClientRect(); const before = pt[1] < rect.top + rect.height / 2; - const inside = pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); + const inside = this.fileSysMode && !this.doc.isFolder ? false : pt[0] > Math.min(rect.left + 75, rect.left + rect.width * .75) || (!before && this.treeViewOpen && this.childDocList.length); if (de.complete.linkDragData) { const sourceDoc = de.complete.linkDragData.linkSourceDocument; const destDoc = this.doc; @@ -435,7 +451,7 @@ export class TreeView extends React.Component { checked: this.doc.treeViewChecked === "check" ? "x" : this.doc.treeViewChecked === "x" ? undefined : "check", containingTreeView: this.props.treeView.props.Document, }, console.log); - } else { + } else if (!this.fileSysMode || this.doc.isFolder) { this.treeViewOpen = !this.treeViewOpen; } e.stopPropagation(); @@ -495,7 +511,7 @@ export class TreeView extends React.Component { contextMenuItems = () => Doc.IsSystem(this.doc) ? [] : [{ script: ScriptField.MakeFunction(`openOnRight(self)`)!, label: "Open" }, { script: ScriptField.MakeFunction(`DocFocus(self)`)!, label: "Focus" }]; truncateTitleWidth = () => NumCast(this.props.treeView.props.Document.treeViewTruncateTitleWidth, this.props.panelWidth()); onChildClick = () => this.props.onChildClick?.() ?? (this._editTitleScript?.() || ScriptCast(this.doc.treeChildClick)); - onChildDoubleClick = () => (!this.outlineMode && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); + onChildDoubleClick = () => (!this.props.treeView.doc.treeViewType && this._openScript?.()) || ScriptCast(this.doc.treeChildDoubleClick); refocus = () => this.props.treeView.props.focus(this.props.treeView.props.Document); ignoreEvent = (e: any) => { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index a5d7489bd..3ca2258ee 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -817,7 +817,7 @@ export class CollectionFreeFormView extends CollectionSubView { - if (this.layoutDoc._lockedTransform || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode) return; + if (this.layoutDoc._lockedTransform || CurrentUserUtils.OverlayDocs.includes(this.props.Document) || this.props.Document.treeViewOutlineMode === "outline") return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index f7fb2b83d..21c9711b3 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -132,6 +132,18 @@ export class MarqueeView extends React.Component SelectionManager.SelectDoc(DocumentManager.Instance.getDocumentView(slide)!, false)); + e.stopPropagation(); } else if (e.key === "b" && e.ctrlKey) { // e.preventDefault(); // navigator.clipboard.readText().then(text => { diff --git a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts index 8d9d36580..243cfc6de 100644 --- a/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts +++ b/src/client/views/nodes/formattedText/ProsemirrorExampleTransfer.ts @@ -49,7 +49,7 @@ export function buildKeymap>(schema: S, props: any, mapKey /// bcz; Argh!! replace with an onEnter func that conditionally handles Enter const addTextBox = (below: boolean, force?: boolean) => { - if (props.Document.treeViewOutlineMode) return true; // bcz: Arghh .. need to determine if this is an treeViewOutlineBox in which case Enter's are ignored.. + if (props.Document.treeViewType === "outline") return true; // bcz: Arghh .. need to determine if this is an treeViewOutlineBox in which case Enter's are ignored.. const layoutDoc = props.Document; const originalDoc = layoutDoc.rootDocument || layoutDoc; if (force || props.Document._singleLine) { diff --git a/src/fields/Doc.ts b/src/fields/Doc.ts index 56b2db48e..da95c3605 100644 --- a/src/fields/Doc.ts +++ b/src/fields/Doc.ts @@ -1147,7 +1147,7 @@ export namespace Doc { case DocumentType.IMG: return "image"; case DocumentType.COMPARISON: return "columns"; case DocumentType.RTF: return "sticky-note"; - case DocumentType.COL: return "folder"; + case DocumentType.COL: return !doc.isFolder ? "folder" : "chevron-right"; case DocumentType.WEB: return "globe-asia"; case DocumentType.SCREENSHOT: return "photo-video"; case DocumentType.WEBCAM: return "video"; diff --git a/src/fields/documentSchemas.ts b/src/fields/documentSchemas.ts index df3dd1eb4..6973079b0 100644 --- a/src/fields/documentSchemas.ts +++ b/src/fields/documentSchemas.ts @@ -81,7 +81,7 @@ export const documentSchema = createSchema({ treeViewLockExpandedView: "boolean", // whether the expanded view can be changed treeViewDefaultExpandedView: "string", // name of field whose contents are displayed by default treeViewPreventOpen: "boolean", // ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document) - treeViewOutlineMode: "boolean", // whether tree view is an outline and clicks edit document titles immediately since double-click opening is turned off + treeViewType: "string", // whether tree view is an outline, file syste or (default) hierarchy. For outline, clicks edit document titles immediately since double-click opening is turned off // interaction and linking properties ignoreClick: "boolean", // whether documents ignores input clicks (but does not ignore manipulation and other events) -- cgit v1.2.3-70-g09d2