From 53685a27139886c1df74840cc9f451c046a32de6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 4 Oct 2019 01:50:57 -0400 Subject: got rid of MainOverlayTextBox! fixed some things with embedding docs in text --- .../collections/collectionFreeForm/CollectionFreeFormView.scss | 6 ------ src/client/views/collections/collectionFreeForm/MarqueeView.scss | 7 +++++++ src/client/views/collections/collectionFreeForm/MarqueeView.tsx | 4 ++-- 3 files changed, 9 insertions(+), 8 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index c4311fa52..bb1a12f88 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -46,12 +46,6 @@ border-radius: inherit; box-sizing: border-box; position: absolute; - overflow: hidden; - - .marqueeView { - overflow: hidden; - } - top: 0; left: 0; width: 100%; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 9fc2e44fb..7dc54ea79 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -6,6 +6,13 @@ width:100%; height:100%; } +.marqueeView { + overflow: hidden; +} + +.marqueeView:focus-within { + overflow: visible; +} .marquee { border-style: dashed; box-sizing: border-box; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 82193aefa..bf154d37d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -442,8 +442,8 @@ export class MarqueeView extends React.Component render() { let p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0]; - return
-
+ return
e.currentTarget.scrollLeft = 0} style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}> +
e.currentTarget.scrollLeft = 0} > {this._visible ? this.marqueeDiv : null}
{this.props.children} -- cgit v1.2.3-70-g09d2 From d611773fc805082a935cae49723d516ce66e1a14 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 4 Oct 2019 23:45:01 -0400 Subject: more pdf cleanup. fix to mix-multiply-mode for better highlighters/opacity. small text box fixes. --- src/client/util/DocumentManager.ts | 2 +- src/client/util/RichTextSchema.tsx | 26 ++- src/client/util/SelectionManager.ts | 2 - src/client/views/DocumentButtonBar.tsx | 1 - src/client/views/DocumentDecorations.tsx | 1 - src/client/views/MainView.tsx | 3 +- .../collectionFreeForm/MarqueeView.scss | 2 +- src/client/views/nodes/Annotation.tsx | 117 ------------ src/client/views/nodes/FormattedTextBox.tsx | 7 +- src/client/views/pdf/Annotation.scss | 3 +- src/client/views/pdf/Annotation.tsx | 4 +- src/client/views/pdf/PDFMenu.tsx | 10 +- src/client/views/pdf/PDFViewer.scss | 6 +- src/client/views/pdf/PDFViewer.tsx | 198 +++++++++------------ src/new_fields/Doc.ts | 2 +- .../authentication/models/current_user_utils.ts | 2 +- 16 files changed, 117 insertions(+), 269 deletions(-) delete mode 100644 src/client/views/nodes/Annotation.tsx (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index ffd311665..6fe97edbb 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -139,7 +139,7 @@ export class DocumentManager { const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); finalDocView && (finalDocView.Document.scrollToLinkID = linkId); finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document); - } + }; const docView = DocumentManager.Instance.getFirstDocumentView(targetDoc); const annotatedDoc = await Cast(targetDoc.annotationOn, Doc); if (docView) { // we have a docView already and aren't forced to create a new one ... just focus on the document. TODO move into view if necessary otherwise just highlight? diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 53eaf9ce2..528c0000b 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -654,17 +654,17 @@ export class ImageResizeView { this._img.onclick = function (e: any) { e.stopPropagation(); e.preventDefault(); - if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) - view.dispatch( - view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2)))); - } + if (view.state.selection.node && view.state.selection.node.type !== view.state.schema.nodes.image) { + view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(view.state.selection.from - 2)))); + } + }; this._img.onpointerdown = function (e: any) { if (e.ctrlKey) { e.preventDefault(); e.stopPropagation(); DocServer.GetRefField(node.attrs.docid).then(async linkDoc => (linkDoc instanceof Doc) && - DocumentManager.Instance.FollowLink(linkDoc, (view.state.schema as any).Document, + DocumentManager.Instance.FollowLink(linkDoc, view.state.schema.Document, document => addDocTab(document, undefined, node.attrs.location ? node.attrs.location : "inTab"), false)); } }; @@ -730,7 +730,7 @@ export class DashDocView { this._dashSpan.style.width = node.attrs.width; this._dashSpan.style.height = node.attrs.height; this._dashSpan.style.position = "absolute"; - this._dashSpan.style.display = "inline-block" + this._dashSpan.style.display = "inline-block"; this._handle.style.position = "absolute"; this._handle.style.width = "20px"; this._handle.style.height = "20px"; @@ -771,16 +771,10 @@ export class DashDocView { this._dashSpan.onclick = function (e: any) { FormattedTextBox.firstTarget && FormattedTextBox.firstTarget(); e.stopPropagation(); - } - this._dashSpan.onkeydown = function (e: any) { - e.stopPropagation(); - } - this._dashSpan.onkeypress = function (e: any) { - e.stopPropagation(); - } - this._dashSpan.onkeyup = function (e: any) { - e.stopPropagation(); - } + }; + this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); }; + this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); }; + this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); }; this._handle.onpointerdown = function (e: any) { e.preventDefault(); e.stopPropagation(); diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index a02a270ee..df1b46b33 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -27,7 +27,6 @@ export namespace SelectionManager { } else if (!ctrlPressed && manager.SelectedDocuments.length > 1) { manager.SelectedDocuments.map(dv => dv !== docView && dv.props.whenActiveChanged(false)); manager.SelectedDocuments = [docView]; - FormattedTextBox.InputBoxOverlay = undefined; } } @action @@ -42,7 +41,6 @@ export namespace SelectionManager { DeselectAll(): void { manager.SelectedDocuments.map(dv => dv.props.whenActiveChanged(false)); manager.SelectedDocuments = []; - FormattedTextBox.InputBoxOverlay = undefined; } } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index e57745b86..9e2d41621 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -140,7 +140,6 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], let selDoc = this.props.views[0]; let container = selDoc.props.ContainingCollectionDoc ? selDoc.props.ContainingCollectionDoc.proto : undefined; let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []); - FormattedTextBox.InputBoxOverlay = undefined; this._linkDrag = UndoManager.StartBatch("Drag Link"); DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { handlers: { diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 26ffaf3a6..9acb77ce2 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -466,7 +466,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> break; } - if (!this._resizing) runInAction(() => FormattedTextBox.InputBoxOverlay = undefined); SelectionManager.SelectedDocuments().forEach(element => { if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { let doc = PositionDocument(element.props.Document); diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 545f99a41..3b0457dff 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,6 +1,5 @@ import { IconName, library } from '@fortawesome/fontawesome-svg-core'; -import { faLink, faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv, faChevronRight, faEllipsisV } from '@fortawesome/free-solid-svg-icons'; -import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt } from '@fortawesome/free-solid-svg-icons'; +import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faClone, faCloudUploadAlt, faCommentAlt, faCut, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, faMusic, faObjectGroup, faPause, faPenNib, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faUndoAlt, faTv, faChevronRight, faEllipsisV } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 7dc54ea79..04f6ec2ad 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -11,7 +11,7 @@ } .marqueeView:focus-within { - overflow: visible; + overflow: hidden; } .marquee { border-style: dashed; diff --git a/src/client/views/nodes/Annotation.tsx b/src/client/views/nodes/Annotation.tsx deleted file mode 100644 index 3e4ed6bf1..000000000 --- a/src/client/views/nodes/Annotation.tsx +++ /dev/null @@ -1,117 +0,0 @@ -import "./ImageBox.scss"; -import React = require("react"); -import { observer } from "mobx-react"; -import { observable, action } from 'mobx'; -import 'react-pdf/dist/Page/AnnotationLayer.css'; - -interface IProps { - Span: HTMLSpanElement; - X: number; - Y: number; - Highlights: any[]; - Annotations: any[]; - CurrAnno: any[]; - -} - -/** - * Annotation class is used to take notes on a particular highlight. You can also change highlighted span's color - * Improvements to be made: Removing the annotation when onRemove is called. (Removing this, not just the highlighted span). - * Also need to support multiline highlighting - * - * Written by: Andrew Kim - */ -@observer -export class Annotation extends React.Component { - - /** - * changes color of the span (highlighted section) - */ - onColorChange = (e: React.PointerEvent) => { - if (e.currentTarget.innerHTML === "r") { - this.props.Span.style.backgroundColor = "rgba(255,0,0, 0.3)"; - } else if (e.currentTarget.innerHTML === "b") { - this.props.Span.style.backgroundColor = "rgba(0,255, 255, 0.3)"; - } else if (e.currentTarget.innerHTML === "y") { - this.props.Span.style.backgroundColor = "rgba(255,255,0, 0.3)"; - } else if (e.currentTarget.innerHTML === "g") { - this.props.Span.style.backgroundColor = "rgba(76, 175, 80, 0.3)"; - } - - } - - /** - * removes the highlighted span. Supposed to remove Annotation too, but I don't know how to unmount this - */ - @action - onRemove = (e: any) => { - let index: number = -1; - //finding the highlight in the highlight array - this.props.Highlights.forEach((e) => { - for (const span of e.spans) { - if (span === this.props.Span) { - index = this.props.Highlights.indexOf(e); - this.props.Highlights.splice(index, 1); - } - } - }); - - //removing from CurrAnno and Annotation array - this.props.Annotations.splice(index, 1); - this.props.CurrAnno.pop(); - - //removing span from div - if (this.props.Span.parentElement) { - let nodesArray = this.props.Span.parentElement.childNodes; - nodesArray.forEach((e) => { - if (e === this.props.Span) { - if (this.props.Span.parentElement) { - this.props.Highlights.forEach((item) => { - if (item === e) { - item.remove(); - } - }); - e.remove(); - } - } - }); - } - - - } - - render() { - return ( -
-
- -
- - - - -
- -
-
- -
-
- - ); - } -} \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 05904e1e7..2b6a86aed 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -291,7 +291,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (de.data.urlField && link) { let url: string = de.data.urlField.url.href; let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image; - node = model.create({ src: url, docid: link[Id] }) + node = model.create({ src: url, docid: link[Id] }); } else { node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), @@ -798,7 +798,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._editorView!.focus(); } } - } + }; } onPointerUp = (e: React.PointerEvent): void => { @@ -807,9 +807,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); } } - + static InputBoxOverlay: any = null; @action onFocused = (e: React.FocusEvent): void => { + FormattedTextBox.InputBoxOverlay = this; document.removeEventListener("keypress", this.recordKeyHandler); document.addEventListener("keypress", this.recordKeyHandler); this.tryUpdateHeight(); diff --git a/src/client/views/pdf/Annotation.scss b/src/client/views/pdf/Annotation.scss index 0c6df74f0..cc326eb93 100644 --- a/src/client/views/pdf/Annotation.scss +++ b/src/client/views/pdf/Annotation.scss @@ -2,6 +2,5 @@ pointer-events: all; user-select: none; position: absolute; - background-color: red; - opacity: 0.1; + background-color: rgba(146, 245, 95, 0.467); } \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 134e757d1..4bb166ffe 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -122,9 +122,9 @@ class RegionAnnotation extends React.Component { left: this.props.x, width: this.props.width, height: this.props.height, - transition: "opacity 0.5s", opacity: this._brushed ? 0.5 : undefined, - backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.color) + backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.backgroundColor), + transition: "opacity 0.5s", }} />); } } \ No newline at end of file diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index e62542014..1e3320069 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -4,7 +4,7 @@ import { observable, action, } from "mobx"; import { observer } from "mobx-react"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { emptyFunction, returnFalse } from "../../../Utils"; -import { Doc } from "../../../new_fields/Doc"; +import { Doc, Opt } from "../../../new_fields/Doc"; @observer export default class PDFMenu extends React.Component { @@ -31,7 +31,7 @@ export default class PDFMenu extends React.Component { @observable public Pinned: boolean = false; public StartDrag: (e: PointerEvent, ele: HTMLElement) => void = emptyFunction; - public Highlight: (color: string) => void = emptyFunction; + public Highlight: (color: string) => Opt = (color: string) => undefined; public Delete: () => void = emptyFunction; public Snippet: (marquee: { left: number, top: number, width: number, height: number }) => void = emptyFunction; public AddTag: (key: string, value: string) => boolean = returnFalse; @@ -155,12 +155,8 @@ export default class PDFMenu extends React.Component { @action highlightClicked = (e: React.MouseEvent) => { - if (!this.Pinned) { - this.Highlight("#f4f442"); - } - else { + if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { this.Highlighting = !this.Highlighting; - this.Highlight("#f4f442"); } } diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index a71e4f81e..c77cee792 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -16,6 +16,7 @@ mix-blend-mode: multiply; opacity: 0.9; } + .textLayer ::selection { background: yellow; } // should match the backgroundColor in createAnnotation() .textLayer .highlight { background-color: yellow; } @@ -51,10 +52,9 @@ pointer-events: none; mix-blend-mode: multiply; - .pdfPage-annotationBox { + .pdfViewer-annotationBox { position: absolute; - background-color: red; - opacity: 0.1; + background-color: rgba(245, 230, 95, 0.616); } } .pdfViewer-waiting { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 1b76ddbdc..4516e9904 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -75,8 +75,9 @@ export class PDFViewer extends React.Component { @observable private _showWaiting = true; @observable private _showCover = false; @observable private _zoomed = 1; + @observable private _scrollTop = 0; - public pdfViewer: any; + private _pdfViewer: any; private _retries = 0; // number of times tried to create the PDF viewer private _isChildActive = false; private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); @@ -149,15 +150,16 @@ export class PDFViewer extends React.Component { copy = (e: ClipboardEvent) => { if (this.props.active() && e.clipboardData) { - e.clipboardData.setData("text/plain", this._selectionText); - e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); - e.clipboardData.setData("dash/pdfRegion", this.makeAnnotationDocument("#0390fc")[Id]); + let annoDoc = this.makeAnnotationDocument("#0390fc"); + if (annoDoc) { + e.clipboardData.setData("text/plain", this._selectionText); + e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); + e.clipboardData.setData("dash/pdfRegion", annoDoc[Id]); + } e.preventDefault(); } } - setSelectionText = (text: string) => this._selectionText = text; - @action initialLoad = async () => { if (this._pageSizes.length === 0) { @@ -215,7 +217,7 @@ export class PDFViewer extends React.Component { document.removeEventListener("copy", this.copy); document.addEventListener("copy", this.copy); document.addEventListener("pagesinit", action(() => { - this.pdfViewer.currentScaleValue = this._zoomed = 1; + this._pdfViewer.currentScaleValue = this._zoomed = 1; this.gotoPage(NumCast(this.props.Document.curPage, 1)); })); document.addEventListener("pagerendered", action(() => this._showCover = this._showWaiting = false)); @@ -223,36 +225,35 @@ export class PDFViewer extends React.Component { let pdfFindController = new PDFJSViewer.PDFFindController({ linkService: pdfLinkService, }); - this.pdfViewer = new PDFJSViewer.PDFViewer({ + this._pdfViewer = new PDFJSViewer.PDFViewer({ container: this._mainCont.current, viewer: this._viewer.current, linkService: pdfLinkService, findController: pdfFindController, renderer: "canvas", }); - pdfLinkService.setViewer(this.pdfViewer); + pdfLinkService.setViewer(this._pdfViewer); pdfLinkService.setDocument(this.props.pdf, null); - this.pdfViewer.setDocument(this.props.pdf); + this._pdfViewer.setDocument(this.props.pdf); } @action - makeAnnotationDocument = (color: string): Doc => { + makeAnnotationDocument = (color: string): Opt => { + if (this._savedAnnotations.size() === 0) return undefined; let mainAnnoDoc = Docs.Create.InstanceFromProto(new Doc(), "", {}); let mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); let annoDocs: Doc[] = []; let minY = Number.MAX_VALUE; - if (this._savedAnnotations.size() === 1 && this._savedAnnotations.values()[0].length === 1) { + if ((this._savedAnnotations.values()[0][0] as any).marqueeing) { let anno = this._savedAnnotations.values()[0][0]; - let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: "rgba(255, 0, 0, 0.1)", title: "Annotation on " + StrCast(this.props.Document.title) }); + let annoDoc = Docs.Create.FreeformDocument([], { backgroundColor: color, title: "Annotation on " + StrCast(this.props.Document.title) }); if (anno.style.left) annoDoc.x = parseInt(anno.style.left); if (anno.style.top) annoDoc.y = parseInt(anno.style.top); if (anno.style.height) annoDoc.height = parseInt(anno.style.height); if (anno.style.width) annoDoc.width = parseInt(anno.style.width); annoDoc.group = mainAnnoDoc; - annoDoc.color = color; - annoDoc.type = AnnotationTypes.Region; - annoDocs.push(annoDoc); annoDoc.isButton = true; + annoDocs.push(annoDoc); anno.remove(); mainAnnoDoc = annoDoc; mainAnnoDocProto = Doc.GetProto(mainAnnoDoc); @@ -265,8 +266,7 @@ export class PDFViewer extends React.Component { if (anno.style.height) annoDoc.height = parseInt(anno.style.height); if (anno.style.width) annoDoc.width = parseInt(anno.style.width); annoDoc.group = mainAnnoDoc; - annoDoc.color = color; - annoDoc.type = AnnotationTypes.Region; + annoDoc.backgroundColor = color; annoDocs.push(annoDoc); anno.remove(); (annoDoc.y !== undefined) && (minY = Math.min(NumCast(annoDoc.y), minY)); @@ -307,7 +307,7 @@ export class PDFViewer extends React.Component { @action gotoPage = (p: number) => { - this.pdfViewer && this.pdfViewer.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); + this._pdfViewer && this._pdfViewer.scrollPageIntoView({ pageNumber: Math.min(Math.max(1, p), this._pageSizes.length) }); } @action @@ -317,25 +317,11 @@ export class PDFViewer extends React.Component { Doc.linkFollowHighlight(scrollToAnnotation); } - sendAnnotations = (page: number) => { - return this._savedAnnotations.getValue(page); - } - - receiveAnnotations = (annotations: HTMLDivElement[], page: number) => { - if (page === -1) { - this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); - this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, annotations)); - } - else { - this._savedAnnotations.setValue(page, annotations); - } - } - @observable scrollTop = 0; @action onScroll = (e: React.UIEvent) => { - this.scrollTop = this._mainCont.current!.scrollTop; - this.pdfViewer && (this.props.Document.curPage = this.pdfViewer.currentPageNumber); + this._scrollTop = this._mainCont.current!.scrollTop; + this._pdfViewer && (this.props.Document.curPage = this._pdfViewer.currentPageNumber); } // get the page index that the vertical offset passed in is on @@ -355,6 +341,8 @@ export class PDFViewer extends React.Component { div.style.top = (parseInt(div.style.top)/*+ this.getScrollFromPage(page)*/).toString(); } this._annotationLayer.current.append(div); + div.style.backgroundColor = "yellow"; + div.style.opacity = "0.5"; let savedPage = this._savedAnnotations.getValue(page); if (savedPage) { savedPage.push(div); @@ -371,8 +359,8 @@ export class PDFViewer extends React.Component { if (!searchString) { fwd ? this.nextAnnotation() : this.prevAnnotation(); } - else if (this.pdfViewer._pageViewsReady) { - this.pdfViewer.findController.executeCommand('findagain', { + else if (this._pdfViewer._pageViewsReady) { + this._pdfViewer.findController.executeCommand('findagain', { caseSensitive: false, findPrevious: !fwd, highlightAll: true, @@ -382,7 +370,7 @@ export class PDFViewer extends React.Component { } else if (this._mainCont.current) { let executeFind = () => { - this.pdfViewer.findController.executeCommand('find', { + this._pdfViewer.findController.executeCommand('find', { caseSensitive: false, findPrevious: !fwd, highlightAll: true, @@ -406,30 +394,24 @@ export class PDFViewer extends React.Component { } this._marqueeing = false; if (!e.altKey && e.button === 0 && this.active()) { + // clear out old marquees and initialize menu for new selection PDFMenu.Instance.StartDrag = this.startDrag; PDFMenu.Instance.Highlight = this.highlight; PDFMenu.Instance.Snippet = this.createSnippet; PDFMenu.Instance.Status = "pdf"; PDFMenu.Instance.fadeOut(true); + this._savedAnnotations.values().forEach(v => v.forEach(a => a.remove())); + this._savedAnnotations.keys().forEach(k => this._savedAnnotations.setValue(k, [])); if (e.target && (e.target as any).parentElement.className === "textLayer") { - if (!e.ctrlKey) { - this.receiveAnnotations([], -1); - } + // start selecting text if mouse down on textLayer spans } - else { + else if (this._mainCont.current) { // set marquee x and y positions to the spatially transformed position - if (this._mainCont.current) { - let boundingRect = this._mainCont.current.getBoundingClientRect(); - this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width); - this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop; - } + let boundingRect = this._mainCont.current.getBoundingClientRect(); + this._startX = this._marqueeX = (e.clientX - boundingRect.left) * (this._mainCont.current.offsetWidth / boundingRect.width); + this._startY = this._marqueeY = (e.clientY - boundingRect.top) * (this._mainCont.current.offsetHeight / boundingRect.height) + this._mainCont.current.scrollTop; + this._marqueeHeight = this._marqueeWidth = 0; this._marqueeing = true; - let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox"); - if (marquees && marquees.length) { // make a copy of the marquee - let marquee = marquees[0] as HTMLDivElement; - marquee.style.opacity = "0.2"; - } - this.receiveAnnotations([], -1); } document.removeEventListener("pointermove", this.onSelectMove); document.addEventListener("pointermove", this.onSelectMove); @@ -464,13 +446,13 @@ export class PDFViewer extends React.Component { let clientRects = selRange.getClientRects(); for (let i = 0; i < clientRects.length; i++) { let rect = clientRects.item(i); - if (rect/* && rect.width !== this._mainCont.current.getBoundingClientRect().width && rect.height !== this._mainCont.current.getBoundingClientRect().height / this.props.pdf.numPages*/) { + if (rect) { let scaleY = this._mainCont.current.offsetHeight / boundingRect.height; let scaleX = this._mainCont.current.offsetWidth / boundingRect.width; if (rect.width !== this._mainCont.current.clientWidth && - (i == 0 || !intersectRect(clientRects[i], clientRects[i - 1]))) { + (i === 0 || !intersectRect(clientRects[i], clientRects[i - 1]))) { let annoBox = document.createElement("div"); - annoBox.className = "pdfPage-annotationBox"; + annoBox.className = "pdfViewer-annotationBox"; // transforms the positions from screen onto the pdf div annoBox.style.top = ((rect.top - boundingRect.top) * scaleY + this._mainCont.current.scrollTop).toString(); annoBox.style.left = ((rect.left - boundingRect.left) * scaleX).toString(); @@ -481,8 +463,7 @@ export class PDFViewer extends React.Component { } } } - let text = selRange.cloneContents().textContent; - text && this.setSelectionText(text); + this._selectionText = selRange.cloneContents().textContent || ""; // clear selection if (sel.empty) { // Chrome @@ -494,22 +475,22 @@ export class PDFViewer extends React.Component { @action onSelectEnd = (e: PointerEvent): void => { + this._savedAnnotations.clear(); if (this._marqueeing) { if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { let marquees = this._mainCont.current!.getElementsByClassName("pdfViewer-dragAnnotationBox"); - if (marquees && marquees.length) { // make a copy of the marquee + if (marquees && marquees.length) { // copy the marquee and convert it to a permanent annotation. + let style = (marquees[0] as HTMLDivElement).style; let copy = document.createElement("div"); - let marquee = marquees[0] as HTMLDivElement; - let style = marquee.style; copy.style.left = style.left; copy.style.top = style.top; copy.style.width = style.width; copy.style.height = style.height; copy.style.border = style.border; copy.style.opacity = style.opacity; - copy.className = "pdfPage-annotationBox"; + (copy as any).marqueeing = true; + copy.className = "pdfViewer-annotationBox"; this.createAnnotation(copy, this.getPageFromScroll(this._marqueeY)); - marquee.style.opacity = "0"; } if (!e.ctrlKey) { @@ -518,8 +499,7 @@ export class PDFViewer extends React.Component { } PDFMenu.Instance.jumpTo(e.clientX, e.clientY); } - - this._marqueeHeight = this._marqueeWidth = 0; + this._marqueeing = false; } else { let sel = window.getSelection(); @@ -531,7 +511,7 @@ export class PDFViewer extends React.Component { } if (PDFMenu.Instance.Highlighting) { - this.highlight("goldenrod"); + this.highlight("rgba(245, 230, 95, 0.616)"); // when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up } else { PDFMenu.Instance.StartDrag = this.startDrag; @@ -545,7 +525,7 @@ export class PDFViewer extends React.Component { highlight = (color: string) => { // creates annotation documents for current highlights let annotationDoc = this.makeAnnotationDocument(color); - Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc); + annotationDoc && Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc); return annotationDoc; } @@ -558,16 +538,18 @@ export class PDFViewer extends React.Component { e.preventDefault(); e.stopPropagation(); let targetDoc = Docs.Create.TextDocument({ width: 200, height: 200, title: "Note linked to " + this.props.Document.title }); - let annotationDoc = this.highlight("red"); - let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); - DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { - handlers: { - dragComplete: () => !(dragData as any).linkedToDoc && - DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${StrCast(this.props.Document.title)}`, "link from PDF") - - }, - hideSource: false - }); + const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); + if (annotationDoc) { + let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); + DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { + handlers: { + dragComplete: () => !(dragData as any).linkedToDoc && + DocUtils.MakeLink({ doc: annotationDoc }, { doc: dragData.dropDocument, ctx: dragData.targetContext }, `Annotation from ${StrCast(this.props.Document.title)}`, "link from PDF") + + }, + hideSource: false + }); + } } createSnippet = (marquee: { left: number, top: number, width: number, height: number }): void => { @@ -609,7 +591,7 @@ export class PDFViewer extends React.Component { return true; } scrollXf = () => { - return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this.scrollTop) : this.props.ScreenToLocalTransform(); + return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform(); } setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => { this._setPreviewCursor = func; @@ -647,9 +629,9 @@ export class PDFViewer extends React.Component { onZoomWheel = (e: React.WheelEvent) => { e.stopPropagation(); if (e.ctrlKey) { - let curScale = Number(this.pdfViewer.currentScaleValue); - this.pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale + curScale * e.deltaY / 1000)); - this._zoomed = Number(this.pdfViewer.currentScaleValue); + let curScale = Number(this._pdfViewer.currentScaleValue); + this._pdfViewer.currentScaleValue = Math.max(1, Math.min(10, curScale + curScale * e.deltaY / 1000)); + this._zoomed = Number(this._pdfViewer.currentScaleValue); } } @@ -657,29 +639,7 @@ export class PDFViewer extends React.Component { return
{this.nonDocAnnotations.sort((a, b) => NumCast(a.y) - NumCast(b.y)).map((anno, index) => )} -
; - } - @computed get pdfViewerDiv() { - return
; - } - @computed get standinViews() { - return <> - {this._showCover ? this.getCoverImage() : (null)} - {this._showWaiting ? : (null)} - ; - } - marqueeWidth = () => this._marqueeWidth; - marqueeHeight = () => this._marqueeHeight; - marqueeX = () => this._marqueeX; - marqueeY = () => this._marqueeY; - marqueeing = () => this._marqueeing; - visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96; - render() { - return (
- {this.pdfViewerDiv} - - {this.annotationLayer} -
+
NumCast(this.props.Document.scrollHeight, NumCast(this.props.Document.nativeHeight))} @@ -702,7 +662,29 @@ export class PDFViewer extends React.Component { chromeCollapsed={true}>
+
; + } + @computed get pdfViewerDiv() { + return
; + } + @computed get standinViews() { + return <> + {this._showCover ? this.getCoverImage() : (null)} + {this._showWaiting ? : (null)} + ; + } + marqueeWidth = () => this._marqueeWidth; + marqueeHeight = () => this._marqueeHeight; + marqueeX = () => this._marqueeX; + marqueeY = () => this._marqueeY; + marqueeing = () => this._marqueeing; + visibleHeight = () => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96; + render() { + return (
+ {this.pdfViewerDiv} + {this.annotationLayer} {this.standinViews} +
); } } @@ -722,11 +704,9 @@ class PdfViewerMarquee extends React.Component { style={{ left: `${this.props.x()}px`, top: `${this.props.y()}px`, width: `${this.props.width()}px`, height: `${this.props.height()}px`, - border: `${this.props.width() === 0 ? "" : "2px dashed black"}` + border: `${this.props.width() === 0 ? "" : "2px dashed black"}`, + opacity: 0.2 }}>
; } -} - - -export enum AnnotationTypes { Region } +} \ No newline at end of file diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 6acc6e1ca..7e37eba84 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -685,7 +685,7 @@ export namespace Doc { document.removeEventListener("pointerdown", linkFollowUnhighlight); document.addEventListener("pointerdown", linkFollowUnhighlight); let x = dt = Date.now(); - window.setTimeout(() => dt == x && linkFollowUnhighlight(), 5000); + window.setTimeout(() => dt === x && linkFollowUnhighlight(), 5000); } export class HighlightBrush { diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index b2509a4f1..0fbfbf2f3 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -112,7 +112,7 @@ export class CurrentUserUtils { if (sidebar) { sidebar.backgroundColor = "lightgrey"; } - }) + }); if (doc.overlays === undefined) { const overlays = Docs.Create.FreeformDocument([], { title: "Overlays" }); -- cgit v1.2.3-70-g09d2 From 3ba540a0aecc624f9c81f32102441d6d137ade85 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 5 Oct 2019 10:35:33 -0400 Subject: fixed adding to presentation box when minimized --- src/client/views/OverlayView.tsx | 11 ++++++----- .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 4 ++-- src/client/views/nodes/DocumentView.tsx | 5 +++-- src/client/views/nodes/PresBox.tsx | 9 ++++----- 4 files changed, 15 insertions(+), 14 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/views/OverlayView.tsx b/src/client/views/OverlayView.tsx index bb6dd5076..95c7b2683 100644 --- a/src/client/views/OverlayView.tsx +++ b/src/client/views/OverlayView.tsx @@ -145,6 +145,7 @@ export class OverlayView extends React.Component { return (null); } return CurrentUserUtils.UserDocument.overlays instanceof Doc && DocListCast(CurrentUserUtils.UserDocument.overlays.data).map(d => { + d.inOverlay = true; let offsetx = 0, offsety = 0; let onPointerMove = action((e: PointerEvent) => { if (e.buttons === 1) { @@ -170,14 +171,14 @@ export class OverlayView extends React.Component { document.addEventListener("pointerup", onPointerUp); }; return
- { - if (this.props.Document.lockedPosition || this.isAnnotationOverlay) return; + if (this.props.Document.lockedPosition || this.props.Document.inOverlay || this.isAnnotationOverlay) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -360,7 +360,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action setPan(panX: number, panY: number) { - if (!this.props.Document.lockedPosition) { + if (!this.props.Document.lockedPosition || this.props.Document.inOverlay) { this.props.Document.panTransformType = "None"; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 54fafc20b..e50008fdf 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -111,6 +111,7 @@ export const documentSchema = createSchema({ type: "string", // enumerated type of document maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) lockedPosition: "boolean", // whether the document can be spatially manipulated + inOverlay: "boolean", // whether the document is rendered in an OverlayView which handles selection/dragging differently borderRounding: "string", // border radius rounding of document searchFields: "string", // the search fields to display when this document matches a search in its metadata heading: "number", // the logical layout 'heading' of this document (used by rule provider to stylize h1 header elements, from h2, etc) @@ -243,7 +244,7 @@ export class DocumentView extends DocComponent(Docu this._hitTemplateDrag = true; } } - if (this.active && e.button === 0 && !this.Document.lockedPosition) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag); + if (this.active && e.button === 0 && !this.Document.lockedPosition && !this.Document.inOverlay) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag); document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -253,7 +254,7 @@ export class DocumentView extends DocComponent(Docu if (e.cancelBubble && this.active) { document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } - else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive()) && !this.Document.lockedPosition) { + else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive()) && !this.Document.lockedPosition && !this.Document.inOverlay) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { if (!e.altKey && !this.topMost && e.buttons === 1) { document.removeEventListener("pointermove", this.onPointerMove); diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index 180ed9032..15fafb022 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -314,12 +314,11 @@ export class PresBox extends React.Component { } toggleMinimize = undoBatch(action((e: React.PointerEvent) => { - if (this.props.Document.minimizedView) { - this.props.Document.minimizedView = false; + if (this.props.Document.inOverlay) { Doc.RemoveDocFromList((CurrentUserUtils.UserDocument.overlays as Doc), this.props.fieldKey, this.props.Document); CollectionDockingView.AddRightSplit(this.props.Document, this.props.DataDoc); + this.props.Document.inOverlay = false; } else { - this.props.Document.minimizedView = true; this.props.Document.x = e.clientX + 25; this.props.Document.y = e.clientY - 25; this.props.addDocTab && this.props.addDocTab(this.props.Document, this.props.DataDoc, "close"); @@ -370,9 +369,9 @@ export class PresBox extends React.Component { - +
- {this.props.Document.minimizedView ? (null) : + {this.props.Document.inOverlay ? (null) :
-- cgit v1.2.3-70-g09d2 From f9916faa215297d434aab2357b98d2c4b1fdcb92 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 5 Oct 2019 13:02:43 -0400 Subject: started cleaning up videos. restored link follwing to timecodes. changed to currentTimecode from curPage --- src/client/documents/Documents.ts | 11 +++-- src/client/util/DictationManager.ts | 1 - src/client/util/DocumentManager.ts | 7 ++- src/client/views/InkingCanvas.tsx | 6 +-- .../views/collections/CollectionBaseView.tsx | 4 +- .../views/collections/CollectionSchemaView.tsx | 2 +- .../views/collections/CollectionVideoView.tsx | 6 +-- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/linking/LinkFollowBox.tsx | 2 +- src/client/views/nodes/PDFBox.tsx | 6 +-- src/client/views/nodes/VideoBox.tsx | 57 ++++++++++++---------- src/new_fields/InkField.ts | 2 +- 13 files changed, 58 insertions(+), 50 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 71b9038d4..98f56af01 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -74,6 +74,7 @@ export interface DocumentOptions { backgroundLayout?: string; chromeStatus?: string; curPage?: number; + currentTimecode?: number; documentText?: string; borderRounding?: string; schemaColumns?: List; @@ -120,7 +121,7 @@ export namespace Docs { }], [DocumentType.IMG, { layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, - options: { curPage: 0 } + options: {} }], [DocumentType.WEB, { layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, @@ -136,7 +137,7 @@ export namespace Docs { }], [DocumentType.VID, { layout: { view: VideoBox, collectionView: [CollectionVideoView, data, anno] as CollectionViewType }, - options: { curPage: 0 }, + options: { currentTimecode: 0 }, }], [DocumentType.AUDIO, { layout: { view: AudioBox }, @@ -662,15 +663,17 @@ export namespace DocUtils { UndoManager.RunInBatch(() => { linkDocProto.type = DocumentType.LINK; - linkDocProto.targetContext = target.ctx; - linkDocProto.sourceContext = source.ctx; linkDocProto.title = title === "" ? source.doc.title + " to " + target.doc.title : title; linkDocProto.linkDescription = description; linkDocProto.anchor1 = source.doc; + linkDocProto.anchor1Context = source.ctx; + linkDocProto.anchor1Timecode = source.doc.currentTimecode; linkDocProto.anchor1Groups = new List([]); linkDocProto.anchor2 = target.doc; + linkDocProto.anchor2Context = target.ctx; linkDocProto.anchor2Groups = new List([]); + linkDocProto.anchor2Timecode = target.doc.currentTimecode; LinkManager.Instance.addLink(linkDocProto); diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index cebb56bbe..3b2307073 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -336,7 +336,6 @@ export namespace DictationManager { let newBox = Docs.Create.TextDocument({ width: 400, height: 200, title: "My Outline" }); newBox.autoHeight = true; let proto = newBox.proto!; - proto.page = -1; let prompt = "Press alt + r to start dictating here..."; let head = 3; let anchor = head + prompt.length; diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 6fe97edbb..83a69465f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,4 +1,4 @@ -import { action, computed, observable } from 'mobx'; +import { action, computed, observable, trace } from 'mobx'; import { Doc, DocListCastAsync } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; import { Cast, NumCast, StrCast } from '../../new_fields/Types'; @@ -196,10 +196,13 @@ export class DocumentManager { const second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs; const linkDoc = first.length ? first[0] : second.length ? second[0] : undefined; const linkFollowDocs = first.length ? [await first[0].anchor2 as Doc, await first[0].anchor1 as Doc] : second.length ? [await second[0].anchor1 as Doc, await second[0].anchor2 as Doc] : undefined; - const linkFollowDocContexts = first.length ? [await first[0].targetContext as Doc, await first[0].sourceContext as Doc] : second.length ? [await second[0].sourceContext as Doc, await second[0].targetContext as Doc] : [undefined, undefined]; + const linkFollowDocContexts = first.length ? [await first[0].anchor2Context as Doc, await first[0].anchor1Context as Doc] : second.length ? [await second[0].anchor1Context as Doc, await second[0].anchor2Context as Doc] : [undefined, undefined]; + const linkFollowTimecodes = first.length ? [NumCast(first[0].anchor2Timecode), NumCast(first[0].anchor1Timecode)] : second.length ? [NumCast(second[0].anchor1Timecode), NumCast(second[0].anchor2Timecode)] : [undefined, undefined]; if (linkFollowDocs && linkDoc) { const maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab"); const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; + const target = linkFollowDocs[reverse ? 1 : 0]; + target.currentTimecode !== undefined && (target.currentTimecode = linkFollowTimecodes[reverse ? 1 : 0]); DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]); } } diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 1cfa8d644..9ab320eab 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -87,7 +87,7 @@ export class InkingCanvas extends React.Component { color: InkingControl.Instance.selectedColor, width: InkingControl.Instance.selectedWidth, tool: InkingControl.Instance.selectedTool, - page: NumCast(this.props.Document.curPage, -1) + displayTimecode: NumCast(this.props.Document.currentTimecode, -1) }); this.inkData = data; } @@ -150,9 +150,9 @@ export class InkingCanvas extends React.Component { @computed get drawnPaths() { - let curPage = NumCast(this.props.Document.curPage, -1); + let curTimecode = NumCast(this.props.Document.currentTimecode, -1); let paths = Array.from(this.inkData).reduce((paths, [id, strokeData]) => { - if (strokeData.page === -1 || (Math.abs(Math.round(strokeData.page) - Math.round(curPage)) < 3)) { + if (strokeData.displayTimecode === -1 || (Math.abs(Math.round(strokeData.displayTimecode) - Math.round(curTimecode)) < 3)) { paths.push( { @action.bound addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { - var curPage = NumCast(this.props.Document.curPage, -1); - Doc.GetProto(doc).page = curPage; + var curTime = NumCast(this.props.Document.currentTimecode, -1); + curTime !== -1 && (doc.displayTimecode = curTime); if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 52a41fc84..1ba35a52b 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -51,7 +51,7 @@ const columnTypes: Map = new Map([ ["title", ColumnType.String], ["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number], ["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean], - ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number] + ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["currentTimecode", ColumnType.Number], ["zIndex", ColumnType.Number] ]); @observer diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx index 5185d9d0e..3d898b7de 100644 --- a/src/client/views/collections/CollectionVideoView.tsx +++ b/src/client/views/collections/CollectionVideoView.tsx @@ -21,7 +21,7 @@ export class CollectionVideoView extends React.Component { } private get uIButtons() { let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); - let curTime = NumCast(this.props.Document.curPage); + let curTime = NumCast(this.props.Document.currentTimecode); return ([
{"" + Math.round(curTime)} {" " + Math.round((curTime - Math.trunc(curTime)) * 100)} @@ -85,7 +85,7 @@ export class CollectionVideoView extends React.Component { onPointerMove = (e: PointerEvent) => { this._isclick += Math.abs(e.movementX) + Math.abs(e.movementY); if (this._videoBox) { - this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.curPage, 0) + Math.sign(e.movementX) * 0.0333)); + this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333)); } e.stopImmediatePropagation(); } @@ -94,7 +94,7 @@ export class CollectionVideoView extends React.Component { document.removeEventListener("pointermove", this.onPointerMove, true); document.removeEventListener("pointerup", this.onPointerUp, true); InkingControl.Instance.switchTool(InkTool.None); - this._isclick < 10 && (this.props.Document.curPage = 0); + this._isclick < 10 && (this.props.Document.currentTimecode = 0); } setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 7b29f2c2a..bfd3e6481 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -105,7 +105,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { SelectionManager.DeselectAll(); docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).map(dv => dv && SelectionManager.SelectDoc(dv, true)); } - public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.page, -1) - NumCast(this.Document.curPage, -1)) < 1.5 || NumCast(doc.page, -1) === -1); } + public isCurrent(doc: Doc) { return !doc.isMinimized && (Math.abs(NumCast(doc.displayTimecode, -1) - NumCast(this.Document.currentTimecode, -1)) < 1.5 || NumCast(doc.displayTimecode, -1) === -1); } public getActiveDocuments = () => { return this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index bf154d37d..eaf65b88c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -285,7 +285,7 @@ export class MarqueeView extends React.Component this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; - d.page = -1; + d.displayTimecode = undefined; return d; }); } diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 2bff3ded4..b18aa5d63 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -128,7 +128,7 @@ export class LinkFollowBox extends React.Component { runInAction(async () => { this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: dest })); this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target })); - let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.targetContext, Doc)) as Doc; + let tcontext = LinkFollowBox.linkDoc && (await Cast(LinkFollowBox.linkDoc.anchor2Context, Doc)) as Doc; runInAction(() => tcontext && this._docs.splice(0, 0, { col: tcontext, target: dest })); }); } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 88cd2cdc4..1f3887608 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -65,9 +65,9 @@ export class PDFBox extends DocComponent(PdfDocumen public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); } public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); } public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); } - public backPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) - 1); } + public backPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) - 1); } public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); }; - public forwardPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) + 1); } + public forwardPage() { this._pdfViewer!.gotoPage((this.Document.curPage || 1) + 1); } @undoBatch @action @@ -128,7 +128,7 @@ export class PDFBox extends DocComponent(PdfDocumen
e.stopPropagation()}>
- this.gotoPage(Number(e.currentTarget.value))} style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }} onClick={action(() => this._pageControls = !this._pageControls)} /> diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index 573197117..e83aa8bea 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -3,7 +3,7 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction, import { observer } from "mobx-react"; import * as rp from 'request-promise'; import { InkTool } from "../../../new_fields/InkField"; -import { makeInterface } from "../../../new_fields/Schema"; +import { makeInterface, createSchema } from "../../../new_fields/Schema"; import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; @@ -16,16 +16,19 @@ import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; import { documentSchema } from "./DocumentView"; import { FieldView, FieldViewProps } from './FieldView'; -import { pageSchema } from "./ImageBox"; import "./VideoBox.scss"; import { library } from "@fortawesome/fontawesome-svg-core"; import { faVideo } from "@fortawesome/free-solid-svg-icons"; import { Doc } from "../../../new_fields/Doc"; import { ScriptField } from "../../../new_fields/ScriptField"; +import { positionSchema } from "./CollectionFreeFormDocumentView"; var path = require('path'); -type VideoDocument = makeInterface<[typeof documentSchema, typeof pageSchema]>; -const VideoDocument = makeInterface(documentSchema, pageSchema); +export const timeSchema = createSchema({ + currentTimecode: "number", +}); +type VideoDocument = makeInterface<[typeof documentSchema, typeof positionSchema, typeof timeSchema]>; +const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); library.add(faVideo); @@ -97,11 +100,11 @@ export class VideoBox extends DocComponent(VideoD } @action public Snapshot() { - let width = NumCast(this.props.Document.width); - let height = NumCast(this.props.Document.height); + let width = this.Document.width || 0; + let height = this.Document.height || 0; var canvas = document.createElement('canvas'); canvas.width = 640; - canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth); + canvas.height = 640 * (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions if (ctx) { ctx.rect(0, 0, canvas.width, canvas.height); @@ -112,24 +115,25 @@ export class VideoBox extends DocComponent(VideoD if (!this._videoRef) { // can't find a way to take snapshots of videos let b = Docs.Create.ButtonDocument({ - x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), - width: 150, height: 50, title: NumCast(this.props.Document.curPage).toString() + x: (this.Document.x || 0) + width, y: (this.Document.y || 0), + width: 150, height: 50, title: (this.Document.currentTimecode || 0).toString() }); - b.onClick = ScriptField.MakeScript(`this.curPage = ${NumCast(this.props.Document.curPage)}`); + b.onClick = ScriptField.MakeScript(`this.currentTimecode = ${(this.Document.currentTimecode || 0)}`); } else { //convert to desired file format var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' // if you want to preview the captured image, - let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, ""); - VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => { + let filename = path.basename(encodeURIComponent("snapshot" + this.Document.title + "_" + (this.Document.currentTimecode || 0).toString())); + VideoBox.convertDataUri(dataUrl, filename.replace(/\..*$/, "")).then(returnedFilename => { if (returnedFilename) { let url = this.choosePath(Utils.prepend(returnedFilename)); let imageSummary = Docs.Create.ImageDocument(url, { - x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y), - width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-" + x: (this.Document.x || 0) + width, y: (this.Document.y || 0), + width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-" }); + imageSummary.isButton = true; this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false); - DocUtils.MakeLink({doc:imageSummary}, {doc: this.props.Document}, "snapshot from " + this.props.Document.title, "video frame snapshot"); + DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot"); } }); } @@ -137,8 +141,8 @@ export class VideoBox extends DocComponent(VideoD @action updateTimecode = () => { - this.player && (this.props.Document.curPage = this.player.currentTime); - this._youtubePlayer && (this.props.Document.curPage = this._youtubePlayer.getCurrentTime()); + this.player && (this.Document.currentTimecode = this.player.currentTime); + this._youtubePlayer && (this.Document.currentTimecode = this._youtubePlayer.getCurrentTime()); } componentDidMount() { @@ -146,12 +150,12 @@ export class VideoBox extends DocComponent(VideoD if (this.youtubeVideoId) { let youtubeaspect = 400 / 315; - var nativeWidth = FieldValue(this.Document.nativeWidth, 0); - var nativeHeight = FieldValue(this.Document.nativeHeight, 0); + var nativeWidth = (this.Document.nativeWidth || 0); + var nativeHeight = (this.Document.nativeHeight || 0); if (!nativeWidth || !nativeHeight) { if (!this.Document.nativeWidth) this.Document.nativeWidth = 600; this.Document.nativeHeight = this.Document.nativeWidth / youtubeaspect; - this.Document.height = FieldValue(this.Document.width, 0) / youtubeaspect; + this.Document.height = (this.Document.width || 0) / youtubeaspect; } } } @@ -168,10 +172,9 @@ export class VideoBox extends DocComponent(VideoD if (vref) { this._videoRef!.ontimeupdate = this.updateTimecode; vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen); - if (this._reactionDisposer) this._reactionDisposer(); - this._reactionDisposer = reaction(() => this.props.Document.curPage, () => - !this.Playing && (vref.currentTime = this.Document.curPage || 0) - , { fireImmediately: true }); + this._reactionDisposer && this._reactionDisposer(); + this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0, + time => !this.Playing && (vref.currentTime = time), { fireImmediately: true }); } } @@ -242,7 +245,7 @@ export class VideoBox extends DocComponent(VideoD let onYoutubePlayerReady = (event: any) => { this._reactionDisposer && this._reactionDisposer(); this._youtubeReactionDisposer && this._youtubeReactionDisposer(); - this._reactionDisposer = reaction(() => this.props.Document.curPage, () => !this.Playing && this.Seek(this.Document.curPage || 0)); + this._reactionDisposer = reaction(() => this.Document.currentTimecode, () => !this.Playing && this.Seek(this.Document.currentTimecode || 0)); this._youtubeReactionDisposer = reaction(() => [this.props.isSelected(), DocumentDecorations.Instance.Interacting, InkingControl.Instance.selectedTool], () => { let interactive = InkingControl.Instance.selectedTool === InkTool.None && this.props.isSelected() && !DocumentDecorations.Instance.Interacting; iframe.style.pointerEvents = interactive ? "all" : "none"; @@ -263,9 +266,9 @@ export class VideoBox extends DocComponent(VideoD this._youtubeIframeId = VideoBox._youtubeIframeCounter++; this._youtubeContentCreated = this._forceCreateYouTubeIFrame ? true : true; let style = "videoBox-content-YouTube" + (this._fullScreen ? "-fullScreen" : ""); - let start = untracked(() => Math.round(NumCast(this.props.Document.curPage))); + let start = untracked(() => Math.round(this.Document.currentTimecode || 0)); return ; } diff --git a/src/new_fields/InkField.ts b/src/new_fields/InkField.ts index 8f64c1c2e..e381d0218 100644 --- a/src/new_fields/InkField.ts +++ b/src/new_fields/InkField.ts @@ -16,7 +16,7 @@ export interface StrokeData { color: string; width: string; tool: InkTool; - page: number; + displayTimecode: number; } export type InkData = Map; -- cgit v1.2.3-70-g09d2 From c4b4b8f8961bfd6deddd9da7e6a02990ad526c2f Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 7 Oct 2019 01:27:52 -0400 Subject: small fixes to link clicking in text box. --- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/client/views/nodes/FormattedTextBox.tsx | 28 +++++++--------------- 2 files changed, 9 insertions(+), 20 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index bfd3e6481..38488f033 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -267,6 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerDown = (e: React.PointerEvent): void => { + if (e.nativeEvent.cancelBubble) return; this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false; if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) { document.removeEventListener("pointermove", this.onPointerMove); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index bdb7c2941..c87185a3e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -18,7 +18,7 @@ import { RichTextField } from "../../../new_fields/RichTextField"; import { RichTextUtils } from '../../../new_fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../new_fields/Schema"; import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; -import { numberRange, timenow, Utils } from '../../../Utils'; +import { numberRange, timenow, Utils, emptyFunction } from '../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; @@ -76,7 +76,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _proseRef?: HTMLDivElement; private _editorView: Opt; private _applyingChange: boolean = false; - private _linkClicked = ""; private _nodeClicked: any; private _undoTyping?: UndoManager.Batch; private _searchReactionDisposer?: Lambda; @@ -117,7 +116,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } public static getToolTip(ev: EditorView) { - return this._toolTipTextMenu ? this._toolTipTextMenu : this._toolTipTextMenu = new TooltipTextMenu(ev, undefined); + return this._toolTipTextMenu ? this._toolTipTextMenu : this._toolTipTextMenu = new TooltipTextMenu(ev); } @undoBatch @@ -750,7 +749,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._searchReactionDisposer && this._searchReactionDisposer(); this._editorView && this._editorView.destroy(); } - public static firstTarget: () => void; + public static firstTarget: () => void = emptyFunction; onPointerDown = (e: React.PointerEvent): void => { if ((e.nativeEvent as any).formattedHandled) return; (e.nativeEvent as any).formattedHandled = true; @@ -800,16 +799,12 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onClick = (e: React.MouseEvent): void => { - let ctrlKey = e.ctrlKey; if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { let href = (e.target as any).href; let location: string; if ((e.target as any).attributes.location) { location = (e.target as any).attributes.location.value; } - for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) { - href = parent.childNodes[0].href ? parent.childNodes[0].href : parent.href; - } let pcords = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); if (node) { @@ -821,18 +816,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { - this._linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; - if (this._linkClicked) { - DocServer.GetRefField(this._linkClicked).then(async linkDoc => + let linkClicked = href.replace(Utils.prepend("/doc/"), "").split("?")[0]; + if (linkClicked) { + DocServer.GetRefField(linkClicked).then(async linkDoc => { (linkDoc instanceof Doc) && - DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false)); - e.stopPropagation(); - e.preventDefault(); + DocumentManager.Instance.FollowLink(linkDoc, this.props.Document, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), false); + }); } } else { let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.props.Document.x, 0) + NumCast(this.props.Document.width, 0), y: NumCast(this.props.Document.y) }); this.props.addDocument && this.props.addDocument(webDoc); - this._linkClicked = webDoc[Id]; } e.stopPropagation(); e.preventDefault(); @@ -862,11 +855,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } this._editorView!.focus(); - if (this._linkClicked) { - this._linkClicked = ""; - e.preventDefault(); - e.stopPropagation(); - } } onMouseDown = (e: React.MouseEvent): void => { if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself -- cgit v1.2.3-70-g09d2 From c7746aea51bd6891ac10f152bd8f09e5d8ddcfd9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 7 Oct 2019 08:16:34 -0400 Subject: initial tests --- src/client/util/RichTextSchema.tsx | 7 +++++++ .../collections/collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 3 +++ src/client/views/nodes/FormattedTextBox.tsx | 4 +++- 4 files changed, 14 insertions(+), 2 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 1109fd292..f5749a01b 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -770,8 +770,15 @@ export class DashDocView { let self = this; this._dashSpan.onclick = function (e: any) { FormattedTextBox.firstTarget && FormattedTextBox.firstTarget(); + }; + this._dashSpan.onpointermove = function (e: any) { + (e as any).formattedHandled = true; + }; + this._dashSpan.onpointerup = function (e: any) { e.stopPropagation(); }; + this._dashSpan.onpointerdown = function (e: any) { + }; this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); }; this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); }; this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); }; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 38488f033..cf76945e0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -267,7 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerDown = (e: React.PointerEvent): void => { - if (e.nativeEvent.cancelBubble) return; + if (e.nativeEvent.cancelBubble || (e.nativeEvent as any).formattedHandled) return; this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false; if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) { document.removeEventListener("pointermove", this.onPointerMove); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e50008fdf..5a9202d40 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -202,6 +202,7 @@ export class DocumentView extends DocComponent(Docu } preventDefault && e.preventDefault(); } + if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); } } buttonClick = async (altKey: boolean, ctrlKey: boolean) => { @@ -249,8 +250,10 @@ export class DocumentView extends DocComponent(Docu document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); document.addEventListener("pointerup", this.onPointerUp); + if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); } } onPointerMove = (e: PointerEvent): void => { + if ((e as any).formattedHandled) { e.stopPropagation(); return; } if (e.cancelBubble && this.active) { document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c87185a3e..9396e3ea1 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -751,7 +751,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } public static firstTarget: () => void = emptyFunction; onPointerDown = (e: React.PointerEvent): void => { - if ((e.nativeEvent as any).formattedHandled) return; + if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } (e.nativeEvent as any).formattedHandled = true; let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos)); @@ -799,6 +799,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onClick = (e: React.MouseEvent): void => { + if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } + (e.nativeEvent as any).formattedHandled = true; if (e.button === 0 && ((!this.props.isSelected() && !e.ctrlKey) || (this.props.isSelected() && e.ctrlKey)) && !e.metaKey && e.target) { let href = (e.target as any).href; let location: string; -- cgit v1.2.3-70-g09d2 From 8c9d58c506d986d5dbdd087b429a449c8283ac12 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 7 Oct 2019 12:59:41 -0400 Subject: many changes to support nested documents within a text box --- src/client/util/RichTextSchema.tsx | 86 ++++++++++++---------- src/client/util/TooltipTextMenu.tsx | 9 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/DocumentView.tsx | 3 +- src/client/views/nodes/FormattedTextBox.tsx | 39 +++++----- 5 files changed, 73 insertions(+), 66 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index f5749a01b..29ec5b23c 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,24 +1,23 @@ +import { action, observable, runInAction, reaction, IReactionDisposer } from "mobx"; import { baseKeymap, toggleMark } from "prosemirror-commands"; import { redo, undo } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { DOMOutputSpecArray, Fragment, MarkSpec, Node, NodeSpec, Schema, Slice } from "prosemirror-model"; import { bulletList, listItem, orderedList } from 'prosemirror-schema-list'; -import { EditorState, TextSelection, NodeSelection } from "prosemirror-state"; +import { EditorState, NodeSelection, TextSelection } from "prosemirror-state"; import { StepMap } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; -import { Doc } from "../../new_fields/Doc"; -import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import * as ReactDOM from 'react-dom'; +import { Doc, WidthSym, HeightSym } from "../../new_fields/Doc"; +import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../Utils"; import { DocServer } from "../DocServer"; +import { DocumentView } from "../views/nodes/DocumentView"; import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; -import React = require("react"); -import { action, Lambda, observable, reaction, computed, runInAction, trace } from "mobx"; -import { observer } from "mobx-react"; -import * as ReactDOM from 'react-dom'; -import { DocumentView } from "../views/nodes/DocumentView"; -import { returnFalse, emptyFunction, returnEmptyString, returnOne } from "../../Utils"; import { Transform } from "./Transform"; -import { NumCast } from "../../new_fields/Types"; +import React = require("react"); +import { BoolCast } from "../../new_fields/Types"; +import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -716,7 +715,16 @@ export class DashDocView { _handle: HTMLElement; _dashSpan: HTMLDivElement; _outer: HTMLElement; - constructor(node: any, view: any, getPos: any, addDocTab: any) { + _dashDoc: Doc | undefined; + _reactionDisposer: IReactionDisposer | undefined; + _textBox: FormattedTextBox; + + getDocTransform = () => { + let { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer); + return new Transform(-translateX, -translateY, 1).scale(1 / scale); + } + constructor(node: any, view: any, getPos: any, tbox: FormattedTextBox) { + this._textBox = tbox; this._handle = document.createElement("span"); this._dashSpan = document.createElement("div"); this._outer = document.createElement("span"); @@ -741,19 +749,28 @@ export class DashDocView { this._handle.style.right = "-10px"; DocServer.GetRefField(node.attrs.docid).then(async dashDoc => { if (dashDoc instanceof Doc) { - let scale = () => 100 / NumCast(dashDoc.nativeWidth, 100); + self._dashDoc = dashDoc; + if (node.attrs.width !== dashDoc.width + "px" || node.attrs.height !== dashDoc.height + "px") { + view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: dashDoc.width + "px", height: dashDoc.height + "px" })); + } + this._reactionDisposer && this._reactionDisposer(); + this._reactionDisposer = reaction(() => { + return dashDoc[HeightSym](); + }, () => { + this._dashSpan.style.height = this._outer.style.height = dashDoc[HeightSym]() + "px"; + }); ReactDOM.render( 100} - PanelHeight={() => 100} + PanelWidth={self._dashDoc![WidthSym]} + PanelHeight={self._dashDoc![HeightSym]} focus={emptyFunction} backgroundColor={returnEmptyString} parentActive={returnFalse} @@ -763,24 +780,16 @@ export class DashDocView { getScale={returnOne} ContainingCollectionView={undefined} ContainingCollectionDoc={undefined} - ContentScaling={scale} - >, this._dashSpan); + ContentScaling={returnOne} + />, this._dashSpan); } }); let self = this; - this._dashSpan.onclick = function (e: any) { - FormattedTextBox.firstTarget && FormattedTextBox.firstTarget(); - }; - this._dashSpan.onpointermove = function (e: any) { - (e as any).formattedHandled = true; - }; - this._dashSpan.onpointerup = function (e: any) { - e.stopPropagation(); - }; - this._dashSpan.onpointerdown = function (e: any) { - }; + this._outer.onpointerenter = function (e: any) { self.selectNode(); }; + this._outer.onpointerleave = function (e: any) { self.deselectNode(); }; this._dashSpan.onkeydown = function (e: any) { e.stopPropagation(); }; this._dashSpan.onkeypress = function (e: any) { e.stopPropagation(); }; + this._dashSpan.onwheel = function (e: any) { e.preventDefault(); }; this._dashSpan.onkeyup = function (e: any) { e.stopPropagation(); }; this._handle.onpointerdown = function (e: any) { e.preventDefault(); @@ -789,19 +798,20 @@ export class DashDocView { const startY = e.pageY; const startWidth = parseFloat(node.attrs.width); const startHeight = parseFloat(node.attrs.height); - const onpointermove = (e: any) => { - const diffInPx = e.pageX - startX; - const diffInPy = e.pageY - startY; - self._outer.style.width = `${startWidth + diffInPx}`; - self._outer.style.height = `${startHeight + diffInPy}`; - }; + const onpointermove = action((e: any) => { + let { scale } = Utils.GetScreenTransform(self._handle as HTMLElement); + const diffInPx = (e.pageX - startX) / scale; + const diffInPy = (e.pageY - startY) / scale; + self._dashDoc!.width = startWidth + diffInPx; + self._dashDoc!.height = startHeight + diffInPy; + self._outer.style.width = `${self._dashDoc!.width}`; + self._outer.style.height = `${self._dashDoc!.height}`; + }); const onpointerup = () => { document.removeEventListener("pointermove", onpointermove); document.removeEventListener("pointerup", onpointerup); - let pos = view.state.selection.from; view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height })); - view.dispatch(view.state.tr.setSelection(new NodeSelection(view.state.doc.resolve(pos)))); }; document.addEventListener("pointermove", onpointermove); diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 9116ef995..55c6e6609 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -186,7 +186,7 @@ export class TooltipTextMenu { this.updateListItemDropdown(":", this.listTypeBtnDom); - this.update(view, undefined, undefined); + this.updateInternal(view, undefined, undefined); TooltipTextMenu.Toolbar = this.wrapper; } public static Toolbar: HTMLDivElement | undefined; @@ -439,7 +439,7 @@ export class TooltipTextMenu { let tr = state.tr; tr.addMark(state.selection.from, state.selection.to, mark); let content = tr.selection.content(); - let newNode = schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); + let newNode = state.schema.nodes.star.create({ visibility: false, text: content, textslice: content.toJSON() }); dispatch && dispatch(tr.replaceSelectionWith(newNode).removeMark(tr.selection.from - 1, tr.selection.from, mark)); return true; } @@ -587,7 +587,7 @@ export class TooltipTextMenu { class: "summarize", execEvent: "", run: (state, dispatch) => { - TooltipTextMenu.insertStar(state, dispatch); + TooltipTextMenu.insertStar(this.view.state, this.view.dispatch); } }); @@ -849,8 +849,9 @@ export class TooltipTextMenu { } } + update(view: EditorView, lastState: EditorState | undefined) { this.updateInternal(view, lastState, this.editorProps) } //updates the tooltip menu when the selection changes - update(view: EditorView, lastState: EditorState | undefined, props: any) { + private updateInternal(view: EditorView, lastState: EditorState | undefined, props: any) { this.view = view; let state = view.state; props && (this.editorProps = props); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index cf76945e0..38488f033 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -267,7 +267,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerDown = (e: React.PointerEvent): void => { - if (e.nativeEvent.cancelBubble || (e.nativeEvent as any).formattedHandled) return; + if (e.nativeEvent.cancelBubble) return; this._hitCluster = this.props.Document.useClusters ? this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)) !== -1 : false; if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) { document.removeEventListener("pointermove", this.onPointerMove); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 5a9202d40..a670d280d 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -202,7 +202,6 @@ export class DocumentView extends DocComponent(Docu } preventDefault && e.preventDefault(); } - if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); } } buttonClick = async (altKey: boolean, ctrlKey: boolean) => { @@ -234,7 +233,7 @@ export class DocumentView extends DocComponent(Docu } onPointerDown = (e: React.PointerEvent): void => { - if (e.nativeEvent.cancelBubble) return; + if (e.nativeEvent.cancelBubble && e.button === 0) return; this._downX = e.clientX; this._downY = e.clientY; this._hitTemplateDrag = false; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9396e3ea1..c030d4bce 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -268,9 +268,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image; node = model.create({ src: url, docid: link[Id] }); } else { + let alias = Doc.MakeAlias(target); + alias.fitToBox = true; node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: target[Id], + title: "dashDoc", docid: alias[Id], float: "none" }); } @@ -700,7 +702,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, dispatchTransaction: this.dispatchTransaction, nodeViews: { - dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self.props.addDocTab); }, + dashDoc(node, view, getPos) { return new DashDocView(node, view, getPos, self); }, image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); }, star(node, view, getPos) { return new SummarizedView(node, view, getPos); }, ordered_list(node, view, getPos) { return new OrderedListView(); }, @@ -749,10 +751,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._searchReactionDisposer && this._searchReactionDisposer(); this._editorView && this._editorView.destroy(); } - public static firstTarget: () => void = emptyFunction; onPointerDown = (e: React.PointerEvent): void => { - if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } - (e.nativeEvent as any).formattedHandled = true; let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); pos && (this._nodeClicked = this._editorView!.state.doc.nodeAt(pos.pos)); if (this.props.onClick && e.button === 0) { @@ -764,16 +763,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { e.preventDefault(); } - FormattedTextBox.firstTarget = () => { // this is here to support nested text boxes. when that happens, the click event will propagate through prosemirror to the outer editor. In RichTextSchema, the outer editor calls this function to revert the focus/selection - if (pos && pos.pos > 0) { - let node = this._editorView!.state.doc.nodeAt(pos.pos); - if (!node || (node.type !== this._editorView!.state.schema.nodes.dashDoc && node.type !== this._editorView!.state.schema.nodes.image && - pos.pos !== this._editorView!.state.selection.from)) { - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new TextSelection(this._editorView!.state.doc.resolve(pos!.pos)))); - this._editorView!.focus(); - } - } - }; } onPointerUp = (e: React.PointerEvent): void => { @@ -832,7 +821,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); e.preventDefault(); } - } // this hackiness handles clicking on the list item bullets to do expand/collapse. the bullets are ::before pseudo elements so there's no real way to hit test against them. if (this.props.isSelected() && e.nativeEvent.offsetX < 40) { @@ -858,14 +846,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } this._editorView!.focus(); } - onMouseDown = (e: React.MouseEvent): void => { - if (!this.props.isSelected()) { // preventing default allows the onClick to be generated instead of being swallowed by the text box itself - e.preventDefault(); // bcz: this would normally be in OnPointerDown - however, if done there, no mouse move events will be generated which makes transititioning to GoldenLayout's drag interactions impossible + onMouseUp = (e: React.MouseEvent): void => { + e.stopPropagation(); + + // this interposes on prosemirror's upHandler to prevent prosemirror's up from invoked multiple times when there are nested prosemirrors. We only want the lowest level prosemirror to be invoked. + if ((this._editorView as any).mouseDown) { + let originalUpHandler = (this._editorView as any).mouseDown.up; + (this._editorView as any).root.removeEventListener("mouseup", originalUpHandler); + (this._editorView as any).mouseDown.up = (e: MouseEvent) => { + !(e as any).formattedHandled && originalUpHandler(e); + e.stopPropagation(); + (e as any).formattedHandled = true; + }; + (this._editorView as any).root.addEventListener("mouseup", (this._editorView as any).mouseDown.up); } } tooltipTextMenuPlugin() { - let myprops = this.props; let self = FormattedTextBox; return new Plugin({ view(_editorView) { @@ -944,7 +941,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onBlur={this.onBlur} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} - onMouseDown={this.onMouseDown} + onMouseUp={this.onMouseUp} onWheel={this.onPointerWheel} onPointerEnter={action(() => this._entered = true)} onPointerLeave={action(() => this._entered = false)} -- cgit v1.2.3-70-g09d2 From 77d66d159d75442ff5635c4bf4843b6155883cc2 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 10 Oct 2019 14:04:22 -0400 Subject: removed CollectionVideoView --- src/client/documents/Documents.ts | 3 +- src/client/util/DocumentManager.ts | 5 +- src/client/util/SharingManager.tsx | 3 +- src/client/views/DocumentDecorations.tsx | 2 +- src/client/views/PreviewCursor.tsx | 18 +-- .../views/collections/CollectionBaseView.tsx | 15 +- .../views/collections/CollectionSchemaCells.tsx | 7 +- .../views/collections/CollectionSchemaView.tsx | 9 +- src/client/views/collections/CollectionSubView.tsx | 17 +- .../views/collections/CollectionVideoView.scss | 51 ------ .../views/collections/CollectionVideoView.tsx | 115 ------------- .../CollectionFreeFormLinkView.tsx | 2 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 8 +- .../collections/collectionFreeForm/MarqueeView.tsx | 14 +- src/client/views/linking/LinkFollowBox.tsx | 2 +- src/client/views/nodes/DocumentContentsView.tsx | 3 +- src/client/views/nodes/DocumentView.tsx | 7 +- src/client/views/nodes/FieldView.tsx | 5 +- src/client/views/nodes/PDFBox.tsx | 2 +- src/client/views/nodes/VideoBox.scss | 57 ++++++- src/client/views/nodes/VideoBox.tsx | 177 +++++++++++++++++++-- src/client/views/nodes/WebBox.tsx | 2 +- src/client/views/pdf/PDFViewer.tsx | 18 ++- 23 files changed, 284 insertions(+), 258 deletions(-) delete mode 100644 src/client/views/collections/CollectionVideoView.scss delete mode 100644 src/client/views/collections/CollectionVideoView.tsx (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 9d1a6ed3e..22aa74634 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -1,7 +1,6 @@ import { HistogramField } from "../northstar/dash-fields/HistogramField"; import { HistogramBox } from "../northstar/dash-nodes/HistogramBox"; import { HistogramOperation } from "../northstar/operations/HistogramOperation"; -import { CollectionVideoView } from "../views/collections/CollectionVideoView"; import { CollectionView } from "../views/collections/CollectionView"; import { CollectionViewType } from "../views/collections/CollectionBaseView"; import { AudioBox } from "../views/nodes/AudioBox"; @@ -137,7 +136,7 @@ export namespace Docs { options: { height: 150 } }], [DocumentType.VID, { - layout: { view: VideoBox, collectionView: [CollectionVideoView, data, anno] as CollectionViewType }, + layout: { view: VideoBox, ext: anno }, options: { currentTimecode: 0 }, }], [DocumentType.AUDIO, { diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index c95d923cb..24285a70a 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -3,7 +3,6 @@ import { Doc, DocListCastAsync } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; import { Cast, NumCast, StrCast } from '../../new_fields/Types'; import { CollectionDockingView } from '../views/collections/CollectionDockingView'; -import { CollectionVideoView } from '../views/collections/CollectionVideoView'; import { CollectionView } from '../views/collections/CollectionView'; import { DocumentView } from '../views/nodes/DocumentView'; import { LinkManager } from './LinkManager'; @@ -56,7 +55,7 @@ export class DocumentManager { return this.getDocumentViewsById(doc[Id]); } - public getDocumentViewById(id: string, preferredCollection?: CollectionView | CollectionVideoView): DocumentView | undefined { + public getDocumentViewById(id: string, preferredCollection?: CollectionView): DocumentView | undefined { let toReturn: DocumentView | undefined; let passes = preferredCollection ? [preferredCollection, undefined] : [undefined]; @@ -81,7 +80,7 @@ export class DocumentManager { return toReturn; } - public getDocumentView(toFind: Doc, preferredCollection?: CollectionView | CollectionVideoView): DocumentView | undefined { + public getDocumentView(toFind: Doc, preferredCollection?: CollectionView): DocumentView | undefined { return this.getDocumentViewById(toFind[Id], preferredCollection); } diff --git a/src/client/util/SharingManager.tsx b/src/client/util/SharingManager.tsx index c989b6c17..d37cd1b80 100644 --- a/src/client/util/SharingManager.tsx +++ b/src/client/util/SharingManager.tsx @@ -17,7 +17,6 @@ import * as fa from '@fortawesome/free-solid-svg-icons'; import { DocumentView } from "../views/nodes/DocumentView"; import { SelectionManager } from "./SelectionManager"; import { DocumentManager } from "./DocumentManager"; -import { CollectionVideoView } from "../views/collections/CollectionVideoView"; import { CollectionView } from "../views/collections/CollectionView"; library.add(fa.faCopy); @@ -185,7 +184,7 @@ export default class SharingManager extends React.Component<{}> { className={"focus-span"} title={title} onClick={() => { - let context: Opt; + let context: Opt; if (this.targetDoc && this.targetDocView && (context = this.targetDocView.props.ContainingCollectionView)) { DocumentManager.Instance.jumpToDocument(this.targetDoc, true, undefined, context.props.Document); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 4f9bdbe9c..1d9f0c74b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -333,7 +333,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> iconDoc.y = NumCast(doc.y) - 24; iconDoc.maximizedDocs = new List(selected.map(s => s.props.Document)); selected.length === 1 && (doc.minimizedDoc = iconDoc); - selected[0].props.addDocument && selected[0].props.addDocument(iconDoc, false); + selected[0].props.addDocument && selected[0].props.addDocument(iconDoc); return iconDoc; } @action diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx index 1aed51e64..eed2cc5da 100644 --- a/src/client/views/PreviewCursor.tsx +++ b/src/client/views/PreviewCursor.tsx @@ -14,7 +14,7 @@ export class PreviewCursor extends React.Component<{}> { static _onKeyPress?: (e: KeyboardEvent) => void; static _getTransform: () => Transform; static _addLiveTextDoc: (doc: Doc) => void; - static _addDocument: (doc: Doc, allowDuplicates: false) => boolean; + static _addDocument: (doc: Doc) => boolean; @observable static _clickPoint = [0, 0]; @observable public static Visible = false; //when focus is lost, this will remove the preview cursor @@ -44,7 +44,7 @@ export class PreviewCursor extends React.Component<{}> { title: url, width: 400, height: 315, nativeWidth: 600, nativeHeight: 472.5, x: newPoint[0], y: newPoint[1] - }), false); + })); return; } @@ -56,7 +56,7 @@ export class PreviewCursor extends React.Component<{}> { title: url, width: 300, height: 300, // nativeWidth: 300, nativeHeight: 472.5, x: newPoint[0], y: newPoint[1] - }), false); + })); return; } @@ -79,11 +79,11 @@ export class PreviewCursor extends React.Component<{}> { let img: Doc = Docs.Create.ImageDocument( arr[1], { - width: 300, title: arr[1], - x: newPoint[0], - y: newPoint[1], - }); - PreviewCursor._addDocument(img, false); + width: 300, title: arr[1], + x: newPoint[0], + y: newPoint[1], + }); + PreviewCursor._addDocument(img); return; } @@ -113,7 +113,7 @@ export class PreviewCursor extends React.Component<{}> { onKeyPress: (e: KeyboardEvent) => void, addLiveText: (doc: Doc) => void, getTransform: () => Transform, - addDocument: (doc: Doc, allowDuplicates: false) => boolean) { + addDocument: (doc: Doc) => boolean) { this._clickPoint = [x, y]; this._onKeyPress = onKeyPress; this._addLiveTextDoc = addLiveText; diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 62be1fc31..61919427a 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -45,7 +45,7 @@ export namespace CollectionViewType { } export interface CollectionRenderProps { - addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; + addDocument: (document: Doc) => boolean; removeDocument: (document: Doc) => boolean; moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; active: () => boolean; @@ -100,22 +100,13 @@ export class CollectionBaseView extends React.Component { @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } @action.bound - addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { - var curTime = NumCast(this.props.Document.currentTimecode, -1); - curTime !== -1 && (doc.displayTimecode = curTime); + addDocument(doc: Doc): boolean { if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplate ? this.extensionDoc : this.props.Document; let targetField = (this.props.fieldExt || this.props.Document.isTemplate) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; - const value = Cast(targetDataDoc[targetField], listSpec(Doc)); - if (value !== undefined) { - if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) { - value.push(doc); - } - } else { - Doc.GetProto(targetDataDoc)[targetField] = new List([doc]); - } + Doc.AddDocToList(targetDataDoc, targetField, doc); Doc.GetProto(doc).lastOpened = new DateField; return true; } diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index fd1362848..79c032723 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -14,15 +14,12 @@ import '../DocumentDecorations.scss'; import { EditableView } from "../EditableView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./CollectionSchemaView.scss"; -import { CollectionVideoView } from "./CollectionVideoView"; import { CollectionView } from "./CollectionView"; import { NumCast, StrCast, BoolCast, FieldValue, Cast } from "../../../new_fields/Types"; import { Docs } from "../../documents/Documents"; -import { DocumentContentsView } from "../nodes/DocumentContentsView"; import { SelectionManager } from "../../util/SelectionManager"; import { library } from '@fortawesome/fontawesome-svg-core'; import { faExpand } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { KeyCodes } from "../../northstar/utils/KeyCodes"; import { undoBatch } from "../../util/UndoManager"; @@ -33,8 +30,8 @@ export interface CellProps { row: number; col: number; rowProps: CellInfo; - CollectionView: Opt; - ContainingCollection: Opt; + CollectionView: Opt; + ContainingCollection: Opt; Document: Doc; fieldKey: string; renderDepth: number; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 670c6bd43..3218f630a 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -23,7 +23,6 @@ import '../DocumentDecorations.scss'; import { DocumentView } from "../nodes/DocumentView"; import "./CollectionSchemaView.scss"; import { CollectionSubView } from "./CollectionSubView"; -import { CollectionVideoView } from "./CollectionVideoView"; import { CollectionView } from "./CollectionView"; import { undoBatch } from "../../util/UndoManager"; import { CollectionSchemaHeader, CollectionSchemaAddColumnHeader } from "./CollectionSchemaHeaders"; @@ -246,8 +245,8 @@ export interface SchemaTableProps { PanelHeight: () => number; PanelWidth: () => number; childDocs?: Doc[]; - CollectionView: Opt; - ContainingCollectionView: Opt; + CollectionView: Opt; + ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; fieldKey: string; renderDepth: number; @@ -904,11 +903,11 @@ interface CollectionSchemaPreviewProps { ruleProvider: Doc | undefined; focus?: (doc: Doc) => void; showOverlays?: (doc: Doc) => { title?: string, caption?: string }; - CollectionView?: CollectionView | CollectionVideoView; + CollectionView?: CollectionView; CollectionDoc?: Doc; onClick?: ScriptField; getTransform: () => Transform; - addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; + addDocument: (document: Doc) => boolean; moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean; removeDocument: (document: Doc) => boolean; active: () => boolean; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 5e2b79278..689adc375 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -18,7 +18,6 @@ import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; import { FieldViewProps } from "../nodes/FieldView"; import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox"; -import { CollectionVideoView } from "./CollectionVideoView"; import { CollectionView } from "./CollectionView"; import React = require("react"); var path = require('path'); @@ -26,7 +25,7 @@ import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; import { ImageUtils } from "../../util/Import & Export/ImageUtils"; export interface CollectionViewProps extends FieldViewProps { - addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; + addDocument: (document: Doc) => boolean; removeDocument: (document: Doc) => boolean; moveDocument: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; PanelWidth: () => number; @@ -37,7 +36,7 @@ export interface CollectionViewProps extends FieldViewProps { } export interface SubCollectionViewProps extends CollectionViewProps { - CollectionView: Opt; + CollectionView: Opt; ruleProvider: Doc | undefined; } @@ -175,14 +174,14 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { DocServer.GetRefField(docid).then(f => { if (f instanceof Doc) { if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView - (f instanceof Doc) && this.props.addDocument(f, false); + (f instanceof Doc) && this.props.addDocument(f); } }); } else { this.props.addDocument && this.props.addDocument(Docs.Create.WebDocument(href, options)); } } else if (text) { - this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }), false); + this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text })); } return; } @@ -194,7 +193,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { let split = img.split("src=\"")[1].split("\"")[0]; let doc = Docs.Create.ImageDocument(split, { ...options, width: 300 }); ImageUtils.ExtractExif(doc); - this.props.addDocument(doc, false); + this.props.addDocument(doc); return; } else { let path = window.location.origin + "/doc/"; @@ -203,12 +202,12 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { DocServer.GetRefField(docid).then(f => { if (f instanceof Doc) { if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView - (f instanceof Doc) && this.props.addDocument(f, false); + (f instanceof Doc) && this.props.addDocument(f); } }); } else { let htmlDoc = Docs.Create.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text }); - this.props.addDocument(htmlDoc, false); + this.props.addDocument(htmlDoc); } return; } @@ -252,7 +251,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { let type = result["content-type"]; if (type) { Docs.Get.DocumentFromType(type, str, { ...options, width: 300, nativeWidth: type.indexOf("video") !== -1 ? 600 : 300 }) - .then(doc => doc && this.props.addDocument(doc, false)); + .then(doc => doc && this.props.addDocument(doc)); } }); promises.push(prom); diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss deleted file mode 100644 index 509851ebb..000000000 --- a/src/client/views/collections/CollectionVideoView.scss +++ /dev/null @@ -1,51 +0,0 @@ - -.collectionVideoView-cont{ - width: 100%; - height: 100%; - position: inherit; - top: 0; - left:0; - z-index: -1; - display:inline-table; -} -.collectionVideoView-time{ - color : white; - top :25px; - left : 25px; - position: absolute; - background-color: rgba(50, 50, 50, 0.2); - transform-origin: left top; -} -.collectionVideoView-snapshot{ - color : white; - top :25px; - right : 25px; - position: absolute; - background-color: rgba(50, 50, 50, 0.2); - transform-origin: left top; -} -.collectionVideoView-play { - width: 25px; - height: 20px; - bottom: 25px; - left : 25px; - position: absolute; - color : white; - background-color: rgba(50, 50, 50, 0.2); - border-radius: 4px; - text-align: center; - transform-origin: left bottom; -} -.collectionVideoView-full { - width: 25px; - height: 20px; - bottom: 25px; - right : 25px; - position: absolute; - color : white; - background-color: rgba(50, 50, 50, 0.2); - border-radius: 4px; - text-align: center; - transform-origin: right bottom; - -} \ No newline at end of file diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx deleted file mode 100644 index 3d898b7de..000000000 --- a/src/client/views/collections/CollectionVideoView.tsx +++ /dev/null @@ -1,115 +0,0 @@ -import { action } from "mobx"; -import { observer } from "mobx-react"; -import { NumCast } from "../../../new_fields/Types"; -import { FieldView, FieldViewProps } from "../nodes/FieldView"; -import { VideoBox } from "../nodes/VideoBox"; -import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView"; -import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; -import "./CollectionVideoView.scss"; -import React = require("react"); -import { InkingControl } from "../InkingControl"; -import { InkTool } from "../../../new_fields/InkField"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; - - -@observer -export class CollectionVideoView extends React.Component { - private _videoBox?: VideoBox; - - public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") { - return FieldView.LayoutString(CollectionVideoView, fieldKey, fieldExt); - } - private get uIButtons() { - let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); - let curTime = NumCast(this.props.Document.currentTimecode); - return ([
- {"" + Math.round(curTime)} - {" " + Math.round((curTime - Math.trunc(curTime)) * 100)} -
, -
- -
, - VideoBox._showControls ? (null) : [ -
- -
, -
- F -
- ]]); - } - - @action - onPlayDown = () => { - if (this._videoBox) { - if (this._videoBox.Playing) { - this._videoBox.Pause(); - } else { - this._videoBox.Play(); - } - } - } - - @action - onFullDown = (e: React.PointerEvent) => { - if (this._videoBox) { - this._videoBox.FullScreen(); - e.stopPropagation(); - e.preventDefault(); - } - } - - @action - onSnapshot = (e: React.PointerEvent) => { - if (this._videoBox) { - this._videoBox.Snapshot(); - e.stopPropagation(); - e.preventDefault(); - } - } - - _isclick = 0; - @action - onResetDown = (e: React.PointerEvent) => { - if (this._videoBox) { - this._videoBox.Pause(); - e.stopPropagation(); - this._isclick = 0; - document.addEventListener("pointermove", this.onPointerMove, true); - document.addEventListener("pointerup", this.onPointerUp, true); - InkingControl.Instance.switchTool(InkTool.Eraser); - } - } - - @action - onPointerMove = (e: PointerEvent) => { - this._isclick += Math.abs(e.movementX) + Math.abs(e.movementY); - if (this._videoBox) { - this._videoBox.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333)); - } - e.stopImmediatePropagation(); - } - @action - onPointerUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.onPointerMove, true); - document.removeEventListener("pointerup", this.onPointerUp, true); - InkingControl.Instance.switchTool(InkTool.None); - this._isclick < 10 && (this.props.Document.currentTimecode = 0); - } - setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; }; - - private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { - let props = { ...this.props, ...renderProps }; - return (<> - - {this.props.isSelected() ? this.uIButtons : (null)} - ); - } - - render() { - return ( - - {this.subView} - ); - } -} \ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index df089eb00..fe92eed10 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -10,7 +10,7 @@ export interface CollectionFreeFormLinkViewProps { A: Doc; B: Doc; LinkDocs: Doc[]; - addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; + addDocument: (document: Doc) => boolean; removeDocument: (document: Doc) => boolean; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 38488f033..d2644480c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -93,10 +93,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { heading = !sorted.length ? Math.max(1, maxHeading) : NumCast(sorted[sorted.length - 1].heading) === 1 ? 2 : NumCast(sorted[sorted.length - 1].heading); } !this.Document.isRuleProvider && (newBox.heading = heading); - this.addDocument(newBox, false); + this.addDocument(newBox); } - private addDocument = (newBox: Doc, allowDuplicates: boolean) => { - let added = this.props.addDocument(newBox, false); + private addDocument = (newBox: Doc) => { + let added = this.props.addDocument(newBox); added && this.bringToFront(newBox); added && this.updateCluster(newBox); return added; @@ -659,7 +659,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (doc instanceof Doc) { const [xx, yy] = this.props.ScreenToLocalTransform().transformPoint(x, y); doc.x = xx, doc.y = yy; - this.props.addDocument && this.props.addDocument(doc, false); + this.props.addDocument && this.props.addDocument(doc); } } } diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index eaf65b88c..bb787106c 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -24,7 +24,7 @@ interface MarqueeViewProps { getContainerTransform: () => Transform; getTransform: () => Transform; container: CollectionFreeFormView; - addDocument: (doc: Doc, allowDuplicates: false) => boolean; + addDocument: (doc: Doc) => boolean; activeDocuments: () => Doc[]; selectDocuments: (docs: Doc[]) => void; removeDocument: (doc: Doc) => boolean; @@ -83,7 +83,7 @@ export class MarqueeView extends React.Component ns.map(line => { let indent = line.search(/\S|$/); let newBox = Docs.Create.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line }); - this.props.addDocument(newBox, false); + this.props.addDocument(newBox); y += 40 * this.props.getTransform().Scale; }); })(); @@ -92,7 +92,7 @@ export class MarqueeView extends React.Component navigator.clipboard.readText().then(text => { let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== ""); if (ns.length === 1 && text.startsWith("http")) { - this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer + this.props.addDocument(Docs.Create.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }));// paste an image from its URL in the paste buffer } else { this.pasteTable(ns, x, y); } @@ -146,7 +146,7 @@ export class MarqueeView extends React.Component } let newCol = Docs.Create.SchemaDocument([...(groupAttr ? [new SchemaHeaderField("_group", "#f1efeb")] : []), ...columns.filter(c => c).map(c => new SchemaHeaderField(c, "#f1efeb"))], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 }); - this.props.addDocument(newCol, false); + this.props.addDocument(newCol); } } @action @@ -202,7 +202,7 @@ export class MarqueeView extends React.Component } } - setPreviewCursor = (x: number, y: number, drag: boolean) => { + setPreviewCursor = action((x: number, y: number, drag: boolean) => { if (drag) { this._downX = this._lastX = x; this._downY = this._lastY = y; @@ -217,7 +217,7 @@ export class MarqueeView extends React.Component this._downY = y; PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument); } - } + }) @action onClick = (e: React.MouseEvent): void => { @@ -350,7 +350,7 @@ export class MarqueeView extends React.Component } } else { - this.props.addDocument(newCollection, false); + this.props.addDocument(newCollection); this.props.selectDocuments([newCollection]); } this.cleanupInteractions(false); diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index b18aa5d63..32ebe7c61 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -278,7 +278,7 @@ export class LinkFollowBox extends React.Component { alias.width = width; alias.height = height; - this.sourceView.props.addDocument(alias, false); + this.sourceView.props.addDocument(alias); } } diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index a824ae9cc..e4b2ecffd 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -9,7 +9,6 @@ import DirectoryImportBox from "../../util/Import & Export/DirectoryImportBox"; import { CollectionDockingView } from "../collections/CollectionDockingView"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { CollectionSchemaView } from "../collections/CollectionSchemaView"; -import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; import { LinkFollowBox } from "../linking/LinkFollowBox"; import { YoutubeBox } from "./../../apis/youtube/YoutubeBox"; @@ -100,7 +99,7 @@ export class DocumentContentsView extends React.Component; + ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; Document: Doc; DataDoc?: Doc; fitToBox?: boolean; onClick?: ScriptField; - addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean; + addDocument?: (doc: Doc) => boolean; removeDocument?: (doc: Doc) => boolean; moveDocument?: (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean; ScreenToLocalTransform: () => Transform; @@ -217,7 +216,7 @@ export class DocumentView extends DocComponent(Docu let maxLocation = StrCast(this.Document.maximizeLocation, "inPlace"); maxLocation = this.Document.maximizeLocation = (!ctrlKey ? !altKey ? maxLocation : (maxLocation !== "inPlace" ? "inPlace" : "onRight") : (maxLocation !== "inPlace" ? "inPlace" : "inTab")); if (maxLocation === "inPlace") { - expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc, false)); + expandedDocs.forEach(maxDoc => this.props.addDocument && this.props.addDocument(maxDoc)); let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs); } else { diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index c17730f48..074efaf6c 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -8,7 +8,6 @@ import { List } from "../../../new_fields/List"; import { RichTextField } from "../../../new_fields/RichTextField"; import { AudioField, ImageField, VideoField } from "../../../new_fields/URLField"; import { Transform } from "../../util/Transform"; -import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; import { AudioBox } from "./AudioBox"; import { FormattedTextBox } from "./FormattedTextBox"; @@ -28,7 +27,7 @@ export interface FieldViewProps { fieldExt: string; leaveNativeSize?: boolean; fitToBox?: boolean; - ContainingCollectionView: Opt; + ContainingCollectionView: Opt; ContainingCollectionDoc: Opt; ruleProvider: Doc | undefined; Document: Doc; @@ -37,7 +36,7 @@ export interface FieldViewProps { isSelected: () => boolean; select: (isCtrlPressed: boolean) => void; renderDepth: number; - addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean; + addDocument?: (document: Doc) => boolean; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; removeDocument?: (document: Doc) => boolean; diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 1f3887608..57803be1f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -174,7 +174,7 @@ export class PDFBox extends DocComponent(PdfDocumen ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } - _initialScale: number | undefined; + _initialScale: number | undefined; // the initial scale of the PDF when first rendered which determines whether the document will be live on startup or not. Getting bigger after startup won't make it automatically be live.... render() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive"); diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index d651a8621..b3cd439aa 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -1,6 +1,14 @@ +// .videoBox-container { +// .collectionfreeformview-container { +// mix-blend-mode: multiply; +// } +// } + .videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen, .videoBox-content, .videoBox-content-interactive, .videoBox-cont-fullScreen { width: 100%; + z-index: 0; // logically this should be 0 (or unset) which would give us transparent brush strokes over videos. However, this makes Chrome crawl to a halt + position: absolute; } .videoBox-content, .videoBox-content-interactive, .videoBox-content-fullScreen { @@ -11,7 +19,52 @@ height: 100%; } -.videoBox-content-interactive, .videoBox-content-fullScreen, -.videoBox-content-YouTube-fullScreen { +.videoBox-content-interactive, .videoBox-content-fullScreen, .videoBox-content-YouTube-fullScreen { pointer-events: all; +} + +.videoBox-time{ + color : white; + top :25px; + left : 25px; + position: absolute; + background-color: rgba(50, 50, 50, 0.2); + transform-origin: left top; + pointer-events:all; +} +.videoBox-snapshot{ + color : white; + top :25px; + right : 25px; + position: absolute; + background-color:rgba(50, 50, 50, 0.2); + transform-origin: left top; + pointer-events:all; +} +.videoBox-play { + width: 25px; + height: 20px; + bottom: 25px; + left : 25px; + position: absolute; + color : white; + background-color: rgba(50, 50, 50, 0.2); + border-radius: 4px; + text-align: center; + transform-origin: left bottom; + pointer-events:all; +} +.videoBox-full { + width: 25px; + height: 20px; + bottom: 25px; + right : 25px; + position: absolute; + color : white; + background-color: rgba(50, 50, 50, 0.2); + border-radius: 4px; + text-align: center; + transform-origin: right bottom; + pointer-events:all; + } \ No newline at end of file diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index e83aa8bea..feb067d8f 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -3,11 +3,11 @@ import { action, computed, IReactionDisposer, observable, reaction, runInAction, import { observer } from "mobx-react"; import * as rp from 'request-promise'; import { InkTool } from "../../../new_fields/InkField"; -import { makeInterface, createSchema } from "../../../new_fields/Schema"; -import { Cast, FieldValue, NumCast, BoolCast } from "../../../new_fields/Types"; +import { makeInterface, createSchema, listSpec } from "../../../new_fields/Schema"; +import { Cast, FieldValue, NumCast, BoolCast, StrCast } from "../../../new_fields/Types"; import { VideoField } from "../../../new_fields/URLField"; import { RouteStore } from "../../../server/RouteStore"; -import { Utils } from "../../../Utils"; +import { Utils, emptyFunction, returnOne } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; @@ -22,6 +22,8 @@ import { faVideo } from "@fortawesome/free-solid-svg-icons"; import { Doc } from "../../../new_fields/Doc"; import { ScriptField } from "../../../new_fields/ScriptField"; import { positionSchema } from "./CollectionFreeFormDocumentView"; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; var path = require('path'); export const timeSchema = createSchema({ @@ -34,19 +36,24 @@ library.add(faVideo); @observer export class VideoBox extends DocComponent(VideoDocument) { + static _youtubeIframeCounter: number = 0; private _reactionDisposer?: IReactionDisposer; private _youtubeReactionDisposer?: IReactionDisposer; private _youtubePlayer: YT.Player | undefined = undefined; private _videoRef: HTMLVideoElement | null = null; private _youtubeIframeId: number = -1; private _youtubeContentCreated = false; - static _youtubeIframeCounter: number = 0; + private _downX: number = 0; + private _downY: number = 0; + private _isResetClick = 0; + private _isChildActive = false; + private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); @observable _forceCreateYouTubeIFrame = false; @observable static _showControls: boolean; @observable _playTimer?: NodeJS.Timeout = undefined; @observable _fullScreen = false; @observable public Playing: boolean = false; - public static LayoutString() { return FieldView.LayoutString(VideoBox); } + public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(VideoBox, "data", fieldExt); } public get player(): HTMLVideoElement | null { return this._videoRef; @@ -123,8 +130,8 @@ export class VideoBox extends DocComponent(VideoD //convert to desired file format var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png' // if you want to preview the captured image, - let filename = path.basename(encodeURIComponent("snapshot" + this.Document.title + "_" + (this.Document.currentTimecode || 0).toString())); - VideoBox.convertDataUri(dataUrl, filename.replace(/\..*$/, "")).then(returnedFilename => { + let filename = path.basename(encodeURIComponent("snapshot" + StrCast(this.Document.title).replace(/\..*$/, "") + "_" + (this.Document.currentTimecode || 0).toString().replace(/\./, "_"))); + VideoBox.convertDataUri(dataUrl, filename).then(returnedFilename => { if (returnedFilename) { let url = this.choosePath(Utils.prepend(returnedFilename)); let imageSummary = Docs.Create.ImageDocument(url, { @@ -132,7 +139,7 @@ export class VideoBox extends DocComponent(VideoD width: 150, height: height / width * 150, title: "--snapshot" + (this.Document.currentTimecode || 0) + " image-" }); imageSummary.isButton = true; - this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.addDocument && this.props.ContainingCollectionView.props.addDocument(imageSummary, false); + this.props.addDocument && this.props.addDocument(imageSummary); DocUtils.MakeLink({ doc: imageSummary }, { doc: this.props.Document }, "snapshot from " + this.Document.title, "video frame snapshot"); } }); @@ -259,7 +266,73 @@ export class VideoBox extends DocComponent(VideoD }); } + private get uIButtons() { + let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale); + let curTime = NumCast(this.props.Document.currentTimecode); + return ([
+ {"" + Math.round(curTime)} + {" " + Math.round((curTime - Math.trunc(curTime)) * 100)} +
, +
+ +
, + VideoBox._showControls ? (null) : [ +
+ +
, +
+ F +
+ ]]); + } + + @action + onPlayDown = () => { + if (this.Playing) { + this.Pause(); + } else { + this.Play(); + } + } + @action + onFullDown = (e: React.PointerEvent) => { + this.FullScreen(); + e.stopPropagation(); + e.preventDefault(); + } + + @action + onSnapshot = (e: React.PointerEvent) => { + this.Snapshot(); + e.stopPropagation(); + e.preventDefault(); + } + + @action + onResetDown = (e: React.PointerEvent) => { + this.Pause(); + e.stopPropagation(); + this._isResetClick = 0; + document.addEventListener("pointermove", this.onResetMove, true); + document.addEventListener("pointerup", this.onResetUp, true); + InkingControl.Instance.switchTool(InkTool.Eraser); + } + + @action + onResetMove = (e: PointerEvent) => { + this._isResetClick += Math.abs(e.movementX) + Math.abs(e.movementY); + this.Seek(Math.max(0, NumCast(this.props.Document.currentTimecode, 0) + Math.sign(e.movementX) * 0.0333)); + e.stopImmediatePropagation(); + } + @action + onResetUp = (e: PointerEvent) => { + document.removeEventListener("pointermove", this.onResetMove, true); + document.removeEventListener("pointerup", this.onResetUp, true); + InkingControl.Instance.switchTool(InkTool.None); + this._isResetClick < 10 && (this.props.Document.currentTimecode = 0); + } + @computed get fieldExtensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } @computed get youtubeContent() { @@ -273,11 +346,95 @@ export class VideoBox extends DocComponent(VideoD >; } + + setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => { + this._setPreviewCursor = func; + } + + @action.bound + removeDocument(doc: Doc): boolean { + Doc.GetProto(doc).annotationOn = undefined; + //TODO This won't create the field if it doesn't already exist + let targetDataDoc = this.fieldExtensionDoc; + let targetField = this.props.fieldExt; + let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); + let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); + index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); + index !== -1 && value.splice(index, 1); + return true; + } + // this is called with the document that was dragged and the collection to move it into. + // if the target collection is the same as this collection, then the move will be allowed. + // otherwise, the document being moved must be able to be removed from its container before + // moving it into the target. + @action.bound + moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { + if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { + return true; + } + return this.removeDocument(doc) ? addDocument(doc) : false; + } + + @action.bound + addDocument(doc: Doc): boolean { + Doc.GetProto(doc).annotationOn = this.props.Document; + var curTime = NumCast(this.props.Document.currentTimecode, -1); + curTime !== -1 && (doc.displayTimecode = curTime); + Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc); + return true; + } + whenActiveChanged = (isActive: boolean) => { + this._isChildActive = isActive; + this.props.whenActiveChanged(isActive); + } + active = () => { + return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; + } + onClick = (e: React.MouseEvent) => { + this._setPreviewCursor && + e.button === 0 && + Math.abs(e.clientX - this._downX) < 3 && + Math.abs(e.clientY - this._downY) < 3 && + this._setPreviewCursor(e.clientX, e.clientY, false); + } + + onPointerDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + if ((e.button !== 0 || e.altKey) && this.active()) { + this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true); + } + } + @computed get annotationLayer() { + return + + } + render() { Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); - return
+ return (
{this.youtubeVideoId ? this.youtubeContent : this.content} -
; + {this.annotationLayer} + {this.uIButtons} +
); } } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index 29eef27a0..bb4c5adc9 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -91,7 +91,7 @@ export class WebBox extends React.Component { }); SelectionManager.SelectedDocuments().map(dv => { - dv.props.addDocument && dv.props.addDocument(newBox, false); + dv.props.addDocument && dv.props.addDocument(newBox); dv.props.removeDocument && dv.props.removeDocument(dv.props.Document); }); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 65ca830d9..366861144 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -18,7 +18,6 @@ import PDFMenu from "./PDFMenu"; import "./PDFViewer.scss"; import React = require("react"); import * as rp from "request-promise"; -import { CollectionVideoView } from "../collections/CollectionVideoView"; import { CollectionView } from "../collections/CollectionView"; import Annotation from "./Annotation"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; @@ -50,10 +49,10 @@ interface IViewerProps { GoToPage?: (n: number) => void; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; - addDocument?: (doc: Doc, allowDuplicates?: boolean) => boolean; + addDocument?: (doc: Doc) => boolean; setPdfViewer: (view: PDFViewer) => void; ScreenToLocalTransform: () => Transform; - ContainingCollectionView: Opt; + ContainingCollectionView: Opt; whenActiveChanged: (isActive: boolean) => void; } @@ -582,12 +581,15 @@ export class PDFViewer extends React.Component { } return this.removeDocument(doc) ? addDocument(doc) : false; } - - + @action.bound + addDocument(doc: Doc): boolean { + Doc.GetProto(doc).annotationOn = this.props.Document; + Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); + return true; + } @action.bound removeDocument(doc: Doc): boolean { Doc.GetProto(doc).annotationOn = undefined; - //TODO This won't create the field if it doesn't already exist let targetDataDoc = this.props.fieldExtensionDoc; let targetField = this.props.fieldExt; let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); @@ -659,8 +661,8 @@ export class PDFViewer extends React.Component { whenActiveChanged={this.whenActiveChanged} removeDocument={this.removeDocument} moveDocument={this.moveDocument} - addDocument={(doc: Doc, allow: boolean | undefined) => { Doc.GetProto(doc).annotationOn = this.props.Document; Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); return true; }} - CollectionView={this.props.ContainingCollectionView} + addDocument={this.addDocument} + CollectionView={undefined} ScreenToLocalTransform={this.scrollXf} ruleProvider={undefined} renderDepth={this.props.renderDepth + 1} -- cgit v1.2.3-70-g09d2 From 21aab2fbf28e9cf6d08366b57d368d120d6813bb Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 10 Oct 2019 16:42:12 -0400 Subject: got rid of all CollectionView wrappers around XxxBox's added DocAnnotatableComponent --- src/client/documents/Documents.ts | 4 +- src/client/util/RichTextSchema.tsx | 4 +- src/client/views/DocComponent.tsx | 50 ++++++- .../collections/CollectionMasonryViewFieldRow.tsx | 2 +- .../CollectionStackingViewFieldColumn.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 1 + .../collectionFreeForm/CollectionFreeFormView.tsx | 20 +-- .../collections/collectionFreeForm/MarqueeView.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 8 +- src/client/views/nodes/ImageBox.scss | 8 +- src/client/views/nodes/ImageBox.tsx | 44 +++++-- src/client/views/nodes/PDFBox.tsx | 24 ++-- src/client/views/nodes/VideoBox.scss | 8 +- src/client/views/nodes/VideoBox.tsx | 143 ++++++--------------- src/client/views/nodes/WebBox.tsx | 69 +++++++--- src/client/views/pdf/Annotation.tsx | 8 +- src/client/views/pdf/PDFViewer.tsx | 65 +++------- src/new_fields/Doc.ts | 6 +- 18 files changed, 234 insertions(+), 234 deletions(-) (limited to 'src/client/views/collections/collectionFreeForm') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 22aa74634..0114f82d8 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -120,11 +120,11 @@ export namespace Docs { options: { height: 300, backgroundColor: "black" } }], [DocumentType.IMG, { - layout: { view: ImageBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, + layout: { view: ImageBox, ext: anno }, options: {} }], [DocumentType.WEB, { - layout: { view: WebBox, collectionView: [CollectionView, data, anno] as CollectionViewType }, + layout: { view: WebBox, ext: anno }, options: { height: 300 } }], [DocumentType.COL, { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index a5502577b..063686d58 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -784,8 +784,8 @@ export class DashDocView { addDocTab={self._textBox.props.addDocTab} pinToPres={returnFalse} renderDepth={1} - PanelWidth={self._dashDoc![WidthSym]} - PanelHeight={self._dashDoc![HeightSym]} + PanelWidth={self._dashDoc[WidthSym]} + PanelHeight={self._dashDoc[HeightSym]} focus={emptyFunction} backgroundColor={returnEmptyString} parentActive={returnFalse} diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index d6562492f..93e852bef 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,6 +1,8 @@ import * as React from 'react'; import { Doc } from '../../new_fields/Doc'; -import { computed } from 'mobx'; +import { computed, action } from 'mobx'; +import { Cast } from '../../new_fields/Types'; +import { listSpec } from '../../new_fields/Schema'; export function DocComponent

(schemaCtor: (doc: Doc) => T) { class Component extends React.Component

{ @@ -11,4 +13,50 @@ export function DocComponent

(schemaCtor: (doc: D } } return Component; +} + +interface DocAnnotatableProps { + Document: Doc; + DataDoc?: Doc; + fieldKey: string; + fieldExt: string; + whenActiveChanged: (isActive: boolean) => void; + isSelected: () => boolean, renderDepth: number +} +export function DocAnnotatableComponent

(schemaCtor: (doc: Doc) => T) { + class Component extends React.Component

{ + //TODO This might be pretty inefficient if doc isn't observed, because computed doesn't cache then + @computed + get Document(): T { + return schemaCtor(this.props.Document); + } + _isChildActive = false; + @action.bound + removeDocument(doc: Doc): boolean { + Doc.GetProto(doc).annotationOn = undefined; + let value = Cast(this.extensionDoc[this.props.fieldExt], listSpec(Doc), []); + let index = value ? Doc.IndexOf(doc, value.map(d => d as Doc), true) : -1; + return index !== -1 && value.splice(index, 1) ? true : false; + } + + @computed get dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } + + @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } + + // if the moved document is already in this overlay collection nothing needs to be done. + // otherwise, if the document can be removed from where it was, it will then be added to this document's overlay collection. + @action.bound + moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { + return Doc.AreProtosEqual(this.props.Document, targetCollection) ? true : this.removeDocument(doc) ? addDocument(doc) : false; + } + + @action.bound + addDocument(doc: Doc): boolean { + Doc.GetProto(doc).annotationOn = this.props.Document; + return Doc.AddDocToList(this.extensionDoc, this.props.fieldExt, doc); + } + whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive); + active = () => this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; + } + return Component; } \ No newline at end of file diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 5c1960d53..6251d7114 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -86,7 +86,7 @@ export class CollectionMasonryViewFieldRow extends React.Component { let parsed = parseInt(value); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index e8627780d..815b28586 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -72,7 +72,7 @@ export class CollectionStackingViewFieldColumn extends React.Component { let parsed = parseInt(value); if (!isNaN(parsed)) { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 689adc375..06d048383 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -38,6 +38,7 @@ export interface CollectionViewProps extends FieldViewProps { export interface SubCollectionViewProps extends CollectionViewProps { CollectionView: Opt; ruleProvider: Doc | undefined; + children?: never | (() => JSX.Element[]) | React.ReactNode; } export function CollectionSubView(schemaCtor: (doc: Doc) => T) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index d2644480c..adbad5da5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,6 +1,6 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; -import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faFileUpload } from "@fortawesome/free-solid-svg-icons"; +import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faFileUpload, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc"; @@ -286,7 +286,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerMove = (e: PointerEvent): void => { - if (!e.cancelBubble && !this.isAnnotationOverlay) { + if (!e.cancelBubble) { if (this._hitCluster && this.tryDragCluster(e)) { e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers e.preventDefault(); @@ -339,7 +339,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.props.Document.lockedPosition || this.props.Document.inOverlay || this.isAnnotationOverlay) return; + if (this.props.Document.lockedPosition || this.props.Document.inOverlay) return; if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming e.stopPropagation(); } @@ -477,7 +477,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { PanelWidth: layoutDoc[WidthSym], PanelHeight: layoutDoc[HeightSym], ContentScaling: returnOne, - ContainingCollectionView: this.props.CollectionView, + ContainingCollectionView: this.props.ContainingCollectionView, focus: this.focusDocument, backgroundColor: returnEmptyString, parentActive: this.props.active, @@ -681,10 +681,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } - private childViews = () => [ - , - ...this.views - ] + private childViews = () => { + let children = typeof this.props.children === "function" ? (this.props.children as any)() as JSX.Element[] : []; + return [ + , + ...children, + ...this.views, + ]; + } render() { // update the actual dimensions of the collection so that they can inquired (e.g., by a minimap) this.props.Document.fitX = this.contentBounds && this.contentBounds.x; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index bb787106c..ecdd02b0f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -217,7 +217,7 @@ export class MarqueeView extends React.Component this._downY = y; PreviewCursor.Show(x, y, this.onKeyPress, this.props.addLiveTextDocument, this.props.getTransform, this.props.addDocument); } - }) + }); @action onClick = (e: React.MouseEvent): void => { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9ca77cc8b..cbe4945af 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -218,13 +218,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); let res = terms.map(term => this.findInNode(this._editorView!, this._editorView!.state.doc, term)); - let tr = this._editorView!.state.tr; + let tr = this._editorView.state.tr; let flattened: TextSelection[] = []; res.map(r => r.map(h => flattened.push(h))); let lastSel = Math.min(flattened.length - 1, this._searchIndex); flattened.forEach((h: TextSelection, ind: number) => tr = tr.addMark(h.from, h.to, ind === lastSel ? activeMark : mark)); this._searchIndex = ++this._searchIndex > flattened.length - 1 ? 0 : this._searchIndex; - this._editorView!.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); + this._editorView.dispatch(tr.setSelection(new TextSelection(tr.doc.resolve(flattened[lastSel].from), tr.doc.resolve(flattened[lastSel].to))).scrollIntoView()); } } @@ -232,8 +232,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._editorView && (this._editorView as any).docView) { const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); - let end = this._editorView!.state.doc.nodeSize - 2; - this._editorView!.dispatch(this._editorView!.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); + let end = this._editorView.state.doc.nodeSize - 2; + this._editorView.dispatch(this._editorView.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); } } setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => { diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss index 71d718b39..97d858f58 100644 --- a/src/client/views/nodes/ImageBox.scss +++ b/src/client/views/nodes/ImageBox.scss @@ -1,9 +1,9 @@ -.imageBox-cont { +.imageBox-cont, .imageBox-cont-interactive { padding: 0vw; - position: relative; + position: absolute; text-align: center; width: 100%; - height: auto; + height: 100%; max-width: 100%; max-height: 100%; pointer-events: none; @@ -11,8 +11,6 @@ .imageBox-cont-interactive { pointer-events: all; - width: 100%; - height: auto; } .imageBox-dot { diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index a198a0764..9f39eccea 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -13,20 +13,21 @@ import { ComputedField } from '../../../new_fields/ScriptField'; import { BoolCast, Cast, FieldValue, NumCast, StrCast } from '../../../new_fields/Types'; import { AudioField, ImageField } from '../../../new_fields/URLField'; import { RouteStore } from '../../../server/RouteStore'; -import { Utils } from '../../../Utils'; +import { Utils, returnOne, emptyFunction } from '../../../Utils'; import { CognitiveServices, Confidence, Service, Tag } from '../../cognitive_services/CognitiveServices'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { undoBatch } from '../../util/UndoManager'; import { ContextMenu } from "../../views/ContextMenu"; import { ContextMenuProps } from '../ContextMenuItem'; -import { DocComponent } from '../DocComponent'; +import { DocAnnotatableComponent } from '../DocComponent'; import { InkingControl } from '../InkingControl'; import { documentSchema } from './DocumentView'; import FaceRectangles from './FaceRectangles'; import { FieldView, FieldViewProps } from './FieldView'; import "./ImageBox.scss"; import React = require("react"); +import { CollectionFreeFormView } from '../collections/collectionFreeForm/CollectionFreeFormView'; var requestImageSize = require('../../util/request-image-size'); var path = require('path'); const { Howl } = require('howler'); @@ -54,9 +55,10 @@ type ImageDocument = makeInterface<[typeof pageSchema, typeof documentSchema]>; const ImageDocument = makeInterface(pageSchema, documentSchema); @observer -export class ImageBox extends DocComponent(ImageDocument) { +export class ImageBox extends DocAnnotatableComponent(ImageDocument) { - public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ImageBox, fieldKey); } + public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(ImageBox, "data", fieldExt); } + @observable static _showControls: boolean; private _imgRef: React.RefObject = React.createRef(); private _downX: number = 0; private _downY: number = 0; @@ -65,10 +67,6 @@ export class ImageBox extends DocComponent(ImageD private dropDisposer?: DragManager.DragDropDisposer; @observable private hoverActive = false; - @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } - protected createDropTarget = (ele: HTMLDivElement) => { if (this.dropDisposer) { this.dropDisposer(); @@ -381,7 +379,8 @@ export class ImageBox extends DocComponent(ImageD return (null); } - render() { + @computed + get content() { // let transform = this.props.ScreenToLocalTransform().inverse(); let pw = typeof this.props.PanelWidth === "function" ? this.props.PanelWidth() : typeof this.props.PanelWidth === "number" ? (this.props.PanelWidth as any) as number : 50; // var [sptX, sptY] = transform.transformPoint(0, 0); @@ -393,7 +392,6 @@ export class ImageBox extends DocComponent(ImageD let paths: string[] = [Utils.CorsProxy("http://www.cs.brown.edu/~bcz/noImage.png")]; // this._curSuffix = ""; // if (w > 20) { - Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); let alts = DocListCast(this.extensionDoc.Alternates); let altpaths: string[] = alts.filter(doc => doc.data instanceof ImageField).map(doc => this.choosePath((doc.data as ImageField).url)); let field = this.dataDoc[this.props.fieldKey]; @@ -448,4 +446,30 @@ export class ImageBox extends DocComponent(ImageD

); } + + render() { + Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); + return (
+ + {() => [this.content]} + +
); + } } \ No newline at end of file diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 57803be1f..9af6d7cad 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,17 +1,21 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } from 'mobx'; +import { action, observable, runInAction } from 'mobx'; import { observer } from "mobx-react"; import * as Pdfjs from "pdfjs-dist"; import "pdfjs-dist/web/pdf_viewer.css"; import 'react-image-lightbox/style.css'; -import { Doc, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Opt, WidthSym } from "../../../new_fields/Doc"; import { makeInterface } from "../../../new_fields/Schema"; import { ScriptField } from '../../../new_fields/ScriptField'; -import { Cast, NumCast } from "../../../new_fields/Types"; +import { Cast } from "../../../new_fields/Types"; import { PdfField } from "../../../new_fields/URLField"; +import { Utils } from '../../../Utils'; import { KeyCodes } from '../../northstar/utils/KeyCodes'; +import { undoBatch } from '../../util/UndoManager'; import { panZoomSchema } from '../collections/collectionFreeForm/CollectionFreeFormView'; -import { DocComponent } from "../DocComponent"; +import { ContextMenu } from '../ContextMenu'; +import { ContextMenuProps } from '../ContextMenuItem'; +import { DocAnnotatableComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; import { PDFViewer } from "../pdf/PDFViewer"; import { documentSchema } from "./DocumentView"; @@ -19,22 +23,17 @@ import { FieldView, FieldViewProps } from './FieldView'; import { pageSchema } from "./ImageBox"; import "./PDFBox.scss"; import React = require("react"); -import { undoBatch } from '../../util/UndoManager'; -import { ContextMenuProps } from '../ContextMenuItem'; -import { ContextMenu } from '../ContextMenu'; -import { Utils } from '../../../Utils'; type PdfDocument = makeInterface<[typeof documentSchema, typeof panZoomSchema, typeof pageSchema]>; const PdfDocument = makeInterface(documentSchema, panZoomSchema, pageSchema); @observer -export class PDFBox extends DocComponent(PdfDocument) { +export class PDFBox extends DocAnnotatableComponent(PdfDocument) { public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(PDFBox, "data", fieldExt); } private _keyValue: string = ""; private _valueValue: string = ""; private _scriptValue: string = ""; private _searchString: string = ""; - private _isChildActive = false; private _everActive = false; // has this box ever had its contents activated -- if so, stop drawing the overlay title private _pdfViewer: PDFViewer | undefined; private _keyRef: React.RefObject = React.createRef(); @@ -46,9 +45,6 @@ export class PDFBox extends DocComponent(PdfDocumen @observable private _pdf: Opt; @observable private _pageControls = false; - @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } - componentDidMount() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); if (pdfUrl instanceof PdfField) { @@ -206,7 +202,7 @@ export class PDFBox extends DocComponent(PdfDocumen pinToPres={this.props.pinToPres} addDocument={this.props.addDocument} ScreenToLocalTransform={this.props.ScreenToLocalTransform} select={this.props.select} isSelected={this.props.isSelected} whenActiveChanged={this.whenActiveChanged} - fieldKey={this.props.fieldKey} fieldExtensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} /> + fieldKey={this.props.fieldKey} extensionDoc={this.extensionDoc} startupLive={this._initialScale < 2.5 ? true : false} /> {this.settingsPanel()}
); } diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss index b3cd439aa..48623eaaf 100644 --- a/src/client/views/nodes/VideoBox.scss +++ b/src/client/views/nodes/VideoBox.scss @@ -1,8 +1,6 @@ -// .videoBox-container { -// .collectionfreeformview-container { -// mix-blend-mode: multiply; -// } -// } +.videoBox-container { + pointer-events: all; +} .videoBox-content-YouTube, .videoBox-content-YouTube-fullScreen, .videoBox-content, .videoBox-content-interactive, .videoBox-cont-fullScreen { diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index feb067d8f..aa9b28118 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -11,7 +11,7 @@ import { Utils, emptyFunction, returnOne } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; -import { DocComponent } from "../DocComponent"; +import { DocAnnotatableComponent } from "../DocComponent"; import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; import { documentSchema } from "./DocumentView"; @@ -35,7 +35,7 @@ const VideoDocument = makeInterface(documentSchema, positionSchema, timeSchema); library.add(faVideo); @observer -export class VideoBox extends DocComponent(VideoDocument) { +export class VideoBox extends DocAnnotatableComponent(VideoDocument) { static _youtubeIframeCounter: number = 0; private _reactionDisposer?: IReactionDisposer; private _youtubeReactionDisposer?: IReactionDisposer; @@ -43,16 +43,12 @@ export class VideoBox extends DocComponent(VideoD private _videoRef: HTMLVideoElement | null = null; private _youtubeIframeId: number = -1; private _youtubeContentCreated = false; - private _downX: number = 0; - private _downY: number = 0; private _isResetClick = 0; - private _isChildActive = false; - private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); @observable _forceCreateYouTubeIFrame = false; - @observable static _showControls: boolean; @observable _playTimer?: NodeJS.Timeout = undefined; @observable _fullScreen = false; - @observable public Playing: boolean = false; + @observable _playing = false; + @observable static _showControls: boolean; public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(VideoBox, "data", fieldExt); } public get player(): HTMLVideoElement | null { @@ -72,7 +68,7 @@ export class VideoBox extends DocComponent(VideoD } @action public Play = (update: boolean = true) => { - this.Playing = true; + this._playing = true; update && this.player && this.player.play(); update && this._youtubePlayer && this._youtubePlayer.playVideo(); this._youtubePlayer && !this._playTimer && (this._playTimer = setInterval(this.updateTimecode, 5)); @@ -85,7 +81,7 @@ export class VideoBox extends DocComponent(VideoD } @action public Pause = (update: boolean = true) => { - this.Playing = false; + this._playing = false; update && this.player && this.player.pause(); update && this._youtubePlayer && this._youtubePlayer.pauseVideo && this._youtubePlayer.pauseVideo(); this._youtubePlayer && this._playTimer && clearInterval(this._playTimer); @@ -181,7 +177,7 @@ export class VideoBox extends DocComponent(VideoD vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen); this._reactionDisposer && this._reactionDisposer(); this._reactionDisposer = reaction(() => this.Document.currentTimecode || 0, - time => !this.Playing && (vref.currentTime = time), { fireImmediately: true }); + time => !this._playing && (vref.currentTime = time), { fireImmediately: true }); } } @@ -218,7 +214,7 @@ export class VideoBox extends DocComponent(VideoD let interactive = InkingControl.Instance.selectedTool || !this.props.isSelected() ? "" : "-interactive"; let style = "videoBox-content" + (this._fullScreen ? "-fullScreen" : "") + interactive; return !field ?
Loading
: -
, VideoBox._showControls ? (null) : [
- +
, -
+
F
]]); } @action - onPlayDown = () => { - if (this.Playing) { - this.Pause(); - } else { - this.Play(); - } - } + onPlayDown = () => this._playing ? this.Pause() : this.Play() @action onFullDown = (e: React.PointerEvent) => { @@ -342,97 +332,40 @@ export class VideoBox extends DocComponent(VideoD let start = untracked(() => Math.round(this.Document.currentTimecode || 0)); return ; - } - - - setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => { - this._setPreviewCursor = func; + src={`https://www.youtube.com/embed/${this.youtubeVideoId}?enablejsapi=1&rel=0&showinfo=1&autoplay=1&mute=1&start=${start}&modestbranding=1&controls=${VideoBox._showControls ? 1 : 0}`} />; } @action.bound - removeDocument(doc: Doc): boolean { - Doc.GetProto(doc).annotationOn = undefined; - //TODO This won't create the field if it doesn't already exist - let targetDataDoc = this.fieldExtensionDoc; - let targetField = this.props.fieldExt; - let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); - let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); - index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); - index !== -1 && value.splice(index, 1); - return true; - } - // this is called with the document that was dragged and the collection to move it into. - // if the target collection is the same as this collection, then the move will be allowed. - // otherwise, the document being moved must be able to be removed from its container before - // moving it into the target. - @action.bound - moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { - return true; - } - return this.removeDocument(doc) ? addDocument(doc) : false; - } - - @action.bound - addDocument(doc: Doc): boolean { + addDocumentWithTimestamp(doc: Doc): boolean { Doc.GetProto(doc).annotationOn = this.props.Document; var curTime = NumCast(this.props.Document.currentTimecode, -1); curTime !== -1 && (doc.displayTimecode = curTime); - Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc); - return true; - } - whenActiveChanged = (isActive: boolean) => { - this._isChildActive = isActive; - this.props.whenActiveChanged(isActive); - } - active = () => { - return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; - } - onClick = (e: React.MouseEvent) => { - this._setPreviewCursor && - e.button === 0 && - Math.abs(e.clientX - this._downX) < 3 && - Math.abs(e.clientY - this._downY) < 3 && - this._setPreviewCursor(e.clientX, e.clientY, false); - } - - onPointerDown = (e: React.PointerEvent): void => { - this._downX = e.clientX; - this._downY = e.clientY; - if ((e.button !== 0 || e.altKey) && this.active()) { - this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true); - } - } - @computed get annotationLayer() { - return - + return Doc.AddDocToList(this.fieldExtensionDoc, this.props.fieldExt, doc); } render() { Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); - return (
- {this.youtubeVideoId ? this.youtubeContent : this.content} - {this.annotationLayer} + return (
+ + {() => [this.youtubeVideoId ? this.youtubeContent : this.content]} + {this.uIButtons}
); } diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index bb4c5adc9..7c7f9fb83 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -1,34 +1,36 @@ +import { library } from "@fortawesome/fontawesome-svg-core"; +import { faStickyNote } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { FieldResult, Doc, Field } from "../../../new_fields/Doc"; +import { Doc, FieldResult } from "../../../new_fields/Doc"; import { HtmlField } from "../../../new_fields/HtmlField"; +import { InkTool } from "../../../new_fields/InkField"; +import { makeInterface } from "../../../new_fields/Schema"; +import { Cast, NumCast } from "../../../new_fields/Types"; import { WebField } from "../../../new_fields/URLField"; +import { emptyFunction, returnOne, Utils } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; +import { SelectionManager } from "../../util/SelectionManager"; +import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { DocumentDecorations } from "../DocumentDecorations"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from './FieldView'; +import { KeyValueBox } from "./KeyValueBox"; import "./WebBox.scss"; import React = require("react"); -import { InkTool } from "../../../new_fields/InkField"; -import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types"; -import { Utils } from "../../../Utils"; -import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { faStickyNote } from '@fortawesome/free-solid-svg-icons'; -import { observable, action, computed } from "mobx"; -import { listSpec } from "../../../new_fields/Schema"; -import { RefField } from "../../../new_fields/RefField"; -import { ObjectField } from "../../../new_fields/ObjectField"; -import { updateSourceFile } from "typescript"; -import { KeyValueBox } from "./KeyValueBox"; -import { setReactionScheduler } from "mobx/lib/internal"; -import { library } from "@fortawesome/fontawesome-svg-core"; -import { SelectionManager } from "../../util/SelectionManager"; -import { Docs } from "../../documents/Documents"; +import { documentSchema } from "./DocumentView"; +import { DocAnnotatableComponent } from "../DocComponent"; library.add(faStickyNote); +type WebDocument = makeInterface<[typeof documentSchema]>; +const WebDocument = makeInterface(documentSchema); + @observer -export class WebBox extends React.Component { +export class WebBox extends DocAnnotatableComponent(WebDocument) { - public static LayoutString() { return FieldView.LayoutString(WebBox); } + public static LayoutString(fieldExt?: string) { return FieldView.LayoutString(WebBox, "data", fieldExt); } @observable private collapsed: boolean = true; @observable private url: string = ""; @@ -162,8 +164,10 @@ export class WebBox extends React.Component { e.stopPropagation(); } } - render() { - let field = this.props.Document[this.props.fieldKey]; + + @computed + get content() { + let field = this.dataDoc[this.props.fieldKey]; let view; if (field instanceof HtmlField) { view = ; @@ -189,4 +193,29 @@ export class WebBox extends React.Component { {!frozen ? (null) :
} ); } + render() { + Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); + return (
+ + {() => [this.content]} + +
); + } } \ No newline at end of file diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 5a07b88d9..ad6240c70 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -11,7 +11,7 @@ import "./Annotation.scss"; interface IAnnotationProps { anno: Doc; - fieldExtensionDoc: Doc; + extensionDoc: Doc; addDocTab: (document: Doc, dataDoc: Opt, where: string) => boolean; pinToPres: (document: Doc) => void; focus: (doc: Doc) => void; @@ -29,7 +29,7 @@ interface IRegionAnnotationProps { y: number; width: number; height: number; - fieldExtensionDoc: Doc; + extensionDoc: Doc; addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => boolean; pinToPres: (document: Doc) => void; document: Doc; @@ -66,12 +66,12 @@ class RegionAnnotation extends React.Component { } deleteAnnotation = () => { - let annotation = DocListCast(this.props.fieldExtensionDoc.annotations); + let annotation = DocListCast(this.props.extensionDoc.annotations); let group = FieldValue(Cast(this.props.document.group, Doc)); if (group) { if (annotation.indexOf(group) !== -1) { let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc))); - this.props.fieldExtensionDoc.annotations = new List(newAnnotations); + this.props.extensionDoc.annotations = new List(newAnnotations); } DocListCast(group.annotations).forEach(anno => anno.delete = true); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 366861144..d0759d207 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -6,7 +6,7 @@ import { Dictionary } from "typescript-collections"; import { Doc, DocListCast, FieldResult, WidthSym, Opt, HeightSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; -import { listSpec } from "../../../new_fields/Schema"; +import { makeInterface } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from "../../../Utils"; @@ -23,19 +23,24 @@ import Annotation from "./Annotation"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { SelectionManager } from "../../util/SelectionManager"; import { undoBatch } from "../../util/UndoManager"; +import { DocAnnotatableComponent } from "../DocComponent"; +import { documentSchema } from "../nodes/DocumentView"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); pdfjsLib.GlobalWorkerOptions.workerSrc = `/assets/pdf.worker.js`; +type PdfDocument = makeInterface<[typeof documentSchema]>; +const PdfDocument = makeInterface(documentSchema); interface IViewerProps { pdf: Pdfjs.PDFDocumentProxy; url: string; - Document: Doc; - DataDoc?: Doc; - fieldExtensionDoc: Doc; fieldKey: string; fieldExt: string; + Document: Doc; + DataDoc?: Doc; + ContainingCollectionView: Opt; + extensionDoc: Doc; PanelWidth: () => number; PanelHeight: () => number; ContentScaling: () => number; @@ -52,7 +57,6 @@ interface IViewerProps { addDocument?: (doc: Doc) => boolean; setPdfViewer: (view: PDFViewer) => void; ScreenToLocalTransform: () => Transform; - ContainingCollectionView: Opt; whenActiveChanged: (isActive: boolean) => void; } @@ -60,7 +64,7 @@ interface IViewerProps { * Handles rendering and virtualization of the pdf */ @observer -export class PDFViewer extends React.Component { +export class PDFViewer extends DocAnnotatableComponent(PdfDocument) { static _annotationStyle: any = addStyleSheet(); @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _annotations: Doc[] = []; @@ -79,7 +83,6 @@ export class PDFViewer extends React.Component { private _pdfViewer: any; private _retries = 0; // number of times tried to create the PDF viewer - private _isChildActive = false; private _setPreviewCursor: undefined | ((x: number, y: number, drag: boolean) => void); private _annotationLayer: React.RefObject = React.createRef(); private _reactionDisposer?: IReactionDisposer; @@ -97,7 +100,7 @@ export class PDFViewer extends React.Component { private _coverPath: any; @computed get allAnnotations() { - return DocListCast(this.props.fieldExtensionDoc.annotations).filter( + return DocListCast(this.props.extensionDoc.annotations).filter( anno => this._script.run({ this: anno }, console.log, true).result); } @@ -186,7 +189,7 @@ export class PDFViewer extends React.Component { await this.initialLoad(); this._annotationReactionDisposer = reaction( - () => this.props.fieldExtensionDoc && DocListCast(this.props.fieldExtensionDoc.annotations), + () => this.props.extensionDoc && DocListCast(this.props.extensionDoc.annotations), annotations => annotations && annotations.length && this.renderAnnotations(annotations, true), { fireImmediately: true }); @@ -530,7 +533,7 @@ export class PDFViewer extends React.Component { highlight = (color: string) => { // creates annotation documents for current highlights let annotationDoc = this.makeAnnotationDocument(color); - annotationDoc && Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, annotationDoc); + annotationDoc && Doc.AddDocToList(this.props.extensionDoc, this.props.fieldExt, annotationDoc); return annotationDoc; } @@ -570,40 +573,9 @@ export class PDFViewer extends React.Component { DragManager.StartDocumentDrag([], new DragManager.DocumentDragData([view]), 0, 0); } - // this is called with the document that was dragged and the collection to move it into. - // if the target collection is the same as this collection, then the move will be allowed. - // otherwise, the document being moved must be able to be removed from its container before - // moving it into the target. - @action.bound - moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean { - if (Doc.AreProtosEqual(this.props.Document, targetCollection)) { - return true; - } - return this.removeDocument(doc) ? addDocument(doc) : false; - } - @action.bound - addDocument(doc: Doc): boolean { - Doc.GetProto(doc).annotationOn = this.props.Document; - Doc.AddDocToList(this.props.fieldExtensionDoc, this.props.fieldExt, doc); - return true; - } - @action.bound - removeDocument(doc: Doc): boolean { - Doc.GetProto(doc).annotationOn = undefined; - let targetDataDoc = this.props.fieldExtensionDoc; - let targetField = this.props.fieldExt; - let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); - let index = value.reduce((p, v, i) => (v instanceof Doc && v === doc) ? i : p, -1); - index = index !== -1 ? index : value.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, doc)) ? i : p, -1); - index !== -1 && value.splice(index, 1); - return true; - } scrollXf = () => { return this._mainCont.current ? this.props.ScreenToLocalTransform().translate(0, this._scrollTop) : this.props.ScreenToLocalTransform(); } - setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => { - this._setPreviewCursor = func; - } onClick = (e: React.MouseEvent) => { this._setPreviewCursor && e.button === 0 && @@ -611,13 +583,9 @@ export class PDFViewer extends React.Component { Math.abs(e.clientY - this._downY) < 3 && this._setPreviewCursor(e.clientX, e.clientY, false); } - whenActiveChanged = (isActive: boolean) => { - this._isChildActive = isActive; - this.props.whenActiveChanged(isActive); - } - active = () => { - return this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; - } + + setPreviewCursor = (func?: (x: number, y: number, drag: boolean) => void) => this._setPreviewCursor = func; + getCoverImage = () => { if (!this.props.Document[HeightSym]() || !this.props.Document.nativeHeight) { @@ -632,7 +600,6 @@ export class PDFViewer extends React.Component { style={{ position: "absolute", display: "inline-block", top: 0, left: 0, width: `${nativeWidth}px`, height: `${nativeHeight}px` }} />; } - @action onZoomWheel = (e: React.WheelEvent) => { e.stopPropagation(); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 418863bcc..eb752f8c6 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -332,8 +332,10 @@ export namespace Doc { return Array.from(results); } - export function IndexOf(toFind: Doc, list: Doc[]) { - return list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)); + export function IndexOf(toFind: Doc, list: Doc[], allowProtos: boolean = true) { + let index = list.reduce((p, v, i) => (v instanceof Doc && v === toFind) ? i : p, -1); + index = allowProtos && index !== -1 ? index : list.reduce((p, v, i) => (v instanceof Doc && Doc.AreProtosEqual(v, toFind)) ? i : p, -1); + return index; // list.findIndex(doc => doc === toFind || Doc.AreProtosEqual(doc, toFind)); } export function RemoveDocFromList(listDoc: Doc, key: string, doc: Doc) { if (listDoc[key] === undefined) { -- cgit v1.2.3-70-g09d2