From 9427474b473d70974784a1517a1be902fb8d18ee Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 2 Oct 2019 18:26:55 -0400 Subject: many fixes to pdfs, linking, annotations, presentations. --- src/client/views/nodes/FormattedTextBox.tsx | 79 ++++++++++++----------------- 1 file changed, 33 insertions(+), 46 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 449f56b16..749886d9a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -9,7 +9,7 @@ import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos } import { EditorState, Plugin, Transaction, TextSelection, NodeSelection } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCast, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { List } from '../../../new_fields/List'; import { RichTextField } from "../../../new_fields/RichTextField"; @@ -230,7 +230,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink(this.dataDoc, this.dataDoc[key] as Doc, undefined, "Ref:" + value, undefined, undefined, id, true); + else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id, true); }); }); }); @@ -342,7 +342,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image; let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] }))); - DocUtils.MakeLink(this.dataDoc, target, undefined, "ImgRef:" + target.title, undefined, undefined, target[Id]); + DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); this.tryUpdateHeight(); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { @@ -667,55 +667,42 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe handlePaste = (view: EditorView, event: Event, slice: Slice): boolean => { let cbe = event as ClipboardEvent; - let docId: string; - let regionId: string; - if (!cbe.clipboardData) { - return false; - } - let linkId: string; - docId = cbe.clipboardData.getData("dash/pdfOrigin"); - regionId = cbe.clipboardData.getData("dash/pdfRegion"); - if (!docId || !regionId) { - return false; - } - - DocServer.GetRefField(docId).then(doc => { - DocServer.GetRefField(regionId).then(region => { - if (!(doc instanceof Doc) || !(region instanceof Doc)) { - return; - } - - let annotations = DocListCast(region.annotations); - annotations.forEach(anno => anno.target = this.props.Document); - let fieldExtDoc = Doc.fieldExtensionDoc(doc, "data"); - let targetAnnotations = DocListCast(fieldExtDoc.annotations); - if (targetAnnotations) { - targetAnnotations.push(region); - fieldExtDoc.annotations = new List(targetAnnotations); - } + const pdfDocId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfOrigin"); + const pdfRegionId = cbe.clipboardData && cbe.clipboardData.getData("dash/pdfRegion"); + if (pdfDocId && pdfRegionId) { + DocServer.GetRefField(pdfDocId).then(pdfDoc => { + DocServer.GetRefField(pdfRegionId).then(pdfRegion => { + if ((pdfDoc instanceof Doc) && (pdfRegion instanceof Doc)) { + setTimeout(async () => { + let targetAnnotations = await DocListCastAsync(Doc.fieldExtensionDoc(pdfDoc, "data").annotations);// bcz: NO... this assumes the pdf is using its 'data' field. need to have the PDF's view handle updating its own annotations + targetAnnotations && targetAnnotations.push(pdfRegion); + }); - let link = DocUtils.MakeLink(this.props.Document, region, doc); - if (link) { - cbe.clipboardData!.setData("dash/linkDoc", link[Id]); - linkId = link[Id]; - let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(doc.title))); - slice = new Slice(frag, slice.openStart, slice.openEnd); - var tr = view.state.tr.replaceSelection(slice); - view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); - } + let link = DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: pdfRegion, ctx: pdfDoc }, "note on " + pdfDoc.title, "pasted PDF link"); + if (link) { + cbe.clipboardData!.setData("dash/linkDoc", link[Id]); + let linkId = link[Id]; + let frag = addMarkToFrag(slice.content, (node: Node) => addLinkMark(node, StrCast(pdfDoc.title), linkId)); + slice = new Slice(frag, slice.openStart, slice.openEnd); + var tr = view.state.tr.replaceSelection(slice); + view.dispatch(tr.scrollIntoView().setMeta("paste", true).setMeta("uiEvent", "paste")); + } + } + }); }); - }); + return true; + } + return false; - return true; function addMarkToFrag(frag: Fragment, marker: (node: Node) => Node) { const nodes: Node[] = []; frag.forEach(node => nodes.push(marker(node))); return Fragment.fromArray(nodes); } - function addLinkMark(node: Node, title: string) { + function addLinkMark(node: Node, title: string, linkId: string) { if (!node.isText) { - const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title)); + const content = addMarkToFrag(node.content, (node: Node) => addLinkMark(node, title, linkId)); return node.copy(content); } const marks = [...node.marks]; @@ -879,16 +866,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (jumpToDoc) { if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); + DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey); return; } } if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), undefined, targetContext); + DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), targetContext); } else if (jumpToDoc) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); + DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } else { - DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); + DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } } }); -- cgit v1.2.3-70-g09d2 From 456e9120857f20fb609ab13bb07cbd8a2d2f850b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Thu, 3 Oct 2019 00:25:44 -0400 Subject: cleaned up link following code. changed opening in place behavior to not open if view already exists. fixed formattedText box scrolling. fixed clicking on image in text box. more... --- src/client/util/DocumentManager.ts | 49 +++++---- src/client/util/RichTextSchema.tsx | 26 +---- src/client/util/SearchUtil.ts | 1 - src/client/views/MetadataEntryMenu.tsx | 2 +- src/client/views/linking/LinkFollowBox.tsx | 82 ++++----------- src/client/views/nodes/DocumentView.scss | 1 + src/client/views/nodes/DocumentView.tsx | 10 +- src/client/views/nodes/FormattedTextBox.tsx | 152 ++++++++++++---------------- src/client/views/nodes/ImageBox.tsx | 2 +- src/client/views/pdf/Annotation.tsx | 2 +- src/new_fields/Doc.ts | 17 +++- 11 files changed, 148 insertions(+), 196 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 4ebcdf83c..305a77b14 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -12,6 +12,7 @@ import { undoBatch, UndoManager } from './UndoManager'; import { Scripting } from './Scripting'; import { List } from '../../new_fields/List'; import { SelectionManager } from './SelectionManager'; +import { notDeepEqual } from 'assert'; export class DocumentManager { @@ -131,12 +132,12 @@ export class DocumentManager { return pairs; } - public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, closeContextIfNotFound: boolean = false): Promise => { + public jumpToDocument = async (targetDoc: Doc, willZoom: boolean, dockFunc?: (doc: Doc) => void, docContext?: Doc, linkId?: string, closeContextIfNotFound: boolean = false): Promise => { 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? annotatedDoc && docView.props.focus(annotatedDoc, false); - docView.props.focus(targetDoc, willZoom); + docView.props.focus(docView.props.Document, willZoom); } else { const contextDocs = docContext ? await DocListCastAsync(docContext.data) : undefined; const contextDoc = contextDocs && contextDocs.find(doc => Doc.AreProtosEqual(doc, targetDoc)) ? docContext : undefined; @@ -160,38 +161,52 @@ export class DocumentManager { if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document); (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); // otherwise create a new view of the target } + const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); + finalDocView && (finalDocView.Document.scrollToLinkID = linkId); + finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document); }, 0); } else { // there's no context view so we need to create one first and try again targetDocContext.scrollY = 0; (dockFunc || CollectionDockingView.AddRightSplit)(targetDocContext, undefined); setTimeout(() => { const foundTargetDocContextView = DocumentManager.Instance.getDocumentView(targetDocContext); - if (foundTargetDocContextView) { // we should always find a target context here.... - this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, true); // so call jump to doc again and if the doc isn't found, it will be created. + if (foundTargetDocContextView) { // we might be lucky and the context loads right away + this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true); // so call jump to doc again and if the doc isn't found, it will be created. + } else { + setTimeout(() => { // if not, wait a bit to see if the context can be loaded (e.g., a PDF). + const foundTargetDocContextView = DocumentManager.Instance.getDocumentView(targetDocContext); + if (foundTargetDocContextView) { // now we should always find a target context here.... + this.jumpToDocument(targetDoc, willZoom, dockFunc, undefined, linkId, true); // so call jump to doc again and if the doc isn't found, it will be created. + } + }, 2000) } - }, 2000); // the long timeout gives the context view a chance to create its children. think pdf's which need to be activated to render their annotations. + }, 0); } } } + const finalDocView = DocumentManager.Instance.getFirstDocumentView(targetDoc); + finalDocView && (finalDocView.Document.scrollToLinkID = linkId); + finalDocView && Doc.linkFollowHighlight(finalDocView.props.Document); } - public async FollowLink(doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { - let linkDocs = LinkManager.Instance.getAllRelatedLinks(doc); + public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { + const linkDocs = link ? [link] : LinkManager.Instance.getAllRelatedLinks(doc); SelectionManager.DeselectAll(); - let firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) && !linkDoc.anchor1anchored); - let secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) && !linkDoc.anchor2anchored); + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) && !linkDoc.anchor1anchored); + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) && !linkDoc.anchor2anchored); const firstDocWithoutView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); const secondDocWithoutView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); - let first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs; - let second = secondDocWithoutView ? [secondDocWithoutView] : secondDocs; - let linkFollowDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor2 as Doc] : undefined; - let 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]; - if (linkFollowDocs && !linkFollowDocs.some(l => l instanceof Promise)) { - let maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab"); - let targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; + const first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs; + 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]; + if (linkFollowDocs && linkDoc) { + const maxLocation = StrCast(linkFollowDocs[0].maximizeLocation, "inTab"); + const targetContext = !Doc.AreProtosEqual(linkFollowDocContexts[reverse ? 1 : 0], currentContext) ? linkFollowDocContexts[reverse ? 1 : 0] : undefined; DocumentManager.Instance.jumpToDocument(linkFollowDocs[reverse ? 1 : 0], zoom, // open up target if it's not already in view ... by zooming into the button document first and setting flag to reset zoom afterwards - (doc: Doc) => focus(doc, maxLocation), targetContext); + (doc: Doc) => focus(doc, maxLocation), targetContext, linkDoc[Id]); } } diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 49bd93942..066266873 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -13,6 +13,7 @@ import { Cast, NumCast } from "../../new_fields/Types"; import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; import { times } from "async"; +import { LinkManager } from "./LinkManager"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -619,27 +620,10 @@ export class ImageResizeView { if (!view.isOverlay || e.ctrlKey) { e.preventDefault(); e.stopPropagation(); - DocServer.GetRefField(node.attrs.docid).then(async linkDoc => { - const location = node.attrs.location; - if (linkDoc instanceof Doc) { - let proto = Doc.GetProto(linkDoc); - let targetContext = await Cast(proto.targetContext, Doc); - let jumpToDoc = await Cast(linkDoc.anchor2, Doc); - if (jumpToDoc) { - if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey); - return; - } - } - if (targetContext) { - DocumentManager.Instance.jumpToDocument(targetContext, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab")); - } else if (jumpToDoc) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab")); - } else { - DocumentManager.Instance.jumpToDocument(linkDoc, e.ctrlKey, document => addDocTab(document, undefined, location ? location : "inTab")); - } - } - }); + DocServer.GetRefField(node.attrs.docid).then(async linkDoc => + (linkDoc instanceof Doc) && + DocumentManager.Instance.FollowLink(linkDoc, (view.state.schema as any).Document, + document => addDocTab(document, undefined, node.attrs.location ? node.attrs.location : "inTab"), false)); } }; this._handle.onpointerdown = function (e: any) { diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index d8b9dbec6..e37f1f90d 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -3,7 +3,6 @@ import { DocServer } from '../DocServer'; import { Doc } from '../../new_fields/Doc'; import { Id } from '../../new_fields/FieldSymbols'; import { Utils } from '../../Utils'; -import { ResultParameters } from '../northstar/model/idea/idea'; import { DocumentType } from '../documents/DocumentTypes'; export namespace SearchUtil { diff --git a/src/client/views/MetadataEntryMenu.tsx b/src/client/views/MetadataEntryMenu.tsx index f1b101b8e..41453f8b2 100644 --- a/src/client/views/MetadataEntryMenu.tsx +++ b/src/client/views/MetadataEntryMenu.tsx @@ -3,7 +3,7 @@ import "./MetadataEntryMenu.scss"; import { observer } from 'mobx-react'; import { observable, action, runInAction, trace, computed, IReactionDisposer, reaction } from 'mobx'; import { KeyValueBox } from './nodes/KeyValueBox'; -import { Doc, Field, DocListCast, DocListCastAsync } from '../../new_fields/Doc'; +import { Doc, Field, DocListCastAsync } from '../../new_fields/Doc'; import * as Autosuggest from 'react-autosuggest'; import { undoBatch } from '../util/UndoManager'; import { emptyFunction } from '../../Utils'; diff --git a/src/client/views/linking/LinkFollowBox.tsx b/src/client/views/linking/LinkFollowBox.tsx index 53b720a9e..2bff3ded4 100644 --- a/src/client/views/linking/LinkFollowBox.tsx +++ b/src/client/views/linking/LinkFollowBox.tsx @@ -152,21 +152,7 @@ export class LinkFollowBox extends React.Component { this.resetPan(); } - unhighlight = () => { - Doc.UnhighlightAll(); - document.removeEventListener("pointerdown", this.unhighlight); - } - - @action - highlightDoc = () => { - if (LinkFollowBox.destinationDoc) { - document.removeEventListener("pointerdown", this.unhighlight); - Doc.HighlightDoc(LinkFollowBox.destinationDoc); - window.setTimeout(() => { - document.addEventListener("pointerdown", this.unhighlight); - }, 10000); - } - } + highlightDoc = () => LinkFollowBox.destinationDoc && Doc.linkFollowHighlight(LinkFollowBox.destinationDoc); @undoBatch openFullScreen = () => { @@ -235,44 +221,11 @@ export class LinkFollowBox extends React.Component { @undoBatch jumpToLink = async (options: { shouldZoom: boolean }) => { - if (LinkFollowBox.destinationDoc && LinkFollowBox.linkDoc) { - let jumpToDoc: Doc = LinkFollowBox.destinationDoc; - let pdfDoc = FieldValue(Cast(LinkFollowBox.destinationDoc, Doc)); - if (pdfDoc) { - jumpToDoc = pdfDoc; - } - let proto = Doc.GetProto(LinkFollowBox.linkDoc); - let targetContext = await Cast(proto.targetContext, Doc); - let sourceContext = await Cast(proto.sourceContext, Doc); - let guid = StrCast(LinkFollowBox.linkDoc[Id]); - const shouldZoom = options ? options.shouldZoom : false; - - let dockingFunc = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; - - if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor2 && targetContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, async document => dockingFunc(document), targetContext); - } - else if (LinkFollowBox.destinationDoc === LinkFollowBox.linkDoc.anchor1 && sourceContext) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, document => dockingFunc(sourceContext!)); - if (LinkFollowBox.sourceDoc && LinkFollowBox.destinationDoc) { - if (guid) { - let views = DocumentManager.Instance.getDocumentViews(jumpToDoc); - views.length && (views[0].props.Document.scrollToLinkID = guid); - } else { - jumpToDoc.linkHref = Utils.prepend("/doc/" + StrCast(LinkFollowBox.linkDoc[Id])); - } - } - } - else if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom); - - } - else { - DocumentManager.Instance.jumpToDocument(jumpToDoc, shouldZoom, dockingFunc); - } + if (LinkFollowBox.sourceDoc && LinkFollowBox.linkDoc) { + let focus = (document: Doc) => { (LinkFollowBox._addDocTab || this.props.addDocTab)(document, undefined, "inTab"); SelectionManager.DeselectAll(); }; + //let focus = (doc: Doc, maxLocation: string) => this.props.focus(docthis.props.focus(LinkFollowBox.destinationDoc, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)); - this.highlightDoc(); - SelectionManager.DeselectAll(); + DocumentManager.Instance.FollowLink(LinkFollowBox.linkDoc, LinkFollowBox.sourceDoc, focus, options && options.shouldZoom, false, undefined); } } @@ -310,20 +263,23 @@ export class LinkFollowBox extends React.Component { openLinkInPlace = (options: { shouldZoom: boolean }) => { if (LinkFollowBox.destinationDoc && LinkFollowBox.sourceDoc) { - let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); - let y = NumCast(LinkFollowBox.sourceDoc.y); - let x = NumCast(LinkFollowBox.sourceDoc.x); + if (this.sourceView && this.sourceView.props.addDocument) { + let destViews = DocumentManager.Instance.getDocumentViews(LinkFollowBox.destinationDoc); + if (!destViews.find(dv => dv.props.ContainingCollectionView === this.sourceView!.props.ContainingCollectionView)) { + let alias = Doc.MakeAlias(LinkFollowBox.destinationDoc); + let y = NumCast(LinkFollowBox.sourceDoc.y); + let x = NumCast(LinkFollowBox.sourceDoc.x); - let width = NumCast(LinkFollowBox.sourceDoc.width); - let height = NumCast(LinkFollowBox.sourceDoc.height); + let width = NumCast(LinkFollowBox.sourceDoc.width); + let height = NumCast(LinkFollowBox.sourceDoc.height); - alias.x = x + width + 30; - alias.y = y; - alias.width = width; - alias.height = height; + alias.x = x + width + 30; + alias.y = y; + alias.width = width; + alias.height = height; - if (this.sourceView && this.sourceView.props.addDocument) { - this.sourceView.props.addDocument(alias, false); + this.sourceView.props.addDocument(alias, false); + } } this.jumpToLink({ shouldZoom: options.shouldZoom }); diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 4ea200e6d..b3e7898c1 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -5,6 +5,7 @@ top: 0; left:0; border-radius: inherit; + transition : outline .3s linear; // background: $light-color; //overflow: hidden; transform-origin: left top; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 3273abc1d..67c3fe6e7 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -224,7 +224,7 @@ export class DocumentView extends DocComponent(Docu } } else if (linkDocs.length) { - DocumentManager.Instance.FollowLink(this.props.Document, + DocumentManager.Instance.FollowLink(undefined, this.props.Document, (doc: Doc, maxLocation: string) => this.props.focus(this.props.Document, true, 1, () => this.props.addDocTab(doc, undefined, maxLocation)), ctrlKey, altKey, this.props.ContainingCollectionDoc); } @@ -658,6 +658,8 @@ export class DocumentView extends DocComponent(Docu let animheight = animDims ? animDims[1] : nativeHeight; let animwidth = animDims ? animDims[0] : nativeWidth; + const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; + const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"]; return (
(Docu transition: this.props.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition), pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all", color: StrCast(this.Document.color), - outlineColor: ["transparent", "maroon", "maroon", "yellow"][fullDegree], - outlineStyle: ["none", "dashed", "solid", "solid"][fullDegree], - outlineWidth: fullDegree && !borderRounding ? `${localScale}px` : "0px", - border: fullDegree && borderRounding ? `${["none", "dashed", "solid", "solid"][fullDegree]} ${["transparent", "maroon", "maroon", "yellow"][fullDegree]} ${localScale}px` : undefined, + outline: fullDegree && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", + border: fullDegree && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, background: backgroundColor, width: animwidth, height: animheight, diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 749886d9a..9347868b3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,47 +1,46 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; +import _ from "lodash"; import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; +import { inputRules } from 'prosemirror-inputrules'; import { keymap } from "prosemirror-keymap"; -import { Fragment, Node, Node as ProsNode, NodeType, Slice, Mark, ResolvedPos } from "prosemirror-model"; -import { EditorState, Plugin, Transaction, TextSelection, NodeSelection } from "prosemirror-state"; +import { Fragment, Mark, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model"; +import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; +import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCast, Opt, WidthSym, DocListCastAsync } from "../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; -import { List } from '../../../new_fields/List'; import { RichTextField } from "../../../new_fields/RichTextField"; -import { BoolCast, Cast, NumCast, StrCast, DateCast, PromiseValue } from "../../../new_fields/Types"; +import { RichTextUtils } from '../../../new_fields/RichTextUtils'; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Utils, numberRange, timenow } from '../../../Utils'; +import { Cast, DateCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { numberRange, timenow, Utils } from '../../../Utils'; +import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; +import { DocumentType } from '../../documents/DocumentTypes'; +import { DictationManager } from '../../util/DictationManager'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { ImageResizeView, schema, SummarizedView, OrderedListView, FootnoteView } from "../../util/RichTextSchema"; +import { FootnoteView, ImageResizeView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; +import { DocumentButtonBar } from '../DocumentButtonBar'; +import { DocumentDecorations } from '../DocumentDecorations'; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; +import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; import React = require("react"); -import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; -import { DocumentDecorations } from '../DocumentDecorations'; -import { DictationManager } from '../../util/DictationManager'; -import { ReplaceStep } from 'prosemirror-transform'; -import { DocumentType } from '../../documents/DocumentTypes'; -import { RichTextUtils } from '../../../new_fields/RichTextUtils'; -import _ from "lodash"; -import { formattedTextBoxCommentPlugin, FormattedTextBoxComment } from './FormattedTextBoxComment'; -import { inputRules } from 'prosemirror-inputrules'; -import { DocumentButtonBar } from '../DocumentButtonBar'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -142,51 +141,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined); } FormattedTextBox.Instance = this; - this._scrollToRegionReactionDisposer = reaction( - () => StrCast(this.props.Document.scrollToLinkID), - async (scrollToLinkID) => { - let findLinkFrag = (frag: Fragment, editor: EditorView) => { - const nodes: Node[] = []; - frag.forEach((node, index) => { - let examinedNode = findLinkNode(node, editor); - if (examinedNode && examinedNode.textContent) { - nodes.push(examinedNode); - start += index; - } - }); - return { frag: Fragment.fromArray(nodes), start: start }; - }; - let findLinkNode = (node: Node, editor: EditorView) => { - if (!node.isText) { - const content = findLinkFrag(node.content, editor); - return node.copy(content.frag); - } - const marks = [...node.marks]; - const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); - return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; - }; - - let start = -1; - if (this._editorView && scrollToLinkID) { - let editor = this._editorView; - let ret = findLinkFrag(editor.state.doc.content, editor); - - if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) { - let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start - if (ret.frag.firstChild) { - selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected - } - editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); - const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); - setTimeout(() => this.unhighlightSearchTerms(), 2000); - } - this.props.Document.scrollToLinkID = undefined; - } - - }, - { fireImmediately: true } - ); } public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } @@ -341,8 +295,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let url = de.data.urlField.url.href; let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image; let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); - this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: target[Id] }))); - DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); + let link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); + link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: link[Id] }))); this.tryUpdateHeight(); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { @@ -424,6 +378,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe _keymap: any = undefined; @computed get config() { this._keymap = buildKeymap(schema); + (schema as any).Document = this.props.Document; return { schema, plugins: this.props.isOverlay ? [ @@ -563,6 +518,51 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, 0); }), { fireImmediately: true } ); + this._scrollToRegionReactionDisposer = reaction( + () => StrCast(this.props.Document.scrollToLinkID), + async (scrollToLinkID) => { + let findLinkFrag = (frag: Fragment, editor: EditorView) => { + const nodes: Node[] = []; + frag.forEach((node, index) => { + let examinedNode = findLinkNode(node, editor); + if (examinedNode && examinedNode.textContent) { + nodes.push(examinedNode); + start += index; + } + }); + return { frag: Fragment.fromArray(nodes), start: start }; + }; + let findLinkNode = (node: Node, editor: EditorView) => { + if (!node.isText) { + const content = findLinkFrag(node.content, editor); + return node.copy(content.frag); + } + const marks = [...node.marks]; + const linkIndex = marks.findIndex(mark => mark.type === editor.state.schema.marks.link); + return linkIndex !== -1 && scrollToLinkID === marks[linkIndex].attrs.href.replace(/.*\/doc\//, "") ? node : undefined; + }; + + let start = -1; + if (this._editorView && scrollToLinkID) { + let editor = this._editorView; + let ret = findLinkFrag(editor.state.doc.content, editor); + + if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) { + let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start + if (ret.frag.firstChild) { + selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected + } + editor.dispatch(editor.state.tr.setSelection(new TextSelection(selection.$from, selection.$from)).scrollIntoView()); + const mark = editor.state.schema.mark(this._editorView.state.schema.marks.search_highlight); + setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); + setTimeout(() => this.unhighlightSearchTerms(), 2000); + } + this.props.Document.scrollToLinkID = undefined; + } + + }, + { fireImmediately: true } + ); setTimeout(() => this.tryUpdateHeight(), 0); } @@ -858,27 +858,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe 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 => { - if (linkDoc instanceof Doc) { - let proto = Doc.GetProto(linkDoc); - let targetContext = await Cast(proto.targetContext, Doc); - let jumpToDoc = await Cast(linkDoc.anchor2, Doc); - - if (jumpToDoc) { - if (DocumentManager.Instance.getDocumentView(jumpToDoc)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, e.altKey); - return; - } - } - if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) { - DocumentManager.Instance.jumpToDocument(jumpToDoc || targetContext, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab"), targetContext); - } else if (jumpToDoc) { - DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); - } else { - DocumentManager.Instance.jumpToDocument(linkDoc, ctrlKey, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); - } - } - }); + DocServer.GetRefField(this._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(); } diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index fe4f75cad..a198a0764 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -299,7 +299,7 @@ export class ImageBox extends DocComponent(ImageD let rotation = NumCast(this.dataDoc.rotation) % 180; let realsize = rotation === 90 || rotation === 270 ? { height: size.width, width: size.height } : size; let aspect = realsize.height / realsize.width; - if (layoutdoc.nativeHeight !== 0 && layoutdoc.nativeWidth !== 0 && (Math.abs(1 - NumCast(layoutdoc.nativeHeight) / NumCast(layoutdoc.nativeWidth) / (realsize.height / realsize.width)) > 0.1)) { + if (layoutdoc.width && (Math.abs(1 - NumCast(layoutdoc.height) / NumCast(layoutdoc.width) / (realsize.height / realsize.width)) > 0.1)) { setTimeout(action(() => { layoutdoc.height = layoutdoc[WidthSym]() * aspect; layoutdoc.nativeHeight = realsize.height; diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index f7f52b3ef..98e04d93e 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -98,7 +98,7 @@ class RegionAnnotation extends React.Component { else if (e.button === 0) { let annoGroup = await Cast(this.props.document.group, Doc); if (annoGroup) { - DocumentManager.Instance.FollowLink(annoGroup, + DocumentManager.Instance.FollowLink(undefined, annoGroup, (doc: Doc, maxLocation: string) => this.props.addDocTab(doc, undefined, e.ctrlKey ? "onRight" : "inTab"), false, false, undefined); } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 58304cebb..6acc6e1ca 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -637,7 +637,7 @@ export namespace Doc { export function isBrushedHighlightedDegree(doc: Doc) { if (Doc.IsHighlighted(doc)) { - return 3; + return 6; } else { return Doc.IsBrushedDegree(doc); @@ -673,6 +673,21 @@ export namespace Doc { return doc; } + export function linkFollowUnhighlight() { + Doc.UnhighlightAll(); + document.removeEventListener("pointerdown", linkFollowUnhighlight); + } + + let dt = 0; + export function linkFollowHighlight(destDoc: Doc) { + linkFollowUnhighlight(); + Doc.HighlightDoc(destDoc); + document.removeEventListener("pointerdown", linkFollowUnhighlight); + document.addEventListener("pointerdown", linkFollowUnhighlight); + let x = dt = Date.now(); + window.setTimeout(() => dt == x && linkFollowUnhighlight(), 5000); + } + export class HighlightBrush { @observable HighlightedDoc: Map = new Map(); } -- cgit v1.2.3-70-g09d2 From 2413d93a31ad4c97e09f79b97bc19346e72a1537 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 3 Oct 2019 16:31:31 -0400 Subject: improved search results to avoid showing aliases. improved Pdf results display. --- src/client/util/SearchUtil.ts | 34 ++++++++++++++++++++++++----- src/client/views/nodes/FormattedTextBox.tsx | 5 ----- src/client/views/pdf/Annotation.tsx | 2 +- src/client/views/pdf/PDFViewer.scss | 17 ++++++++++----- src/client/views/pdf/PDFViewer.tsx | 33 +++++++++++++++++++--------- src/client/views/search/SearchItem.scss | 13 +++++++++-- src/client/views/search/SearchItem.tsx | 19 ++++++++++------ 7 files changed, 87 insertions(+), 36 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/SearchUtil.ts b/src/client/util/SearchUtil.ts index e37f1f90d..6706dcb89 100644 --- a/src/client/util/SearchUtil.ts +++ b/src/client/util/SearchUtil.ts @@ -41,23 +41,45 @@ export namespace SearchUtil { } let { ids, numFound, highlighting } = result; - let lines: string[][] = ids.map(i => []); let txtresult = query !== "*" && JSON.parse(await rp.get(Utils.prepend("/textsearch"), { qs: { ...options, q: query }, })); + let fileids = txtresult ? txtresult.ids : []; + let newIds: string[] = []; + let newLines: string[][] = []; await Promise.all(fileids.map(async (tr: string, i: number) => { let docQuery = "fileUpload_t:" + tr.substr(0, 7); //If we just have a filter query, search for * as the query let docResult = JSON.parse(await rp.get(Utils.prepend("/search"), { qs: { ...options, q: docQuery } })); - ids.push(...docResult.ids); - lines.push(...docResult.ids.map((dr: any) => txtresult.lines[i])); - numFound += docResult.numFound; + newIds.push(...docResult.ids); + newLines.push(...docResult.ids.map((dr: any) => txtresult.lines[i])); })); + + let theDocs: Doc[] = []; + let theLines: string[][] = []; + const textDocMap = await DocServer.GetRefFields(newIds); + const textDocs = newIds.map((id: string) => textDocMap[id]).map(doc => doc as Doc); + for (let i = 0; i < textDocs.length; i++) { + let testDoc = textDocs[i]; + if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { + theDocs.push(Doc.GetProto(testDoc)); + theLines.push(newLines[i].map(line => line.replace(query, query.toUpperCase()))); + } + } + const docMap = await DocServer.GetRefFields(ids); - const docs = ids.map((id: string) => docMap[id]).filter((doc: any) => doc instanceof Doc && doc.type !== DocumentType.KVP); - return { docs, numFound, highlighting, lines }; + const docs = ids.map((id: string) => docMap[id]).map(doc => doc as Doc); + for (let i = 0; i < ids.length; i++) { + let testDoc = docs[i]; + if (testDoc instanceof Doc && testDoc.type !== DocumentType.KVP && theDocs.findIndex(d => Doc.AreProtosEqual(d, testDoc)) === -1) { + theDocs.push(testDoc); + theLines.push([]); + } + } + + return { docs: theDocs, numFound: theDocs.length, highlighting, lines: theLines }; } export async function GetAliasesOfDocument(doc: Doc): Promise; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 9347868b3..c37258f50 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -474,11 +474,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._searchReactionDisposer = reaction(() => { return StrCast(this.props.Document.search_string); }, searchString => { - const fieldkey = 'preview'; - let preview = false; - // if (!this._editorView && Object.keys(this.props.Document).indexOf(fieldkey) !== -1) { - // preview = true; - // } if (searchString) { this.highlightSearchTerms([searchString]); } diff --git a/src/client/views/pdf/Annotation.tsx b/src/client/views/pdf/Annotation.tsx index 26de12a0d..134e757d1 100644 --- a/src/client/views/pdf/Annotation.tsx +++ b/src/client/views/pdf/Annotation.tsx @@ -122,7 +122,7 @@ class RegionAnnotation extends React.Component { left: this.props.x, width: this.props.width, height: this.props.height, - transition: "background-color 0.5s, opacity 0.5s", + transition: "opacity 0.5s", opacity: this._brushed ? 0.5 : undefined, backgroundColor: this._brushed ? "orange" : StrCast(this.props.document.color) }} />); diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index 8027e93a3..a71e4f81e 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -11,10 +11,17 @@ // transform: scale(0.75); // transform-origin: top left; // } - // .textLayer { - // transform: scale(0.75); - // transform-origin: top left; - // } + .textLayer { + + mix-blend-mode: multiply; + opacity: 0.9; + } + .textLayer .highlight { + background-color: yellow; + } + .textLayer .highlight.selected { + background-color: orange; + } .page { position: relative; @@ -30,7 +37,6 @@ } .pdfViewer-overlay { - transform: scale(2.14359); transform-origin: left top; position: absolute; top: 0px; @@ -43,6 +49,7 @@ top: 0; width: 100%; pointer-events: none; + mix-blend-mode: multiply; .pdfPage-annotationBox { position: absolute; diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 20dfc4d8c..9ff3e1bd1 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -9,7 +9,7 @@ import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import smoothScroll, { Utils, emptyFunction, returnOne } from "../../../Utils"; +import smoothScroll, { Utils, emptyFunction, returnOne, intersectRect } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { CompiledScript, CompileScript } from "../../util/Scripting"; @@ -85,6 +85,7 @@ export class PDFViewer extends React.Component { private _selectionReactionDisposer?: IReactionDisposer; private _annotationReactionDisposer?: IReactionDisposer; private _filterReactionDisposer?: IReactionDisposer; + private _searchReactionDisposer?: IReactionDisposer; private _viewer: React.RefObject = React.createRef(); private _mainCont: React.RefObject = React.createRef(); private _selectionText: string = ""; @@ -103,12 +104,24 @@ export class PDFViewer extends React.Component { return this._annotations.filter(anno => this._script.run({ this: anno }, console.log, true).result); } + _lastSearch: string = ""; componentDidMount = async () => { // change the address to be the file address of the PNG version of each page // file address of the pdf this._coverPath = JSON.parse(await rp.get(Utils.prepend(`/thumbnail${this.props.url.substring("files/".length, this.props.url.length - ".pdf".length)}-${NumCast(this.props.Document.curPage, 1)}.PNG`))); runInAction(() => this._showWaiting = this._showCover = true); this.props.startupLive && this.setupPdfJsViewer(); + this._searchReactionDisposer = reaction(() => StrCast(this.props.Document.search_string), searchString => { + if (searchString) { + this.search(searchString, true); + this._lastSearch = searchString; + } + else { + setTimeout(() => this._lastSearch === "mxytzlaf" && this.search("mxytzlaf", true), 200); // bcz: how do we clear search highlights? + this._lastSearch && (this._lastSearch = "mxytzlaf"); + } + }, { fireImmediately: true }); + this._selectionReactionDisposer = reaction(() => this.props.isSelected(), () => (this.props.isSelected() && SelectionManager.SelectedDocuments().length === 1) && this.setupPdfJsViewer(), { fireImmediately: true }); this._reactionDisposer = reaction( () => this.props.Document.scrollY, @@ -130,6 +143,7 @@ export class PDFViewer extends React.Component { this._annotationReactionDisposer && this._annotationReactionDisposer(); this._filterReactionDisposer && this._filterReactionDisposer(); this._selectionReactionDisposer && this._selectionReactionDisposer(); + this._searchReactionDisposer && this._searchReactionDisposer(); document.removeEventListener("copy", this.copy); } @@ -298,12 +312,9 @@ export class PDFViewer extends React.Component { @action scrollToAnnotation = (scrollToAnnotation: Doc) => { - this.allAnnotations.forEach(d => Doc.UnBrushDoc(d)); - let windowHgt = this.props.PanelHeight() / this.props.ContentScaling(); - let scrollRange = this._mainCont.current!.scrollHeight - windowHgt; - let pgScroll = scrollRange / this._pageSizes.length; - this._mainCont.current!.scrollTo(0, NumCast(scrollToAnnotation.y) - pgScroll / 2); - Doc.BrushDoc(scrollToAnnotation); + let offset = this.visibleHeight() / 2 * 96 / 72; + this._mainCont.current && smoothScroll(500, this._mainCont.current, NumCast(scrollToAnnotation.y) - offset); + Doc.linkFollowHighlight(scrollToAnnotation); } sendAnnotations = (page: number) => { @@ -454,7 +465,8 @@ export class PDFViewer extends React.Component { if (rect/* && rect.width !== this._mainCont.current.getBoundingClientRect().width && rect.height !== this._mainCont.current.getBoundingClientRect().height / this.props.pdf.numPages*/) { let scaleY = this._mainCont.current.offsetHeight / boundingRect.height; let scaleX = this._mainCont.current.offsetWidth / boundingRect.width; - if (rect.width !== this._mainCont.current.clientWidth) { + if (rect.width !== this._mainCont.current.clientWidth && + (i == 0 || !intersectRect(clientRects[i], clientRects[i - 1]))) { let annoBox = document.createElement("div"); annoBox.className = "pdfPage-annotationBox"; // transforms the positions from screen onto the pdf div @@ -659,17 +671,18 @@ export class PDFViewer extends React.Component { 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.annotationLayer} NumCast(this.props.Document.scrollHeight, NumCast(this.props.Document.nativeHeight))} PanelWidth={() => this._pageSizes.length && this._pageSizes[0] ? this._pageSizes[0].width : NumCast(this.props.Document.nativeWidth)} - VisibleHeight={() => this.props.PanelHeight() / this.props.ContentScaling() * 72 / 96} + VisibleHeight={this.visibleHeight} focus={this.props.focus} isSelected={this.props.isSelected} select={emptyFunction} diff --git a/src/client/views/search/SearchItem.scss b/src/client/views/search/SearchItem.scss index 273d49349..62715c5eb 100644 --- a/src/client/views/search/SearchItem.scss +++ b/src/client/views/search/SearchItem.scss @@ -4,7 +4,6 @@ display: flex; flex-direction: row-reverse; justify-content: flex-end; - height: 70px; z-index: 0; } @@ -15,9 +14,12 @@ border-color: $intermediate-color; border-bottom-style: solid; padding: 10px; - height: 70px; + min-height: 70px; + max-height: 150px; + height: auto; z-index: 0; display: inline-block; + overflow: auto; .main-search-info { display: flex; @@ -26,6 +28,7 @@ .search-title-container { width: 100%; + overflow: hidden; .search-title { text-transform: uppercase; @@ -181,6 +184,12 @@ background: $lighter-alt-accent; } +.search-highlighting { + overflow: hidden; + text-overflow: ellipsis; + white-space: pre; +} + .searchBox-instances { float: left; opacity: 1; diff --git a/src/client/views/search/SearchItem.tsx b/src/client/views/search/SearchItem.tsx index 4d021216d..a7822ed46 100644 --- a/src/client/views/search/SearchItem.tsx +++ b/src/client/views/search/SearchItem.tsx @@ -4,13 +4,10 @@ import { faCaretUp, faChartBar, faFile, faFilePdf, faFilm, faFingerprint, faGlob import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, HeightSym, WidthSym } from "../../../new_fields/Doc"; +import { Doc } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; -import { ObjectField } from "../../../new_fields/ObjectField"; -import { RichTextField } from "../../../new_fields/RichTextField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { emptyFunction, returnEmptyString, returnFalse, returnOne, Utils } from "../../../Utils"; -import { DocServer } from "../../DocServer"; import { DocumentType } from "../../documents/DocumentTypes"; import { DocumentManager } from "../../util/DocumentManager"; import { DragManager, SetupDrag } from "../../util/DragManager"; @@ -228,6 +225,12 @@ export class SearchItem extends React.Component { @action pointerDown = (e: React.PointerEvent) => { e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); } + nextHighlight = (e: React.PointerEvent) => { + e.preventDefault(); e.button === 0 && SearchBox.Instance.openSearch(e); + let sstring = StrCast(this.props.doc.search_string); + this.props.doc.search_string = ""; + setTimeout(() => this.props.doc.search_string = sstring, 0); + } highlightDoc = (e: React.PointerEvent) => { if (this.props.doc.type === DocumentType.LINK) { if (this.props.doc.anchor1 && this.props.doc.anchor2) { @@ -240,6 +243,7 @@ export class SearchItem extends React.Component { } else { Doc.BrushDoc(this.props.doc); } + e.stopPropagation(); } unHighlightDoc = (e: React.PointerEvent) => { @@ -283,13 +287,14 @@ export class SearchItem extends React.Component { const doc2 = Cast(this.props.doc.anchor2, Doc); return (
-
+
{StrCast(this.props.doc.title)}
-
{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? "Text:" + this.props.lines[0] : ""}
+
{this.props.highlighting.length ? "Matched fields:" + this.props.highlighting.join(", ") : this.props.lines.length ? this.props.lines[0] : ""}
+ {this.props.lines.filter((m, i) => i).map((l, i) =>
`${l}`
)}
-- cgit v1.2.3-70-g09d2 From 48ac0b54cfe29b97a7add72b2369bfc2896f98f7 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 3 Oct 2019 17:07:33 -0400 Subject: partially fixed editing text boxes in stacking views. tweaked link following from text boxes. --- src/client/documents/Documents.ts | 3 +-- src/client/util/DocumentManager.ts | 4 ++-- src/client/views/MainOverlayTextBox.tsx | 3 ++- src/client/views/nodes/FormattedTextBox.tsx | 4 ++-- 4 files changed, 7 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2d323ea4b..71b9038d4 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -653,7 +653,7 @@ export namespace DocUtils { } }); } - export function MakeLink(source: {doc:Doc,ctx?:Doc}, target: {doc:Doc,ctx?:Doc}, title: string = "", description: string = "", id?: string, anchored1?: boolean) { + export function MakeLink(source: { doc: Doc, ctx?: Doc }, target: { doc: Doc, ctx?: Doc }, title: string = "", description: string = "", id?: string) { let sv = DocumentManager.Instance.getDocumentView(source.doc); if (sv && sv.props.ContainingCollectionDoc === target.doc) return; if (target.doc === CurrentUserUtils.UserDocument) return undefined; @@ -669,7 +669,6 @@ export namespace DocUtils { linkDocProto.anchor1 = source.doc; linkDocProto.anchor1Groups = new List([]); - linkDocProto.anchor1anchored = anchored1; linkDocProto.anchor2 = target.doc; linkDocProto.anchor2Groups = new List([]); diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 5130db131..ffd311665 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -188,8 +188,8 @@ export class DocumentManager { public async FollowLink(link: Doc | undefined, doc: Doc, focus: (doc: Doc, maxLocation: string) => void, zoom: boolean = false, reverse: boolean = false, currentContext?: Doc) { const linkDocs = link ? [link] : LinkManager.Instance.getAllRelatedLinks(doc); SelectionManager.DeselectAll(); - const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc) && !linkDoc.anchor1anchored); - const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc) && !linkDoc.anchor2anchored); + const firstDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor1 as Doc, doc)); + const secondDocs = linkDocs.filter(linkDoc => Doc.AreProtosEqual(linkDoc.anchor2 as Doc, doc)); const firstDocWithoutView = firstDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); const secondDocWithoutView = secondDocs.find(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); const first = firstDocWithoutView ? [firstDocWithoutView] : firstDocs; diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 335cc609f..73eef9478 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -78,7 +78,8 @@ export class MainOverlayTextBox extends React.Component this._textTargetDiv = div; this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave; if (div) { - this._textBottom = div.parentElement && getComputedStyle(div.parentElement).top !== "0px" ? true : false; + let parTop = div.parentElement && getComputedStyle(div.parentElement).top; + this._textBottom = parTop && parTop !== "0px" && parTop != "auto" ? true : false; this._textColor = (getComputedStyle(div) as any).color; div.style.color = "transparent"; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c37258f50..f84301c10 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -184,7 +184,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.dataDoc[key] = doc || Docs.Create.FreeformDocument([], { title: value, width: 500, height: 500 }, value); DocUtils.Publish(this.dataDoc[key] as Doc, value, this.props.addDocument, this.props.removeDocument); if (linkDoc) { (linkDoc as Doc).anchor2 = this.dataDoc[key] as Doc; } - else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id, true); + else DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: this.dataDoc[key] as Doc }, "Ref:" + value, "link to named target", id); }); }); }); @@ -307,7 +307,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); e.stopPropagation(); } - } else { + } else if (de.mods === "CtrlKey") { draggedDoc.isTemplate = true; if (typeof (draggedDoc.layout) === "string") { let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); -- cgit v1.2.3-70-g09d2 From 460cec4d786e026dabdb8fb873284f6574799367 Mon Sep 17 00:00:00 2001 From: bob Date: Thu, 3 Oct 2019 17:52:35 -0400 Subject: start of embedding documents in text notes. --- src/client/util/RichTextSchema.tsx | 143 ++++++++++++++++++++- src/client/views/DocumentButtonBar.tsx | 2 +- src/client/views/DocumentDecorations.tsx | 36 ------ .../views/collections/CollectionDockingView.tsx | 1 - src/client/views/nodes/FormattedTextBox.tsx | 24 +++- 5 files changed, 158 insertions(+), 48 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 066266873..7be773475 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -9,11 +9,16 @@ import { EditorView } from "prosemirror-view"; import { Doc } from "../../new_fields/Doc"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; import { DocServer } from "../DocServer"; -import { Cast, NumCast } from "../../new_fields/Types"; import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; -import { times } from "async"; -import { LinkManager } from "./LinkManager"; +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"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -158,6 +163,35 @@ export const nodes: { [index: string]: NodeSpec } = { } }, + dashDoc: { + inline: true, + attrs: { + width: { default: 200 }, + height: { default: 100 }, + title: { default: null }, + float: { default: "left" }, + location: { default: "onRight" }, + docid: { default: "" } + }, + group: "inline", + draggable: true, + // parseDOM: [{ + // tag: "img[src]", getAttrs(dom: any) { + // return { + // src: dom.getAttribute("src"), + // title: dom.getAttribute("title"), + // alt: dom.getAttribute("alt"), + // width: Math.min(100, Number(dom.getAttribute("width"))), + // }; + // } + // }], + // TODO if we don't define toDom, dragging the image crashes. Why? + toDOM(node) { + const attrs = { style: `width: ${node.attrs.width}, height: ${node.attrs.height}` }; + return ["div", { ...node.attrs, ...attrs }]; + } + }, + video: { inline: true, attrs: { @@ -671,6 +705,109 @@ export class ImageResizeView { } } +export class DashDocView { + _handle: HTMLElement; + _dashSpan: HTMLDivElement; + _outer: HTMLElement; + constructor(node: any, view: any, getPos: any, addDocTab: any) { + this._handle = document.createElement("span"); + this._dashSpan = document.createElement("div"); + this._outer = document.createElement("span"); + this._outer.style.position = "relative"; + this._outer.style.width = node.attrs.width; + this._outer.style.height = node.attrs.height; + this._outer.style.display = "inline-block"; + this._outer.style.overflow = "hidden"; + (this._outer.style as any).float = node.attrs.float; + + this._dashSpan.style.width = "100%"; + this._dashSpan.style.height = "100%"; + this._dashSpan.style.position = "absolute"; + this._dashSpan.style.display = "inline-block" + this._handle.style.position = "absolute"; + this._handle.style.width = "20px"; + this._handle.style.height = "20px"; + this._handle.style.backgroundColor = "blue"; + this._handle.style.borderRadius = "15px"; + this._handle.style.display = "none"; + this._handle.style.bottom = "-10px"; + this._handle.style.right = "-10px"; + DocServer.GetRefField(node.attrs.docid).then(async dashDoc => { + if (dashDoc instanceof Doc) { + let scale = () => 100 / NumCast(dashDoc.nativeWidth, 100); + ReactDOM.render( 100} + PanelHeight={() => 100} + focus={emptyFunction} + backgroundColor={returnEmptyString} + parentActive={returnFalse} + whenActiveChanged={returnFalse} + bringToFront={emptyFunction} + zoomToScale={emptyFunction} + getScale={returnOne} + ContainingCollectionView={undefined} + ContainingCollectionDoc={undefined} + ContentScaling={scale} + >, this._dashSpan); + } + }); + let self = this; + this._dashSpan.onpointerdown = function (e: any) { + }; + this._handle.onpointerdown = function (e: any) { + e.preventDefault(); + e.stopPropagation(); + const startX = e.pageX; + const startWidth = parseFloat(node.attrs.width); + const onpointermove = (e: any) => { + const currentX = e.pageX; + const diffInPx = currentX - startX; + self._outer.style.width = `${startWidth + diffInPx}`; + //Array.from(FormattedTextBox.InputBoxOverlay!.CurrentDiv.getElementsByTagName("img")).map((img: any) => img.opacity = "0.1"); + FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "0"; + }; + + const onpointerup = () => { + document.removeEventListener("pointermove", onpointermove); + document.removeEventListener("pointerup", onpointerup); + view.dispatch( + view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null, + { ...node.attrs, width: self._outer.style.width }) + ); + FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1"; + }; + + document.addEventListener("pointermove", onpointermove); + document.addEventListener("pointerup", onpointerup); + }; + + this._outer.appendChild(this._handle); + this._outer.appendChild(this._dashSpan); + (this as any).dom = this._outer; + } + + selectNode() { + this._dashSpan.classList.add("ProseMirror-selectednode"); + + this._handle.style.display = ""; + } + + deselectNode() { + this._dashSpan.classList.remove("ProseMirror-selectednode"); + + this._handle.style.display = "none"; + } +} + export class OrderedListView { update(node: any) { return false; // if attr's of an ordered_list (e.g., bulletStyle) change, return false forces the dom node to be recreated which is necessary for the bullet labels to update diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 338f6b83e..e57745b86 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -203,7 +203,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], considerEmbed = () => { let thisDoc = this.props.views[0].props.Document; let canEmbed = thisDoc.data && thisDoc.data instanceof URLField; - if (!canEmbed) return (null); + // if (!canEmbed) return (null); return (
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 9d42eb719..26ffaf3a6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -48,7 +48,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> private _resizeBorderWidth = 16; private _linkBoxHeight = 20 + 3; // link button height + margin private _titleHeight = 20; - private _embedButton = React.createRef(); private _downX = 0; private _downY = 0; private _iconDoc?: Doc = undefined; @@ -414,41 +413,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } - onEmbedButtonDown = (e: React.PointerEvent): void => { - e.stopPropagation(); - document.removeEventListener("pointermove", this.onEmbedButtonMoved); - document.addEventListener("pointermove", this.onEmbedButtonMoved); - document.removeEventListener("pointerup", this.onEmbedButtonUp); - document.addEventListener("pointerup", this.onEmbedButtonUp); - } - - - - onEmbedButtonUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onEmbedButtonMoved); - document.removeEventListener("pointerup", this.onEmbedButtonUp); - e.stopPropagation(); - } - - @action - onEmbedButtonMoved = (e: PointerEvent): void => { - if (this._embedButton.current !== null) { - document.removeEventListener("pointermove", this.onEmbedButtonMoved); - document.removeEventListener("pointerup", this.onEmbedButtonUp); - - let dragDocView = SelectionManager.SelectedDocuments()[0]; - let dragData = new DragManager.EmbedDragData(dragDocView.props.Document); - - DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } - e.stopPropagation(); - } - onPointerMove = (e: PointerEvent): void => { e.stopPropagation(); e.preventDefault(); diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 14e513157..fe805a980 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -31,7 +31,6 @@ import { SubCollectionViewProps } from "./CollectionSubView"; import React = require("react"); import { ButtonSelector } from './ParentDocumentSelector'; import { DocumentType } from '../../documents/DocumentTypes'; -import { compileFunction } from 'vm'; import { ComputedField } from '../../../new_fields/ScriptField'; library.add(faFile); const _global = (window /* browser */ || global /* node */) as any; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index f84301c10..819accf20 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -12,7 +12,7 @@ import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from " import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; import { DateField } from '../../../new_fields/DateField'; -import { Doc, DocListCastAsync, Opt, WidthSym } from "../../../new_fields/Doc"; +import { Doc, DocListCastAsync, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { Copy, Id } from '../../../new_fields/FieldSymbols'; import { RichTextField } from "../../../new_fields/RichTextField"; import { RichTextUtils } from '../../../new_fields/RichTextUtils'; @@ -28,7 +28,7 @@ import { DocumentManager } from '../../util/DocumentManager'; import { DragManager } from "../../util/DragManager"; import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { FootnoteView, ImageResizeView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema"; +import { FootnoteView, ImageResizeView, DashDocView, OrderedListView, schema, SummarizedView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; @@ -289,14 +289,23 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action drop = async (e: Event, de: DragManager.DropEvent) => { // We're dealing with a link to a document - if (de.data instanceof DragManager.EmbedDragData && de.data.urlField) { + if (de.data instanceof DragManager.EmbedDragData) { let target = de.data.embeddableSourceDoc; // We're dealing with an internal document drop - let url = de.data.urlField.url.href; - let model: NodeType = [".mov", ".mp4"].includes(url) ? schema.nodes.video : schema.nodes.image; + const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); + let node: Node; + 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] }) + } else { + node = schema.nodes.dashDoc.create({ + width: this.props.Document[WidthSym](), height: this.props.Document[HeightSym](), + title: "dashDoc", docid: target[Id] + }); + } let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); - let link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); - link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, model.create({ src: url, docid: link[Id] }))); + link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, node)); this.tryUpdateHeight(); e.stopPropagation(); } else if (de.data instanceof DragManager.DocumentDragData) { @@ -736,6 +745,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); }, 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(); }, -- cgit v1.2.3-70-g09d2 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 --- src/client/util/RichTextSchema.tsx | 4 +- src/client/views/InkingControl.tsx | 3 +- src/client/views/MainOverlayTextBox.scss | 29 ---- src/client/views/MainOverlayTextBox.tsx | 156 --------------------- src/client/views/MainView.tsx | 5 +- .../collectionFreeForm/CollectionFreeFormView.scss | 6 - .../collectionFreeForm/MarqueeView.scss | 7 + .../collections/collectionFreeForm/MarqueeView.tsx | 4 +- src/client/views/nodes/FormattedTextBox.scss | 7 +- src/client/views/nodes/FormattedTextBox.tsx | 52 ++----- 10 files changed, 31 insertions(+), 242 deletions(-) delete mode 100644 src/client/views/MainOverlayTextBox.scss delete mode 100644 src/client/views/MainOverlayTextBox.tsx (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 7be773475..12cf40b32 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -720,8 +720,8 @@ export class DashDocView { this._outer.style.overflow = "hidden"; (this._outer.style as any).float = node.attrs.float; - this._dashSpan.style.width = "100%"; - this._dashSpan.style.height = "100%"; + 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._handle.style.position = "absolute"; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index a10df0e75..ee8b77050 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -10,7 +10,6 @@ import { InkTool } from "../../new_fields/InkField"; import { Doc } from "../../new_fields/Doc"; import { undoBatch, UndoManager } from "../util/UndoManager"; import { StrCast, NumCast, Cast } from "../../new_fields/Types"; -import { MainOverlayTextBox } from "./MainOverlayTextBox"; import { listSpec } from "../../new_fields/Schema"; import { List } from "../../new_fields/List"; import { Utils } from "../../Utils"; @@ -46,7 +45,7 @@ export class InkingControl extends React.Component { switchColor = action((color: ColorResult): void => { this._selectedColor = color.hex + (color.rgb.a !== undefined ? this.decimalToHexString(Math.round(color.rgb.a * 255)) : "ff"); if (InkingControl.Instance.selectedTool === InkTool.None) { - if (MainOverlayTextBox.Instance.SetColor(color.hex)) return; + // if (MainOverlayTextBox.Instance.SetColor(color.hex)) return; let selected = SelectionManager.SelectedDocuments(); let oldColors = selected.map(view => { let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss deleted file mode 100644 index c9d44e194..000000000 --- a/src/client/views/MainOverlayTextBox.scss +++ /dev/null @@ -1,29 +0,0 @@ -@import "globalCssVariables"; - -.mainOverlayTextBox-textInput { - background-color: rgba(248, 6, 6, 0.001); - width: 400px; - height: 200px; - position: absolute; - overflow: visible; - top: 0; - left: 0; - pointer-events: none; - z-index: $mainTextInput-zindex; - - .formattedTextBox-cont { - background-color: rgba(248, 6, 6, 0.001); - width: 100%; - height: 100%; - position: absolute; - top: 0; - left: 0; - } -} - -.mainOverlayTextBox-unscaled_div { - // width: 0px; - z-index: 10000; - position: absolute; - pointer-events: none; -} \ No newline at end of file diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx deleted file mode 100644 index 73eef9478..000000000 --- a/src/client/views/MainOverlayTextBox.tsx +++ /dev/null @@ -1,156 +0,0 @@ -import { action, observable, reaction, trace } from 'mobx'; -import { observer } from 'mobx-react'; -import "normalize.css"; -import * as React from 'react'; -import { Doc, DocListCast, Opt } from '../../new_fields/Doc'; -import { BoolCast } from '../../new_fields/Types'; -import { emptyFunction, returnTrue, returnZero, Utils, returnOne } from '../../Utils'; -import { DragManager } from '../util/DragManager'; -import { Transform } from '../util/Transform'; -import { CollectionDockingView } from './collections/CollectionDockingView'; -import "./MainOverlayTextBox.scss"; -import { FormattedTextBox } from './nodes/FormattedTextBox'; - -interface MainOverlayTextBoxProps { - firstinstance?: boolean; -} - -@observer -export class MainOverlayTextBox extends React.Component { - public static Instance: MainOverlayTextBox; - @observable _textXf: () => Transform = () => Transform.Identity(); - public TextFieldKey: string = "data"; - private _textColor: string | null = null; - private _textHideOnLeave?: boolean; - private _textTargetDiv: HTMLDivElement | undefined; - private _textProxyDiv: React.RefObject; - private _textBottom: boolean | undefined; - private _setouterdiv = (outerdiv: HTMLElement | null) => { this._outerdiv = outerdiv; this.updateTooltip(); }; - private _outerdiv: HTMLElement | null = null; - private _textBox: FormattedTextBox | undefined; - private _tooltip?: HTMLElement; - ChromeHeight?: () => number; - @observable public TextDoc?: Doc; - @observable public TextDataDoc?: Doc; - - updateTooltip = () => { - this._outerdiv && this._tooltip && !this._outerdiv.contains(this._tooltip) && this._outerdiv.appendChild(this._tooltip); - } - - public SetColor(color: string) { - return this._textBox && this._textBox.setFontColor(color); - } - - constructor(props: MainOverlayTextBoxProps) { - super(props); - this._textProxyDiv = React.createRef(); - MainOverlayTextBox.Instance = this; - reaction(() => FormattedTextBox.InputBoxOverlay, - (box?: FormattedTextBox) => { - this._textBox = box; - if (box) { - this.ChromeHeight = box.props.ChromeHeight; - this.TextDoc = box.props.Document; - this.TextDataDoc = box.props.DataDoc; - let xf = () => { - box.props.ScreenToLocalTransform(); - let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined); - return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); - }; - this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight) || box.props.height === "min-content"); - } - else { - this.TextDoc = undefined; - this.TextDataDoc = undefined; - this.setTextDoc(); - } - }); - } - - @action - private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform, autoHeight?: boolean) { - if (this._textTargetDiv) { - this._textTargetDiv.style.color = this._textColor; - } - this.TextFieldKey = textFieldKey!; - let txf = tx ? tx : () => Transform.Identity(); - this._textXf = txf; - this._textTargetDiv = div; - this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave; - if (div) { - let parTop = div.parentElement && getComputedStyle(div.parentElement).top; - this._textBottom = parTop && parTop !== "0px" && parTop != "auto" ? true : false; - this._textColor = (getComputedStyle(div) as any).color; - div.style.color = "transparent"; - } - } - - @action - textScroll = (e: React.UIEvent) => { - if (this._textProxyDiv.current && this._textTargetDiv) { - this._textTargetDiv.scrollTop = (e as any)._targetInst.stateNode.scrollTop; - } - } - - textBoxDown = (e: React.PointerEvent) => { - if (e.button !== 0 || e.metaKey || e.altKey) { - document.addEventListener("pointermove", this.textBoxMove); - document.addEventListener('pointerup', this.textBoxUp); - } - } - @action - textBoxMove = (e: PointerEvent) => { - if ((e.movementX > 1 || e.movementY > 1) && FormattedTextBox.InputBoxOverlay) { - document.removeEventListener("pointermove", this.textBoxMove); - document.removeEventListener('pointerup', this.textBoxUp); - let dragData = new DragManager.DocumentDragData([FormattedTextBox.InputBoxOverlay.props.Document]); - const [left, top] = this._textXf().inverse().transformPoint(0, 0); - dragData.offset = [e.clientX - left, e.clientY - top]; - DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } - } - textBoxUp = (e: PointerEvent) => { - document.removeEventListener("pointermove", this.textBoxMove); - document.removeEventListener('pointerup', this.textBoxUp); - } - - addDocTab = (doc: Doc, dataDoc: Opt, location: string) => { - return this._textBox && this._textBox.props.addDocTab(doc, dataDoc, location) ? true : false; - } - render() { - this.TextDoc; this.TextDataDoc; - if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) { - let wid = FormattedTextBox.InputBoxOverlay.props.Document.width; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations) - let hgtx = FormattedTextBox.InputBoxOverlay.props.Document.height; // need to force overlay to render when underlying text box is resized (eg, w/ DocDecorations) - let textRect = this._textTargetDiv.getBoundingClientRect(); - let s = this._textXf().Scale; - let location = this._textBottom ? textRect.bottom : textRect.top; - let hgt = (this._textBox && this._textBox.props.Document.autoHeight) || this._textBottom ? "auto" : this._textTargetDiv.clientHeight; - return
-
-
-
- { this._tooltip = tooltip; this.updateTooltip(); }} /> -
-
-
- ; - } - else return (null); - } -} \ No newline at end of file diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 660b42290..545f99a41 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -34,14 +34,12 @@ import { DocumentDecorations } from './DocumentDecorations'; import KeyManager from './GlobalKeyHandler'; import { InkingControl } from './InkingControl'; import "./Main.scss"; -import { MainOverlayTextBox } from './MainOverlayTextBox'; import MainViewModal from './MainViewModal'; import { DocumentView } from './nodes/DocumentView'; -import { PresBox } from './nodes/PresBox'; -import { OverlayView } from './OverlayView'; import PDFMenu from './pdf/PDFMenu'; import { PreviewCursor } from './PreviewCursor'; import { FilterBox } from './search/FilterBox'; +import { OverlayView } from './OverlayView'; @observer export class MainView extends React.Component { @@ -687,7 +685,6 @@ export class MainView extends React.Component { {this.nodesMenu()} {this.miscButtons} -
); 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} diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 45e516015..541c29faa 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -28,7 +28,7 @@ } .formattedTextBox-cont-hidden { - pointer-events: none; + // pointer-events: none; } .formattedTextBox-inner-rounded { @@ -40,9 +40,10 @@ left: 10%; } -.formattedTextBox-inner-rounded div, -.formattedTextBox-inner div { +.formattedTextBox-inner-rounded , +.formattedTextBox-inner { padding: 10px 10px; + height: 100%; } .menuicon { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 819accf20..13eb78f48 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -46,7 +46,6 @@ library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); export interface FormattedTextBoxProps { - isOverlay?: boolean; hideOnLeave?: boolean; height?: string; color?: string; @@ -95,9 +94,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @observable private _fontFamily = "Arial"; @observable private _fontAlign = ""; @observable private _entered = false; - @observable public static InputBoxOverlay?: FormattedTextBox = undefined; public static SelectOnLoad = ""; - public static InputBoxOverlayScroll: number = 0; public static IsFragment(html: string) { return html.indexOf("data-pm-slice") !== -1; } @@ -137,9 +134,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe constructor(props: FieldViewProps) { super(props); - if (this.props.isOverlay) { - DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined); - } FormattedTextBox.Instance = this; } @@ -300,8 +294,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe node = model.create({ src: url, docid: link[Id] }) } else { node = schema.nodes.dashDoc.create({ - width: this.props.Document[WidthSym](), height: this.props.Document[HeightSym](), - title: "dashDoc", docid: target[Id] + width: target[WidthSym](), height: target[HeightSym](), + title: "dashDoc", docid: target[Id], + float: "none" }); } let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); @@ -390,7 +385,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe (schema as any).Document = this.props.Document; return { schema, - plugins: this.props.isOverlay ? [ + plugins: [ inputRules(inpRules), this.tooltipTextMenuPlugin(), history(), @@ -403,26 +398,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } }), formattedTextBoxCommentPlugin - ] : [ - history(), - keymap(this._keymap), - keymap(baseKeymap), - ] + ] }; } componentDidMount() { - - if (!this.props.isOverlay) { - this._proxyReactionDisposer = reaction(() => this.props.isSelected(), - () => { - if (this.props.isSelected()) { - FormattedTextBox.InputBoxOverlay = this; - FormattedTextBox.InputBoxOverlayScroll = this._ref.current!.scrollTop; - } - }, { fireImmediately: true }); - } - this.pullFromGoogleDoc(this.checkState); this.dataDoc[GoogleRef] && this.dataDoc.unchanged && runInAction(() => DocumentDecorations.Instance.isAnimatingFetch = true); @@ -551,7 +531,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let editor = this._editorView; let ret = findLinkFrag(editor.state.doc.content, editor); - if (ret.frag.size > 2 && ((!this.props.isOverlay && !this.props.isSelected()) || (this.props.isSelected() && this.props.isOverlay))) { + if (ret.frag.size > 2) { let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected @@ -754,7 +734,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe clipboardTextSerializer: this.clipboardTextSerializer, handlePaste: this.handlePaste, }); - (this._editorView as any).isOverlay = this.props.isOverlay; if (startup) { Doc.GetProto(doc).documentText = undefined; this._editorView.dispatch(this._editorView.state.tr.insertText(startup)); @@ -766,7 +745,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox.SelectOnLoad = ""; this.props.select(false); } - else if (this.props.isOverlay) this._editorView!.focus(); + this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; } @@ -824,13 +803,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe document.removeEventListener("keypress", this.recordKeyHandler); document.addEventListener("keypress", this.recordKeyHandler); this.tryUpdateHeight(); - if (!this.props.isOverlay) { - FormattedTextBox.InputBoxOverlay = this; - } else { - if (this._ref.current) { - this._ref.current.scrollTop = FormattedTextBox.InputBoxOverlayScroll; - } - } } onPointerWheel = (e: React.WheelEvent): void => { // if a text note is not selected and scrollable, this prevents us from being able to scroll and zoom out at the same time @@ -901,7 +873,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } } - this._proseRef!.focus(); + let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + if (pos && pos.pos > 0) { + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new TextSelection(this._editorView!.state.doc.resolve(pos.pos)))); + } + this._editorView!.focus(); if (this._linkClicked) { this._linkClicked = ""; e.preventDefault(); @@ -959,7 +935,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe tryUpdateHeight() { const ChromeHeight = this.props.ChromeHeight; let sh = this._ref.current ? this._ref.current.scrollHeight : 0; - if (!this.props.isOverlay && !this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0) { + if (!this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0) { let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.props.Document.height, 0); this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0)); @@ -968,7 +944,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } render() { - let style = this.props.isOverlay ? "scroll" : "hidden"; + let style = "hidden"; let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground ? "none" : "all"; -- cgit v1.2.3-70-g09d2 From 850ab4b13f3402464b7ff9a89261bc278031f961 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 4 Oct 2019 12:49:42 -0400 Subject: fixes for embedded text boxes. --- src/client/util/RichTextSchema.tsx | 60 ++++++++++++++++++----------- src/client/views/nodes/FormattedTextBox.tsx | 20 ++++++---- 2 files changed, 51 insertions(+), 29 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 12cf40b32..e6b456313 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -3,7 +3,7 @@ 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 } from "prosemirror-state"; +import { EditorState, TextSelection, NodeSelection } from "prosemirror-state"; import { StepMap } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; import { Doc } from "../../new_fields/Doc"; @@ -635,6 +635,7 @@ export class ImageResizeView { this._outer = document.createElement("span"); this._outer.style.position = "relative"; this._outer.style.width = node.attrs.width; + this._outer.style.height = node.attrs.height; this._outer.style.display = "inline-block"; this._outer.style.overflow = "hidden"; (this._outer.style as any).float = node.attrs.float; @@ -650,8 +651,15 @@ export class ImageResizeView { this._handle.style.bottom = "-10px"; this._handle.style.right = "-10px"; let self = this; + 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)))); + } this._img.onpointerdown = function (e: any) { - if (!view.isOverlay || e.ctrlKey) { + if (e.ctrlKey) { e.preventDefault(); e.stopPropagation(); DocServer.GetRefField(node.attrs.docid).then(async linkDoc => @@ -663,32 +671,31 @@ export class ImageResizeView { this._handle.onpointerdown = function (e: any) { e.preventDefault(); e.stopPropagation(); + let wid = Number(getComputedStyle(self._img).width!.replace(/px/, "")); + let hgt = Number(getComputedStyle(self._img).height!.replace(/px/, "")); const startX = e.pageX; const startWidth = parseFloat(node.attrs.width); const onpointermove = (e: any) => { const currentX = e.pageX; const diffInPx = currentX - startX; self._outer.style.width = `${startWidth + diffInPx}`; - //Array.from(FormattedTextBox.InputBoxOverlay!.CurrentDiv.getElementsByTagName("img")).map((img: any) => img.opacity = "0.1"); - FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "0"; + self._outer.style.height = `${(startWidth + diffInPx) * hgt / wid}`; }; const onpointerup = () => { document.removeEventListener("pointermove", onpointermove); document.removeEventListener("pointerup", onpointerup); - view.dispatch( - view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null, - { ...node.attrs, width: self._outer.style.width }) - ); - FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1"; + 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); document.addEventListener("pointerup", onpointerup); }; - this._outer.appendChild(this._handle); this._outer.appendChild(this._img); + this._outer.appendChild(this._handle); (this as any).dom = this._outer; } @@ -761,37 +768,46 @@ export class DashDocView { } }); let self = this; - this._dashSpan.onpointerdown = function (e: any) { - }; + this._dashSpan.onclick = function (e: any) { + FormattedTextBox.firstTarget && FormattedTextBox.firstTarget(); + } + 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(); const startX = e.pageX; + const startY = e.pageY; const startWidth = parseFloat(node.attrs.width); + const startHeight = parseFloat(node.attrs.height); const onpointermove = (e: any) => { - const currentX = e.pageX; - const diffInPx = currentX - startX; + const diffInPx = e.pageX - startX; + const diffInPy = e.pageY - startY; self._outer.style.width = `${startWidth + diffInPx}`; - //Array.from(FormattedTextBox.InputBoxOverlay!.CurrentDiv.getElementsByTagName("img")).map((img: any) => img.opacity = "0.1"); - FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "0"; + self._outer.style.height = `${startHeight + diffInPy}`; }; const onpointerup = () => { document.removeEventListener("pointermove", onpointermove); document.removeEventListener("pointerup", onpointerup); - view.dispatch( - view.state.tr.setSelection(view.state.selection).setNodeMarkup(getPos(), null, - { ...node.attrs, width: self._outer.style.width }) - ); - FormattedTextBox.InputBoxOverlay!.CurrentDiv.style.opacity = "1"; + 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); document.addEventListener("pointerup", onpointerup); }; - this._outer.appendChild(this._handle); this._outer.appendChild(this._dashSpan); + this._outer.appendChild(this._handle); (this as any).dom = this._outer; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 13eb78f48..05904e1e7 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -774,9 +774,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._searchReactionDisposer && this._searchReactionDisposer(); this._editorView && this._editorView.destroy(); } - - + public static firstTarget: () => void; onPointerDown = (e: React.PointerEvent): void => { + if ((e.nativeEvent as any).formattedHandled) 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) { @@ -785,10 +786,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); } - let ctrlKey = e.ctrlKey; 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 => { @@ -873,10 +883,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } } - let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); - if (pos && pos.pos > 0) { - this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new TextSelection(this._editorView!.state.doc.resolve(pos.pos)))); - } this._editorView!.focus(); if (this._linkClicked) { this._linkClicked = ""; -- cgit v1.2.3-70-g09d2 From 54f2067dbadb66e22249c1572bdc5d6d097f41d1 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 4 Oct 2019 17:01:09 -0400 Subject: restored tooltiptextmenu --- src/client/util/RichTextRules.ts | 8 +++-- src/client/util/RichTextSchema.tsx | 1 - src/client/util/SelectionManager.ts | 2 -- src/client/util/TooltipTextMenu.tsx | 45 +++++++++++++++-------------- src/client/views/DocumentButtonBar.tsx | 1 - src/client/views/DocumentDecorations.tsx | 4 +-- src/client/views/nodes/FormattedTextBox.tsx | 35 +++++----------------- 7 files changed, 40 insertions(+), 56 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index cd37ea0bb..2d5ec1743 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -92,8 +92,10 @@ export const inpRules = { let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "left"; + return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; } - return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; }), new InputRule( new RegExp(/^\]\]\s$/), @@ -104,8 +106,10 @@ export const inpRules = { let heading = NumCast(FormattedTextBox.InputBoxOverlay!.props.Document.heading); if (ruleProvider && heading) { ruleProvider["ruleAlign_" + heading] = "right"; + return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; } - return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; }), new InputRule( new RegExp(/\^f\s$/), diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 53eaf9ce2..948a3c5bd 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -864,7 +864,6 @@ export class FootnoteView { if (this.innerView) this.close(); } open() { - if (!this.outerView.isOverlay) return; // Append a tooltip to the outer node let tooltip = this.dom.appendChild(document.createElement("div")); tooltip.className = "footnote-tooltip"; 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/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index a83a3949d..9116ef995 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -10,7 +10,6 @@ import { Doc, Field, Opt } from "../../new_fields/Doc"; import { Id } from "../../new_fields/FieldSymbols"; import { Utils } from "../../Utils"; import { DocServer } from "../DocServer"; -import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { FieldViewProps } from "../views/nodes/FieldView"; import { FormattedTextBoxProps } from "../views/nodes/FormattedTextBox"; import { DocumentManager } from "./DocumentManager"; @@ -28,10 +27,10 @@ export class TooltipTextMenu { public tooltip: HTMLElement; private view: EditorView; + private editorProps: FieldViewProps & FormattedTextBoxProps | undefined; private fontStyles: MarkType[]; private fontSizes: MarkType[]; private listTypes: (NodeType | any)[]; - private editorProps: FieldViewProps & FormattedTextBoxProps; private fontSizeToNum: Map; private fontStylesToName: Map; private listTypeToIcon: Map; @@ -59,9 +58,8 @@ export class TooltipTextMenu { private _collapsed: boolean = false; - constructor(view: EditorView, editorProps: FieldViewProps & FormattedTextBoxProps) { + constructor(view: EditorView) { this.view = view; - this.editorProps = editorProps; this.wrapper = document.createElement("div"); this.tooltip = document.createElement("div"); @@ -120,10 +118,10 @@ export class TooltipTextMenu { //pointer down handler to activate button effects dom.addEventListener("pointerdown", e => { e.preventDefault(); - view.focus(); + this.view.focus(); if (dom.contains(e.target as Node)) { e.stopPropagation(); - command(view.state, view.dispatch, view); + command(this.view.state, this.view.dispatch, this.view); // if (this.view.state.selection.empty) { // if (dom.style.color === "white") { dom.style.color = "greenyellow"; } // else { dom.style.color = "white"; } @@ -188,12 +186,10 @@ export class TooltipTextMenu { this.updateListItemDropdown(":", this.listTypeBtnDom); - this.update(view, undefined); - - // add tooltip to outerdiv to circumvent scaling problem - const outer_div = this.editorProps.outer_div; - outer_div && outer_div(this.wrapper); + this.update(view, undefined, undefined); + TooltipTextMenu.Toolbar = this.wrapper; } + public static Toolbar: HTMLDivElement | undefined; //label of dropdown will change to given label updateFontSizeDropdown(label: string) { @@ -275,7 +271,7 @@ export class TooltipTextMenu { if (DocumentManager.Instance.getDocumentView(f)) { DocumentManager.Instance.getDocumentView(f)!.props.focus(f, false); } - else this.editorProps.addDocTab(f, undefined, "onRight"); + else this.editorProps && this.editorProps.addDocTab(f, undefined, "onRight"); } })); } @@ -293,6 +289,7 @@ export class TooltipTextMenu { this.linkDrag.style.background = "black"; this.linkDrag.style.cssFloat = "left"; this.linkDrag.onpointerdown = (e: PointerEvent) => { + if (!this.editorProps) return; let dragData = new DragManager.LinkDragData(this.editorProps.Document); dragData.dontClearTextBox = true; // hack to get source context -sy @@ -503,19 +500,23 @@ export class TooltipTextMenu { if (markType.name[0] === 'p') { let size = this.fontSizeToNum.get(markType); if (size) { this.updateFontSizeDropdown(String(size) + " pt"); } - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleSize_" + heading] = size; + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleSize_" + heading] = size; + } } } else { let fontName = this.fontStylesToName.get(markType); if (fontName) { this.updateFontStyleDropdown(fontName); } - let ruleProvider = this.editorProps.ruleProvider; - let heading = NumCast(this.editorProps.Document.heading); - if (ruleProvider && heading) { - ruleProvider["ruleFont_" + heading] = fontName; + if (this.editorProps) { + let ruleProvider = this.editorProps.ruleProvider; + let heading = NumCast(this.editorProps.Document.heading); + if (ruleProvider && heading) { + ruleProvider["ruleFont_" + heading] = fontName; + } } } //actually apply font @@ -849,8 +850,10 @@ export class TooltipTextMenu { } //updates the tooltip menu when the selection changes - update(view: EditorView, lastState: EditorState | undefined) { + update(view: EditorView, lastState: EditorState | undefined, props: any) { + this.view = view; let state = view.state; + props && (this.editorProps = props); // Don't do anything if the document/selection didn't change if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; 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..b7ec27957 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -24,6 +24,7 @@ import { FieldView } from "./nodes/FieldView"; import { FormattedTextBox } from "./nodes/FormattedTextBox"; import { IconBox } from "./nodes/IconBox"; import React = require("react"); +import { TooltipTextMenu } from '../util/TooltipTextMenu'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -466,7 +467,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); @@ -578,7 +578,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0, }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
-
r && TooltipTextMenu.Toolbar && Array.from(r.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && r.appendChild(TooltipTextMenu.Toolbar)} style={{ width: (bounds.r - bounds.x + this._resizeBorderWidth) + "px", height: (bounds.b - bounds.y + this._resizeBorderWidth + this._linkBoxHeight + this._titleHeight + 3) + "px", left: bounds.x - this._resizeBorderWidth / 2, diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 05904e1e7..cbdb0503b 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -116,8 +116,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return ""; } - public static getToolTip() { - return this._toolTipTextMenu; + public static getToolTip(ev: EditorView) { + return this._toolTipTextMenu ? this._toolTipTextMenu : this._toolTipTextMenu = new TooltipTextMenu(ev, undefined); } @undoBatch @@ -143,29 +143,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? this.props.DataDoc : Doc.GetProto(this.props.Document); } - // this should be internal to prosemirror, but is needed - // here to make sure that footnote view nodes in the overlay editor - // get removed when they're not selected. - - syncNodeSelection(view: any, sel: any) { - if (sel instanceof NodeSelection) { - var desc = view.docView.descAt(sel.from); - if (desc !== view.lastSelectedViewDesc) { - if (view.lastSelectedViewDesc) { - view.lastSelectedViewDesc.deselectNode(); - view.lastSelectedViewDesc = null; - } - if (desc) { desc.selectNode(); } - view.lastSelectedViewDesc = desc; - } - } else { - if (view.lastSelectedViewDesc) { - view.lastSelectedViewDesc.deselectNode(); - view.lastSelectedViewDesc = null; - } - } - } - linkOnDeselect: Map = new Map(); doLinkOnDeselect() { @@ -211,7 +188,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } const state = this._editorView.state.apply(tx); this._editorView.updateState(state); - this.syncNodeSelection(this._editorView, this._editorView.state.selection); // bcz: ugh -- shouldn't be needed but without this the overlay view's footnote popup doesn't get deselected if (state.selection.empty && FormattedTextBox._toolTipTextMenu && tx.storedMarks) { FormattedTextBox._toolTipTextMenu.mark_key_pressed(tx.storedMarks); } @@ -808,8 +784,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + static InputBoxOverlay: FormattedTextBox | undefined; @action onFocused = (e: React.FocusEvent): void => { + FormattedTextBox.InputBoxOverlay = this; document.removeEventListener("keypress", this.recordKeyHandler); document.addEventListener("keypress", this.recordKeyHandler); this.tryUpdateHeight(); @@ -901,7 +879,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let self = FormattedTextBox; return new Plugin({ view(_editorView) { - return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops); + return self._toolTipTextMenu = FormattedTextBox.getToolTip(_editorView); } }); } @@ -955,6 +933,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); + if (this.props.isSelected()) { + FormattedTextBox._toolTipTextMenu!.update(this._editorView!, undefined, this.props); + } return (
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/nodes/FormattedTextBox.tsx') 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 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/nodes/FormattedTextBox.tsx') 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/nodes/FormattedTextBox.tsx') 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/nodes/FormattedTextBox.tsx') 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 463cf0c4a6225fe01492510238973eabc1577fd5 Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 7 Oct 2019 14:48:54 -0400 Subject: added deleting ndested nodes from text boxes --- src/client/util/RichTextSchema.tsx | 72 ++++++----------------------- src/client/views/nodes/FormattedTextBox.tsx | 21 +++------ 2 files changed, 21 insertions(+), 72 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 29ec5b23c..432e0c2fb 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -16,7 +16,7 @@ import { DocumentManager } from "./DocumentManager"; import ParagraphNodeSpec from "./ParagraphNodeSpec"; import { Transform } from "./Transform"; import React = require("react"); -import { BoolCast } from "../../new_fields/Types"; +import { BoolCast, NumCast } from "../../new_fields/Types"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], @@ -712,7 +712,6 @@ export class ImageResizeView { } export class DashDocView { - _handle: HTMLElement; _dashSpan: HTMLDivElement; _outer: HTMLElement; _dashDoc: Doc | undefined; @@ -721,11 +720,11 @@ export class DashDocView { getDocTransform = () => { let { scale, translateX, translateY } = Utils.GetScreenTransform(this._outer); - return new Transform(-translateX, -translateY, 1).scale(1 / scale); + return new Transform(-translateX, -translateY, 1).scale(1 / this.contentScaling() / scale); } + contentScaling = () => NumCast(this._dashDoc!.nativeWidth) > 0 && !this._dashDoc!.ignoreAspect ? this._dashDoc![WidthSym]() / NumCast(this._dashDoc!.nativeWidth) : 1; 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"); this._outer.style.position = "relative"; @@ -739,14 +738,11 @@ export class DashDocView { this._dashSpan.style.height = node.attrs.height; this._dashSpan.style.position = "absolute"; this._dashSpan.style.display = "inline-block"; - this._handle.style.position = "absolute"; - this._handle.style.width = "20px"; - this._handle.style.height = "20px"; - this._handle.style.backgroundColor = "blue"; - this._handle.style.borderRadius = "15px"; - this._handle.style.display = "none"; - this._handle.style.bottom = "-10px"; - this._handle.style.right = "-10px"; + let removeDoc = () => { + let ns = new NodeSelection(view.state.doc.resolve(getPos())); + view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); + return true; + } DocServer.GetRefField(node.attrs.docid).then(async dashDoc => { if (dashDoc instanceof Doc) { self._dashDoc = dashDoc; @@ -754,16 +750,15 @@ export class DashDocView { 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._reactionDisposer = reaction(() => dashDoc[HeightSym]() + dashDoc[WidthSym](), () => { this._dashSpan.style.height = this._outer.style.height = dashDoc[HeightSym]() + "px"; + this._dashSpan.style.width = this._outer.style.width = dashDoc[WidthSym]() + "px"; }); ReactDOM.render(, this._dashSpan); } }); let self = this; - 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(); - e.stopPropagation(); - const startX = e.pageX; - const startY = e.pageY; - const startWidth = parseFloat(node.attrs.width); - const startHeight = parseFloat(node.attrs.height); - 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); - view.dispatch(view.state.tr.setNodeMarkup(getPos(), null, { ...node.attrs, width: self._outer.style.width, height: self._outer.style.height })); - }; - - document.addEventListener("pointermove", onpointermove); - document.addEventListener("pointerup", onpointerup); - }; - this._outer.appendChild(this._dashSpan); - this._outer.appendChild(this._handle); (this as any).dom = this._outer; } - - selectNode() { - this._dashSpan.classList.add("ProseMirror-selectednode"); - - this._handle.style.display = ""; - } - - deselectNode() { - this._dashSpan.classList.remove("ProseMirror-selectednode"); - - this._handle.style.display = "none"; + destroy() { + this._reactionDisposer && this._reactionDisposer(); } } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index c030d4bce..966f183b0 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -262,20 +262,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let target = de.data.embeddableSourceDoc; // We're dealing with an internal document drop const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); - let node: Node; - 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] }); - } else { - let alias = Doc.MakeAlias(target); - alias.fitToBox = true; - node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: alias[Id], - float: "none" - }); - } + let alias = Doc.MakeAlias(target); + alias.fitToBox = true; + let node = schema.nodes.dashDoc.create({ + width: target[WidthSym](), height: target[HeightSym](), + title: "dashDoc", docid: alias[Id], + float: "none" + }); let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, node)); this.tryUpdateHeight(); -- cgit v1.2.3-70-g09d2 From 2485a9f0f32968040a79f757118dfb6fad8930fd Mon Sep 17 00:00:00 2001 From: bob Date: Mon, 7 Oct 2019 17:13:48 -0400 Subject: fixes for toolbar and text footnote/comments. --- src/client/util/RichTextSchema.tsx | 2 +- src/client/util/TooltipTextMenu.tsx | 8 +++++--- src/client/views/DocumentDecorations.tsx | 9 ++++++++- src/client/views/nodes/FormattedTextBox.tsx | 9 +++++---- src/client/views/nodes/FormattedTextBoxComment.tsx | 10 ++++++---- 5 files changed, 25 insertions(+), 13 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 432e0c2fb..06b97fa68 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -168,7 +168,7 @@ export const nodes: { [index: string]: NodeSpec } = { width: { default: 200 }, height: { default: 100 }, title: { default: null }, - float: { default: "left" }, + float: { default: "right" }, location: { default: "onRight" }, docid: { default: "" } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 55c6e6609..2732b708d 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -19,6 +19,7 @@ import { schema } from "./RichTextSchema"; import "./TooltipTextMenu.scss"; import { Cast, NumCast } from '../../new_fields/Types'; import { updateBullets } from './ProsemirrorExampleTransfer'; +import { DocumentDecorations } from '../views/DocumentDecorations'; const { toggleMark, setBlockType } = require("prosemirror-commands"); const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); @@ -186,7 +187,7 @@ export class TooltipTextMenu { this.updateListItemDropdown(":", this.listTypeBtnDom); - this.updateInternal(view, undefined, undefined); + this.updateFromDash(view, undefined, undefined); TooltipTextMenu.Toolbar = this.wrapper; } public static Toolbar: HTMLDivElement | undefined; @@ -849,11 +850,12 @@ export class TooltipTextMenu { } } - update(view: EditorView, lastState: EditorState | undefined) { this.updateInternal(view, lastState, this.editorProps) } + update(view: EditorView, lastState: EditorState | undefined) { this.updateFromDash(view, lastState, this.editorProps) } //updates the tooltip menu when the selection changes - private updateInternal(view: EditorView, lastState: EditorState | undefined, props: any) { + public updateFromDash(view: EditorView, lastState: EditorState | undefined, props: any) { this.view = view; let state = view.state; + DocumentDecorations.Instance.setTextBar(DocumentDecorations.Instance.TextBar); props && (this.editorProps = props); // Don't do anything if the document/selection didn't change if (lastState && lastState.doc.eq(state.doc) && diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index b7ec27957..4f9bdbe9c 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -546,6 +546,13 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> return "-unset-"; } + TextBar: HTMLDivElement | undefined; + setTextBar = (ele: HTMLDivElement) => { + if (ele) { + this.TextBar = ele; + TooltipTextMenu.Toolbar && Array.from(ele.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && ele.appendChild(TooltipTextMenu.Toolbar); + } + } render() { var bounds = this.Bounds; let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; @@ -578,7 +585,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> zIndex: SelectionManager.SelectedDocuments().length > 1 ? 900 : 0, }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} >
-
r && TooltipTextMenu.Toolbar && Array.from(r.childNodes).indexOf(TooltipTextMenu.Toolbar) === -1 && r.appendChild(TooltipTextMenu.Toolbar)} style={{ +
{ - FormattedTextBoxComment.textBox = this; + if (!(e.nativeEvent as any).formattedHandled) { FormattedTextBoxComment.textBox = this; } + (e.nativeEvent as any).formattedHandled = true; + if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } @@ -837,7 +839,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } } - this._editorView!.focus(); } onMouseUp = (e: React.MouseEvent): void => { e.stopPropagation(); @@ -914,7 +915,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); if (this.props.isSelected()) { - FormattedTextBox._toolTipTextMenu!.update(this._editorView!, undefined, this.props); + FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); } return (
{ - let keep = e.target && (e.target as any).type === "checkbox"; + let keep = e.target && (e.target as any).type === "checkbox" ? true : false; FormattedTextBoxComment.opened = keep || !FormattedTextBoxComment.opened; - FormattedTextBoxComment.textBox && FormattedTextBoxComment.textBox.setAnnotation( + FormattedTextBoxComment.textBox && FormattedTextBoxComment.start !== undefined && FormattedTextBoxComment.textBox.setAnnotation( FormattedTextBoxComment.start, FormattedTextBoxComment.end, FormattedTextBoxComment.mark, FormattedTextBoxComment.opened, keep); }; @@ -92,8 +93,9 @@ export class FormattedTextBoxComment { if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; + if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props.isSelected()) return; let set = "none"; - if (state.selection.$from) { + if (FormattedTextBoxComment.textBox && state.selection.$from) { let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); let naft = findEndOfMark(state.selection.$from, view, findOtherUserMark); const spos = state.selection.$from.pos - nbef; -- cgit v1.2.3-70-g09d2 From ebc1e7bd1c34e948fd7288cde19f600a8e2985ee Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 7 Oct 2019 18:50:33 -0400 Subject: fixed-up captions to work with text again. --- src/client/util/TooltipTextMenu.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 55c6e6609..0b1613ed6 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -851,7 +851,7 @@ export class TooltipTextMenu { update(view: EditorView, lastState: EditorState | undefined) { this.updateInternal(view, lastState, this.editorProps) } //updates the tooltip menu when the selection changes - private updateInternal(view: EditorView, lastState: EditorState | undefined, props: any) { + public 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/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 966f183b0..3082edbed 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -899,7 +899,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe tryUpdateHeight() { const ChromeHeight = this.props.ChromeHeight; let sh = this._ref.current ? this._ref.current.scrollHeight : 0; - if (!this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0) { + if (!this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0 && getComputedStyle(this._ref.current!.parentElement!).bottom !== "0px") { let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.props.Document.height, 0); this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0)); @@ -913,8 +913,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); - if (this.props.isSelected()) { - FormattedTextBox._toolTipTextMenu!.update(this._editorView!, undefined, this.props); + if (this._editorView && this.props.isSelected()) { + FormattedTextBox._toolTipTextMenu!.updateInternal(this._editorView, undefined, this.props); } return (
Date: Mon, 7 Oct 2019 20:01:28 -0400 Subject: switched recording over to a menu item. --- src/client/views/nodes/FormattedTextBox.tsx | 36 ++++++++++++++--------------- 1 file changed, 18 insertions(+), 18 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 3082edbed..cab408a31 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -41,6 +41,8 @@ import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './FormattedTextBoxComment'; import React = require("react"); +import { ContextMenuProps } from '../ContextMenuItem'; +import { ContextMenu } from '../ContextMenu'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -296,14 +298,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - recordKeyHandler = (e: KeyboardEvent) => { - if (SelectionManager.SelectedDocuments().length && this.props.Document === SelectionManager.SelectedDocuments()[0].props.Document) { - if (e.key === "R" && e.altKey) { - e.stopPropagation(); - e.preventDefault(); - this.recordBullet(); - } - } + specificContextMenu = (e: React.MouseEvent): void => { + let funcs: ContextMenuProps[] = []; + funcs.push({ description: "Dictate", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); + + ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: funcs, icon: "asterisk" }); } recordBullet = async () => { @@ -336,12 +335,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe nextBullet = (pos: number) => { if (this._editorView) { let frag = Fragment.fromArray(this.newListItems(2)); - let slice = new Slice(frag, 2, 2); - let state = this._editorView.state; - this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice))); - pos += 4; - state = this._editorView.state; - this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); + if (this._editorView.state.doc.resolve(pos).depth >= 2) { + let slice = new Slice(frag, 2, 2); + let state = this._editorView.state; + this._editorView.dispatch(state.tr.step(new ReplaceStep(pos, pos, slice))); + pos += 4; + state = this._editorView.state; + this._editorView.dispatch(state.tr.setSelection(TextSelection.create(this._editorView.state.doc, pos, pos))); + } } } @@ -769,8 +770,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action onFocused = (e: React.FocusEvent): void => { FormattedTextBox.InputBoxOverlay = this; - document.removeEventListener("keypress", this.recordKeyHandler); - document.addEventListener("keypress", this.recordKeyHandler); this.tryUpdateHeight(); } onPointerWheel = (e: React.WheelEvent): void => { @@ -873,7 +872,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } onBlur = (e: any) => { - document.removeEventListener("keypress", this.recordKeyHandler); + DictationManager.Controls.stop(false); if (this._undoTyping) { this._undoTyping.end(); this._undoTyping = undefined; @@ -899,7 +898,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe tryUpdateHeight() { const ChromeHeight = this.props.ChromeHeight; let sh = this._ref.current ? this._ref.current.scrollHeight : 0; - if (!this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0 && getComputedStyle(this._ref.current!.parentElement!).bottom !== "0px") { + if (!this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.props.Document.height, 0); this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0)); @@ -928,6 +927,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe fontSize: this._fontSize, fontFamily: this._fontFamily, }} + onContextMenu={this.specificContextMenu} onKeyDown={this.onKeyPress} onFocus={this.onFocused} onClick={this.onClick} -- cgit v1.2.3-70-g09d2 From d970ad82bc7b0b1c8dae27b5f55ad78bddb0f7cd Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Mon, 7 Oct 2019 21:48:01 -0400 Subject: added commeting with (( and ## --- src/client/documents/Documents.ts | 1 + src/client/util/RichTextRules.ts | 46 ++++++++++++++++++++++++++--- src/client/util/RichTextSchema.tsx | 3 +- src/client/views/nodes/FormattedTextBox.tsx | 2 +- 4 files changed, 46 insertions(+), 6 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 98f56af01..34e146f0e 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -73,6 +73,7 @@ export interface DocumentOptions { dropAction?: dropActionType; backgroundLayout?: string; chromeStatus?: string; + fontSize?: number; curPage?: number; currentTimecode?: number; documentText?: string; diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 2d5ec1743..2ff758fbc 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -1,10 +1,12 @@ import { textblockTypeInputRule, smartQuotes, emDash, ellipsis, InputRule } from "prosemirror-inputrules"; import { schema } from "./RichTextSchema"; import { wrappingInputRule } from "./prosemirrorPatches"; -import { NodeSelection } from "prosemirror-state"; +import { NodeSelection, TextSelection } from "prosemirror-state"; import { NumCast, Cast } from "../../new_fields/Types"; import { Doc } from "../../new_fields/Doc"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { Docs } from "../documents/Documents"; +import { Id } from "../../new_fields/FieldSymbols"; export const inpRules = { rules: [ @@ -80,8 +82,9 @@ export const inpRules = { ruleProvider["ruleAlign_" + heading] = "center"; return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; } - return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + let replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "center" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), new InputRule( new RegExp(/^\[\[\s$/), @@ -94,8 +97,9 @@ export const inpRules = { ruleProvider["ruleAlign_" + heading] = "left"; return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; } - return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + let replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "left" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); }), new InputRule( new RegExp(/^\]\]\s$/), @@ -108,8 +112,42 @@ export const inpRules = { ruleProvider["ruleAlign_" + heading] = "right"; return node ? state.tr.deleteRange(start, end).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; } - return node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + let replaced = node ? state.tr.replaceRangeWith(start, end, schema.nodes.paragraph.create({ align: "right" })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 2))); + }), + new InputRule( + new RegExp(/##\s$/), + (state, match, start, end) => { + let node = (state.doc.resolve(start) as any).nodeAfter; + let sm = state.storedMarks || undefined; + let target = Docs.Create.TextDocument({ width: 75, height: 35, autoHeight: true, fontSize: 9, title: "inline comment" }); + let replaced = node ? state.tr.insertText("←", start).replaceRangeWith(start + 1, end + 1, schema.nodes.dashDoc.create({ + width: 75, height: 35, + title: "dashDoc", docid: target[Id], + float: "right" + })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end - 1))); + }), + new InputRule( + new RegExp(/\(\(/), + (state, match, start, end) => { + let node = (state.doc.resolve(start) as any).nodeAfter; + let sm = state.storedMarks || undefined; + let mark = state.schema.marks.highlight.create(); + let selected = state.tr.setSelection(new TextSelection(state.doc.resolve(start), state.doc.resolve(end))).addMark(start, end, mark); + let content = selected.selection.content(); + let replaced = node ? selected.replaceRangeWith(start, start, + schema.nodes.star.create({ visibility: true, text: content, textslice: content.toJSON() })).setStoredMarks([...node.marks, ...(sm ? sm : [])]) : + state.tr; + return replaced.setSelection(new TextSelection(replaced.doc.resolve(end + 1))); + }), + new InputRule( + new RegExp(/\)\)/), + (state, match, start, end) => { + let mark = state.schema.marks.highlight.create(); + return state.tr.removeStoredMark(mark); }), new InputRule( new RegExp(/\^f\s$/), diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 8d0f59ddc..047706368 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -739,7 +739,8 @@ export class DashDocView { this._dashSpan.style.position = "absolute"; this._dashSpan.style.display = "inline-block"; let removeDoc = () => { - let ns = new NodeSelection(view.state.doc.resolve(getPos())); + let pos = getPos(); + let ns = new NodeSelection(view.state.doc.resolve(pos)); view.dispatch(view.state.tr.setSelection(ns).deleteSelection()); return true; }; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index cab408a31..cacef1ac3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -457,7 +457,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, action((rules: any) => { this._fontFamily = rules ? rules.font : "Arial"; - this._fontSize = rules ? rules.size : 13; + this._fontSize = rules ? rules.size : NumCast(this.props.Document.fontSize, 13); rules && setTimeout(() => { const view = this._editorView!; if (this._proseRef) { -- cgit v1.2.3-70-g09d2 From 00ada8bd463f15f69ae604b79445c69167f8425a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 8 Oct 2019 01:11:18 -0400 Subject: a bunch of fixes to text outline mode. --- src/client/util/ProsemirrorExampleTransfer.ts | 4 ++-- src/client/util/RichTextSchema.tsx | 6 ++++-- src/client/util/TooltipTextMenu.tsx | 29 +++++++++------------------ src/client/views/nodes/FormattedTextBox.scss | 22 +++++++++++--------- src/client/views/nodes/FormattedTextBox.tsx | 25 +++++++++++++++++++---- 5 files changed, 49 insertions(+), 37 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/ProsemirrorExampleTransfer.ts b/src/client/util/ProsemirrorExampleTransfer.ts index dd0f72af0..003ff6272 100644 --- a/src/client/util/ProsemirrorExampleTransfer.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -11,7 +11,7 @@ const mac = typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : export type KeyMap = { [key: string]: any }; -export let updateBullets = (tx2: Transaction, schema: Schema) => { +export let updateBullets = (tx2: Transaction, schema: Schema, mapStyle?: string) => { let fontSize: number | undefined = undefined; tx2.doc.descendants((node: any, offset: any, index: any) => { if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { @@ -20,7 +20,7 @@ export let updateBullets = (tx2: Transaction, schema: Schema) => { if (node.type === schema.nodes.ordered_list) depth++; fontSize = depth === 1 && node.attrs.setFontSize ? Number(node.attrs.setFontSize) : fontSize; let fsize = fontSize && node.type === schema.nodes.ordered_list ? Math.max(6, fontSize - (depth - 1) * 4) : undefined; - tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks); + tx2.setNodeMarkup(offset, node.type, { ...node.attrs, mapStyle: mapStyle ? mapStyle : node.attrs.mapStyle, bulletStyle: depth, inheritedFontSize: fsize }, node.marks); } }); return tx2; diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 047706368..46c2740f4 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -234,6 +234,7 @@ export const nodes: { [index: string]: NodeSpec } = { bulletStyle: { default: 0 }, mapStyle: { default: "decimal" }, setFontSize: { default: undefined }, + setFontFamily: { default: undefined }, inheritedFontSize: { default: undefined }, visibility: { default: true } }, @@ -243,8 +244,9 @@ export const nodes: { [index: string]: NodeSpec } = { const multiMap = bs === 1 ? "decimal1" : bs === 2 ? "upper-alpha" : bs === 3 ? "lower-roman" : bs === 4 ? "lower-alpha" : ""; let map = node.attrs.mapStyle === "decimal" ? decMap : multiMap; let fsize = node.attrs.setFontSize ? node.attrs.setFontSize : node.attrs.inheritedFontSize; - return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none;font-size: ${fsize}` }, 0] : - ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}` }]; + let ffam = node.attrs.setFontFamily; + return node.attrs.visibility ? ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }, 0] : + ['ol', { class: `${map}-ol`, style: `list-style: none; font-size: ${fsize}; font-family: ${ffam}` }]; } }, diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 765c2fe92..75943e537 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -521,23 +521,14 @@ export class TooltipTextMenu { } //actually apply font if ((view.state.selection as any).node && (view.state.selection as any).node.type === view.state.schema.nodes.ordered_list) { - view.dispatch(updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, - { ...(view.state.selection as NodeSelection).node.attrs, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema)); + let status = updateBullets(view.state.tr.setNodeMarkup(view.state.selection.from, (view.state.selection as any).node.type, + { ...(view.state.selection as NodeSelection).node.attrs, setFontFamily: markType.name, setFontSize: Number(markType.name.replace(/p/, "")) }), view.state.schema); + view.dispatch(status.setSelection(new NodeSelection(status.doc.resolve(view.state.selection.from)))); } else toggleMark(markType)(view.state, view.dispatch, view); } } - updateBullets = (tx2: Transaction, style: string) => { - tx2.doc.descendants((node: any, offset: any, index: any) => { - if (node.type === schema.nodes.ordered_list || node.type === schema.nodes.list_item) { - let path = (tx2.doc.resolve(offset) as any).path; - let depth = Array.from(path).reduce((p: number, c: any) => p + (c.hasOwnProperty("type") && c.type === schema.nodes.ordered_list ? 1 : 0), 0); - if (node.type === schema.nodes.ordered_list) depth++; - tx2.setNodeMarkup(offset, node.type, { mapStyle: style, bulletStyle: depth }, node.marks); - } - }); - } //remove all node typeand apply the passed-in one to the selected text changeToNodeType = (nodeType: NodeType | undefined, view: EditorView) => { //remove oldif (nodeType) { //add new @@ -546,18 +537,18 @@ export class TooltipTextMenu { } else { var marks = view.state.storedMarks || (view.state.selection.$to.parentOffset && view.state.selection.$from.marks()); if (!wrapInList(schema.nodes.ordered_list)(view.state, (tx2: any) => { - this.updateBullets(tx2, (nodeType as any).attrs.mapStyle); - marks && tx2.ensureMarks([...marks]); - marks && tx2.setStoredMarks([...marks]); + let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); view.dispatch(tx2); })) { let tx2 = view.state.tr; - this.updateBullets(tx2, (nodeType as any).attrs.mapStyle); - marks && tx2.ensureMarks([...marks]); - marks && tx2.setStoredMarks([...marks]); + let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); + marks && tx3.ensureMarks([...marks]); + marks && tx3.setStoredMarks([...marks]); - view.dispatch(tx2); + view.dispatch(tx3); } } } diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 541c29faa..29e8b14a8 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -154,17 +154,18 @@ footnote::after { content: "..."; } -ol { counter-reset: deci1 0;} -.decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 } -.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 } -.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 } -.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 } -.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 } -.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 } -.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 } -.upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18 } +.ProseMirror { +ol { counter-reset: deci1 0; padding-left: 0px; } +.decimal1-ol {counter-reset: deci1; p { display: inline }; font-size: 24 ; ul, ol { padding-left:30px; } } +.decimal2-ol {counter-reset: deci2; p { display: inline }; font-size: 18 ; ul, ol { padding-left:30px; } } +.decimal3-ol {counter-reset: deci3; p { display: inline }; font-size: 14 ; ul, ol { padding-left:30px; } } +.decimal4-ol {counter-reset: deci4; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } } +.decimal5-ol {counter-reset: deci5; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } } +.decimal6-ol {counter-reset: deci6; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } } +.decimal7-ol {counter-reset: deci7; p { display: inline }; font-size: 10 ; ul, ol { padding-left:30px; } } +.upper-alpha-ol {counter-reset: ualph; p { display: inline }; font-size: 18; } .lower-roman-ol {counter-reset: lroman; p { display: inline }; font-size: 14; } -.lower-alpha-ol {counter-reset: lalpha; p { display: inline }; font-size: 10;} +.lower-alpha-ol {counter-reset: lalpha; p { display: inline }; font-size: 10; } .decimal1:before { content: counter(deci1) ") "; counter-increment: deci1; display:inline-block; min-width: 30;} .decimal2:before { content: counter(deci1) "." counter(deci2) ") "; counter-increment: deci2; display:inline-block; min-width: 35} .decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ") "; counter-increment: deci3; display:inline-block; min-width: 35} @@ -175,3 +176,4 @@ ol { counter-reset: deci1 0;} .upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ") "; counter-increment: ualph; display:inline-block; min-width: 35 } .lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ") "; counter-increment: lroman;display:inline-block; min-width: 50 } .lower-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) "." counter(lalpha, lower-alpha) ") "; counter-increment: lalpha; display:inline-block; min-width: 35} +} diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index cacef1ac3..041675bef 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -779,6 +779,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + static _sheet: any = undefined; + static addRule = ((style) => { + style.type = "text/css" + var sheets = document.head.appendChild(style); + FormattedTextBox._sheet = (sheets as any).sheet; + return function (selector: any, css: any) { + var propText = typeof css === "string" ? css : Object.keys(css).map(function (p) { + return p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p]); + }).join(";"); + return FormattedTextBox._sheet.insertRule("." + selector + "{" + propText + "}", FormattedTextBox._sheet.cssRules.length); + }; + })(document.createElement("style")); + onClick = (e: React.MouseEvent): void => { if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } (e.nativeEvent as any).formattedHandled = true; @@ -814,6 +827,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.preventDefault(); } } + + if (FormattedTextBox._sheet.rules.length) { + FormattedTextBox._sheet.removeRule(0); + } // 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) { let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); @@ -821,14 +838,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let node = this._editorView!.state.doc.nodeAt(pos.pos); let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; if (node === this._nodeClicked && node2 && (node2.type === schema.nodes.ordered_list || node2.type === schema.nodes.list_item)) { - let hit = this._editorView!.domAtPos(pos.pos).node as any; - let beforeEle = document.querySelector("." + hit.className) as Element; - let before = beforeEle ? window.getComputedStyle(beforeEle, ':before') : undefined; + let hit = this._editorView!.domAtPos(pos.pos).node as any; // let beforeEle = document.querySelector("." + hit.className) as Element; + let before = hit ? window.getComputedStyle(hit, ':before') : undefined; let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined; if (beforeWidth && e.nativeEvent.offsetX < beforeWidth) { let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined; - if (ol && ol.type === schema.nodes.ordered_list && !e.shiftKey) { + if (ol && ol.type === schema.nodes.ordered_list && e.shiftKey) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2)))); + FormattedTextBox.addRule(hit.className + ":before", { background: "gray" }); } else { this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); } -- cgit v1.2.3-70-g09d2 From de0b723551eb033f7ad1999dd23309b4f306f344 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 8 Oct 2019 10:08:36 -0400 Subject: moved style sheet stuff into Utils. fixed menu/footnote interaction --- src/Utils.ts | 13 ++++++++++ src/client/util/RichTextSchema.tsx | 11 +++++++-- src/client/views/nodes/FormattedTextBox.tsx | 37 ++++++++++++----------------- 3 files changed, 37 insertions(+), 24 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/Utils.ts b/src/Utils.ts index 4b892aa70..f6ab434a8 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -337,4 +337,17 @@ export default function smoothScroll(duration: number, element: HTMLElement, to: } }; animateScroll(); +} +export function addStyleSheet(styleType: string = "text/css") { + let style = document.createElement("style"); + style.type = styleType; + var sheets = document.head.appendChild(style); + return (sheets as any).sheet; +} +export function addStyleSheetRule(sheet: any, selector: any, css: any) { + var propText = typeof css === "string" ? css : Object.keys(css).map(p => p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p])).join(";"); + return sheet.insertRule("." + selector + "{" + propText + "}", sheet.cssRules.length); +} +export function removeStyleSheetRule(sheet: any, rule: number) { + sheet.removeRule(rule); } \ No newline at end of file diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 836713c01..bc1ad5070 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -4,7 +4,7 @@ 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, NodeSelection, TextSelection } from "prosemirror-state"; +import { EditorState, NodeSelection, TextSelection, Plugin } from "prosemirror-state"; import { StepMap } from "prosemirror-transform"; import { EditorView } from "prosemirror-view"; import * as ReactDOM from 'react-dom'; @@ -847,7 +847,14 @@ export class FootnoteView { "Mod-z": () => undo(this.outerView.state, this.outerView.dispatch), "Mod-y": () => redo(this.outerView.state, this.outerView.dispatch), "Mod-b": toggleMark(schema.marks.strong) - })] + }), + new Plugin({ + view(newView) { + return FormattedTextBox.getToolTip(newView); + } + }) + ], + }), // This is the magic part dispatchTransaction: this.dispatchInner.bind(this), diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 41bd29422..96b434708 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, emptyFunction } from '../../../Utils'; +import { numberRange, timenow, Utils, addStyleSheet, addStyleSheetRule, removeStyleSheetRule } from '../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; @@ -781,18 +781,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - static _sheet: any = undefined; - static addRule = ((style) => { - style.type = "text/css" - var sheets = document.head.appendChild(style); - FormattedTextBox._sheet = (sheets as any).sheet; - return function (selector: any, css: any) { - var propText = typeof css === "string" ? css : Object.keys(css).map(function (p) { - return p + ":" + (p === "content" ? "'" + css[p] + "'" : css[p]); - }).join(";"); - return FormattedTextBox._sheet.insertRule("." + selector + "{" + propText + "}", FormattedTextBox._sheet.cssRules.length); - }; - })(document.createElement("style")); + static _sheet: any = addStyleSheet(); onClick = (e: React.MouseEvent): void => { if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } @@ -830,12 +819,16 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey); + } + + // 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. + hitBulletTargets(x: number, y: number, offsetX: number, select: boolean = false) { if (FormattedTextBox._sheet.rules.length) { - FormattedTextBox._sheet.removeRule(0); + removeStyleSheetRule(FormattedTextBox._sheet, 0); } - // 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) { - let pos = this._editorView!.posAtCoords({ left: e.clientX, top: e.clientY }); + if (this.props.isSelected() && offsetX < 40) { + let pos = this._editorView!.posAtCoords({ left: x, top: y }); if (pos && pos.pos > 0) { let node = this._editorView!.state.doc.nodeAt(pos.pos); let node2 = node && node.type === schema.nodes.paragraph ? this._editorView!.state.doc.nodeAt(pos.pos - 1) : undefined; @@ -843,11 +836,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let hit = this._editorView!.domAtPos(pos.pos).node as any; // let beforeEle = document.querySelector("." + hit.className) as Element; let before = hit ? window.getComputedStyle(hit, ':before') : undefined; let beforeWidth = before ? Number(before.getPropertyValue('width').replace("px", "")) : undefined; - if (beforeWidth && e.nativeEvent.offsetX < beforeWidth) { + if (beforeWidth && offsetX < beforeWidth) { let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined; - if (ol && ol.type === schema.nodes.ordered_list && e.shiftKey) { + if (ol && ol.type === schema.nodes.ordered_list && select) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2)))); - FormattedTextBox.addRule(hit.className + ":before", { background: "gray" }); + addStyleSheetRule(FormattedTextBox._sheet, hit.className + ":before", { background: "gray" }); } else { this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); } @@ -875,8 +868,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe tooltipTextMenuPlugin() { let self = FormattedTextBox; return new Plugin({ - view(_editorView) { - return self._toolTipTextMenu = FormattedTextBox.getToolTip(_editorView); + view(newView) { + return self._toolTipTextMenu = FormattedTextBox.getToolTip(newView); } }); } -- cgit v1.2.3-70-g09d2 From 84211163c9f085a521b3ca3af0dc9103640e3622 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 8 Oct 2019 14:07:32 -0400 Subject: cleaned up tagging of text spans with data and timestamps --- src/Utils.ts | 13 ++++++- src/client/util/DocumentManager.ts | 2 +- src/client/util/RichTextRules.ts | 21 ++++++++++ src/client/util/RichTextSchema.tsx | 34 ++++++++++++----- src/client/views/nodes/FormattedTextBox.tsx | 59 +++++++++++++++++++++++++---- 5 files changed, 109 insertions(+), 20 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/Utils.ts b/src/Utils.ts index f6ab434a8..489de3b50 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -349,5 +349,16 @@ export function addStyleSheetRule(sheet: any, selector: any, css: any) { return sheet.insertRule("." + selector + "{" + propText + "}", sheet.cssRules.length); } export function removeStyleSheetRule(sheet: any, rule: number) { - sheet.removeRule(rule); + if (sheet.rules.length) { + sheet.removeRule(rule); + return true; + } + return false; +} +export function clearStyleSheetRules(sheet: any) { + if (sheet.rules.length) { + numberRange(sheet.rules.length).map(n => sheet.removeRule(0)); + return true; + } + return false; } \ No newline at end of file diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 83a69465f..c45d3a75f 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -168,7 +168,7 @@ export class DocumentManager { retryDocView.props.focus(targetDoc, willZoom); // focus on the target if it now exists in the context } else { if (closeContextIfNotFound && targetDocContextView.props.removeDocument) targetDocContextView.props.removeDocument(targetDocContextView.props.Document); - (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); // otherwise create a new view of the target + targetDoc.layout && (dockFunc || CollectionDockingView.AddRightSplit)(Doc.BrushDoc(Doc.MakeAlias(targetDoc)), undefined); // otherwise create a new view of the target } highlight(); }, 0); diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 2ff758fbc..346d5ab99 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -71,6 +71,27 @@ export const inpRules = { } return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })); }), + new InputRule( + new RegExp(/\t/), + (state, match, start, end) => { + if (state.selection.to === state.selection.from) return null; + let node = (state.doc.resolve(start) as any).nodeAfter; + return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + }), + new InputRule( + new RegExp(/\!/), + (state, match, start, end) => { + if (state.selection.to === state.selection.from) return null; + let node = (state.doc.resolve(start) as any).nodeAfter; + return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "important", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + }), + new InputRule( + new RegExp(/\x/), + (state, match, start, end) => { + if (state.selection.to === state.selection.from) return null; + let node = (state.doc.resolve(start) as any).nodeAfter; + return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "disagree", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + }), new InputRule( new RegExp(/^\^\^\s$/), (state, match, start, end) => { diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index bc1ad5070..80bcf25ad 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -439,20 +439,34 @@ export const marks: { [index: string]: MarkSpec } = { user_mark: { attrs: { userid: { default: "" }, - hide_users: { default: [] }, opened: { default: true }, - modified: { default: "when?" } + modified: { default: "when?" }, // 5 second intervals since 1970 }, group: "inline", toDOM(node: any) { - let hideUsers = node.attrs.hide_users; - let hidden = hideUsers.indexOf(node.attrs.userid) !== -1 || (hideUsers.length === 0 && node.attrs.userid !== Doc.CurrentUserEmail); - return hidden ? - (node.attrs.opened ? - ['span', { class: "userMarkOpen" }, 0] : - ['span', { class: "userMark" }, ['span', 0]] - ) : - ['span', 0]; + let uid = node.attrs.userid.replace(".", "").replace("@", ""); + let min = Math.round(node.attrs.modified / 12); + let hr = Math.round(min / 60); + let day = Math.round(hr / 60 / 24); + return node.attrs.opened ? + ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] : + ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]]; + } + }, + // the id of the user who entered the text + user_tag: { + attrs: { + userid: { default: "" }, + opened: { default: true }, + modified: { default: "when?" }, // 5 second intervals since 1970 + tag: { default: "" } + }, + group: "inline", + toDOM(node: any) { + let uid = node.attrs.userid.replace(".", "").replace("@", ""); + return node.attrs.opened ? + ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, 0] : + ['span', { class: "userTag-" + uid + " userTag-" + node.attrs.tag }, ['span', 0]]; } }, diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 96b434708..d878f2b74 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, addStyleSheet, addStyleSheetRule, removeStyleSheetRule } from '../../../Utils'; +import { numberRange, Utils, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from '../../../Utils'; import { GoogleApiClientUtils, Pulls, Pushes } from '../../apis/google_docs/GoogleApiClientUtils'; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from '../../documents/Documents'; @@ -43,6 +43,7 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format import React = require("react"); import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; +import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -298,9 +299,51 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + + static _highlights: string[] = []; + + updateHighlights = () => { + clearStyleSheetRules(FormattedTextBox._userStyleSheet); + if (FormattedTextBox._highlights.indexOf("My Text") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "yellow" }); + } + if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" }); + } + if (FormattedTextBox._highlights.indexOf("Important Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "important", { "font-size": "larger" }); + } + if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" }); + } + if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + let min = Math.round(Date.now() / 1000 / 60); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-min-" + (min - i), { opacity: ((10 - i - 1) / 10).toString() })); + setTimeout(() => this.updateHighlights()); + } + if (FormattedTextBox._highlights.indexOf("By Recent Hour") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); + let hr = Math.round(Date.now() / 1000 / 60 / 60); + numberRange(10).map(i => addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-hr-" + (hr - i), { opacity: ((10 - i - 1) / 10).toString() })); + } + } + specificContextMenu = (e: React.MouseEvent): void => { let funcs: ContextMenuProps[] = []; funcs.push({ description: "Dictate", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); + ["My Text", "Todo Items", "Important Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => + funcs.push({ + description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { + e.stopPropagation(); + if (FormattedTextBox._highlights.indexOf(option) === -1) { + FormattedTextBox._highlights.push(option); + } else { + FormattedTextBox._highlights.splice(FormattedTextBox._highlights.indexOf(option), 1); + } + this.updateHighlights(); + }, icon: "expand-arrows-alt" + })); ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: funcs, icon: "asterisk" }); } @@ -718,7 +761,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } this._editorView!.focus(); // add user mark for any first character that was typed since the user mark that gets set in KeyPress won't have been called yet. - this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })]; + this._editorView!.state.storedMarks = [...(this._editorView!.state.storedMarks ? this._editorView!.state.storedMarks : []), schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) })]; } getFont(font: string) { switch (font) { @@ -781,7 +824,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - static _sheet: any = addStyleSheet(); + static _bulletStyleSheet: any = addStyleSheet(); + static _userStyleSheet: any = addStyleSheet(); onClick = (e: React.MouseEvent): void => { if ((e.nativeEvent as any).formattedHandled) { e.stopPropagation(); return; } @@ -824,9 +868,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe // 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. hitBulletTargets(x: number, y: number, offsetX: number, select: boolean = false) { - if (FormattedTextBox._sheet.rules.length) { - removeStyleSheetRule(FormattedTextBox._sheet, 0); - } + clearStyleSheetRules(FormattedTextBox._bulletStyleSheet); if (this.props.isSelected() && offsetX < 40) { let pos = this._editorView!.posAtCoords({ left: x, top: y }); if (pos && pos.pos > 0) { @@ -840,7 +882,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let ol = this._editorView!.state.doc.nodeAt(pos.pos - 2) ? this._editorView!.state.doc.nodeAt(pos.pos - 2) : undefined; if (ol && ol.type === schema.nodes.ordered_list && select) { this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2)))); - addStyleSheetRule(FormattedTextBox._sheet, hit.className + ":before", { background: "gray" }); + addStyleSheetRule(FormattedTextBox._bulletStyleSheet, hit.className + ":before", { background: "gray" }); } else { this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); } @@ -898,7 +940,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }))); + let mark = schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: Math.round(Date.now() / 1000 / 5) }); + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(mark)); if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); -- cgit v1.2.3-70-g09d2 From 121cbc76bf971e688398533b303e32d637265364 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 8 Oct 2019 14:26:38 -0400 Subject: added ignore items markup --- src/client/util/RichTextRules.ts | 9 ++++++++- src/client/views/nodes/FormattedTextBox.tsx | 5 ++++- 2 files changed, 12 insertions(+), 2 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/RichTextRules.ts b/src/client/util/RichTextRules.ts index 346d5ab99..ebb9bda8a 100644 --- a/src/client/util/RichTextRules.ts +++ b/src/client/util/RichTextRules.ts @@ -72,12 +72,19 @@ export const inpRules = { return state.tr.deleteRange(start, end).addStoredMark(schema.marks.pFontSize.create({ fontSize: Number(match[1]) })); }), new InputRule( - new RegExp(/\t/), + new RegExp(/t/), (state, match, start, end) => { if (state.selection.to === state.selection.from) return null; let node = (state.doc.resolve(start) as any).nodeAfter; return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "todo", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; }), + new InputRule( + new RegExp(/i/), + (state, match, start, end) => { + if (state.selection.to === state.selection.from) return null; + let node = (state.doc.resolve(start) as any).nodeAfter; + return node ? state.tr.addMark(start, end, schema.marks.user_tag.create({ userid: Doc.CurrentUserEmail, tag: "ignore", modified: Math.round(Date.now() / 1000 / 60) })) : state.tr; + }), new InputRule( new RegExp(/\!/), (state, match, start, end) => { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d878f2b74..86166b0b3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -316,6 +316,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (FormattedTextBox._highlights.indexOf("Disagree Items") !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "disagree", { "text-decoration": "line-through" }); } + if (FormattedTextBox._highlights.indexOf("Ignore Items") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "ignore", { "font-size": "0" }); + } if (FormattedTextBox._highlights.indexOf("By Recent Minute") !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { opacity: "0.1" }); let min = Math.round(Date.now() / 1000 / 60); @@ -332,7 +335,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe specificContextMenu = (e: React.MouseEvent): void => { let funcs: ContextMenuProps[] = []; funcs.push({ description: "Dictate", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); - ["My Text", "Todo Items", "Important Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => + ["My Text", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => funcs.push({ description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { e.stopPropagation(); -- cgit v1.2.3-70-g09d2 From 29cdf0d66df3e38408f69ac8225b4d59397ee2e6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 8 Oct 2019 19:02:35 -0400 Subject: fixes for text selection in pdfs to be less jumpy and to work through other annotations. --- src/Utils.ts | 2 +- src/client/util/TooltipTextMenu.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/client/views/pdf/PDFMenu.tsx | 2 +- src/client/views/pdf/PDFViewer.scss | 4 ++++ src/client/views/pdf/PDFViewer.tsx | 15 ++++++++++----- 6 files changed, 18 insertions(+), 9 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/Utils.ts b/src/Utils.ts index 489de3b50..9a2f01f80 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -320,7 +320,7 @@ const easeInOutQuad = (currentTime: number, start: number, change: number, durat return (-change / 2) * (newCurrentTime * (newCurrentTime - 2) - 1) + start; }; -export default function smoothScroll(duration: number, element: HTMLElement, to: number) { +export function smoothScroll(duration: number, element: HTMLElement, to: number) { const start = element.scrollTop; const change = to - start; const startDate = new Date().getTime(); diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 5f2bc18b5..fe4c5ac9f 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -545,7 +545,7 @@ export class TooltipTextMenu { view.dispatch(tx2); })) { let tx2 = view.state.tr; - let tx3 = updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle); + let tx3 = nodeType ? updateBullets(tx2, schema, (nodeType as any).attrs.mapStyle) : tx2; marks && tx3.ensureMarks([...marks]); marks && tx3.setStoredMarks([...marks]); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 86166b0b3..67d1bc42c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -548,7 +548,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let editor = this._editorView; let ret = findLinkFrag(editor.state.doc.content, editor); - if (ret.frag.size > 2) { + if (ret.frag.size > 2 && ret.start >= 0) { let selection = TextSelection.near(editor.state.doc.resolve(ret.start)); // default to near the start if (ret.frag.firstChild) { selection = TextSelection.between(editor.state.doc.resolve(ret.start + 2), editor.state.doc.resolve(ret.start + ret.frag.firstChild.nodeSize)); // bcz: looks better to not have the target selected diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 1e3320069..517a99a68 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -155,7 +155,7 @@ export default class PDFMenu extends React.Component { @action highlightClicked = (e: React.MouseEvent) => { - if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { + if (!this.Highlight("rgba(245, 230, 95, 0.616)") && this.Pinned) { // yellowish highlight color for a marker type highlight this.Highlighting = !this.Highlighting; } } diff --git a/src/client/views/pdf/PDFViewer.scss b/src/client/views/pdf/PDFViewer.scss index c77cee792..f6fedf3da 100644 --- a/src/client/views/pdf/PDFViewer.scss +++ b/src/client/views/pdf/PDFViewer.scss @@ -15,6 +15,10 @@ mix-blend-mode: multiply; opacity: 0.9; + span { + padding-right: 5px; + padding-bottom: 4px; + } } .textLayer ::selection { background: yellow; } // should match the backgroundColor in createAnnotation() .textLayer .highlight { diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index 4516e9904..b010d16c8 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -9,7 +9,7 @@ import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; -import smoothScroll, { Utils, emptyFunction, returnOne, intersectRect } from "../../../Utils"; +import { smoothScroll, Utils, emptyFunction, returnOne, intersectRect, addStyleSheet, addStyleSheetRule, clearStyleSheetRules } from "../../../Utils"; import { Docs, DocUtils } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { CompiledScript, CompileScript } from "../../util/Scripting"; @@ -24,6 +24,7 @@ import { CollectionView } from "../collections/CollectionView"; import Annotation from "./Annotation"; import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView"; import { SelectionManager } from "../../util/SelectionManager"; +import { undoBatch } from "../../util/UndoManager"; const PDFJSViewer = require("pdfjs-dist/web/pdf_viewer"); const pdfjsLib = require("pdfjs-dist"); @@ -62,6 +63,7 @@ interface IViewerProps { */ @observer export class PDFViewer extends React.Component { + static _annotationStyle: any = addStyleSheet(); @observable private _pageSizes: { width: number, height: number }[] = []; @observable private _annotations: Doc[] = []; @observable private _savedAnnotations: Dictionary = new Dictionary(); @@ -150,7 +152,7 @@ export class PDFViewer extends React.Component { copy = (e: ClipboardEvent) => { if (this.props.active() && e.clipboardData) { - let annoDoc = this.makeAnnotationDocument("#0390fc"); + let annoDoc = this.makeAnnotationDocument("rgba(3,144,152,0.3)"); // copied text markup color (blueish) if (annoDoc) { e.clipboardData.setData("text/plain", this._selectionText); e.clipboardData.setData("dash/pdfOrigin", this.props.Document[Id]); @@ -237,6 +239,7 @@ export class PDFViewer extends React.Component { this._pdfViewer.setDocument(this.props.pdf); } + @undoBatch @action makeAnnotationDocument = (color: string): Opt => { if (this._savedAnnotations.size() === 0) return undefined; @@ -388,6 +391,7 @@ export class PDFViewer extends React.Component { // if alt+left click, drag and annotate this._downX = e.clientX; this._downY = e.clientY; + addStyleSheetRule(PDFViewer._annotationStyle, "pdfAnnotation", { "pointer-events": "none" }); if (NumCast(this.props.Document.scale, 1) !== 1) return; if ((e.button !== 0 || e.altKey) && this.active()) { this._setPreviewCursor && this._setPreviewCursor(e.clientX, e.clientY, true); @@ -475,6 +479,7 @@ export class PDFViewer extends React.Component { @action onSelectEnd = (e: PointerEvent): void => { + clearStyleSheetRules(PDFViewer._annotationStyle); this._savedAnnotations.clear(); if (this._marqueeing) { if (this._marqueeWidth > 10 || this._marqueeHeight > 10) { @@ -510,8 +515,8 @@ export class PDFViewer extends React.Component { } } - if (PDFMenu.Instance.Highlighting) { - this.highlight("rgba(245, 230, 95, 0.616)"); // when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up + if (PDFMenu.Instance.Highlighting) {// when highlighter has been toggled when menu is pinned, we auto-highlight immediately on mouse up + this.highlight("rgba(245, 230, 95, 0.616)"); // yellowish highlight color for highlighted text (should match PDFMenu's highlight color) } else { PDFMenu.Instance.StartDrag = this.startDrag; @@ -538,7 +543,7 @@ 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 }); - const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); + const annotationDoc = this.highlight("rgba(146, 245, 95, 0.467)"); // yellowish highlight color when dragging out a text selection if (annotationDoc) { let dragData = new DragManager.AnnotationDragData(this.props.Document, annotationDoc, targetDoc); DragManager.StartAnnotationDrag([ele], dragData, e.pageX, e.pageY, { -- cgit v1.2.3-70-g09d2 From bfdbc495c0327b87ee0f896778968e65e2f18af6 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 9 Oct 2019 20:39:40 -0400 Subject: fixed rich text search highlights --- src/client/util/RichTextSchema.tsx | 7 ++- src/client/views/nodes/FormattedTextBox.tsx | 79 ++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 27 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 80bcf25ad..a5502577b 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -427,10 +427,13 @@ export const marks: { [index: string]: MarkSpec } = { }, search_highlight: { + attrs: { + selected: { default: false } + }, parseDOM: [{ style: 'background: yellow' }], - toDOM() { + toDOM(node: any) { return ['span', { - style: 'background: yellow' + style: `background: ${node.attrs.selected ? "orange" : "yellow"}` }]; } }, diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 67d1bc42c..77543d2e8 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -7,7 +7,7 @@ import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { inputRules } from 'prosemirror-inputrules'; import { keymap } from "prosemirror-keymap"; -import { Fragment, Mark, Node, Node as ProsNode, NodeType, Slice } from "prosemirror-model"; +import { Fragment, Mark, Node, Node as ProsNode, Slice } from "prosemirror-model"; import { EditorState, NodeSelection, Plugin, TextSelection, Transaction } from "prosemirror-state"; import { ReplaceStep } from 'prosemirror-transform'; import { EditorView } from "prosemirror-view"; @@ -43,7 +43,7 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format import React = require("react"); import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; -import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils'; +import { TextShadowProperty } from 'csstype'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -80,6 +80,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _editorView: Opt; private _applyingChange: boolean = false; private _nodeClicked: any; + private _searchIndex = 0; private _undoTyping?: UndoManager.Batch; private _searchReactionDisposer?: Lambda; private _scrollToRegionReactionDisposer: Opt; @@ -212,37 +213,27 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - public highlightSearchTerms = (terms: String[]) => { + public highlightSearchTerms = (terms: string[]) => { if (this._editorView && (this._editorView as any).docView) { - const doc = this._editorView.state.doc; const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => { - if (node.isLeaf && node.isText && node.text) { - let nodeText: String = node.text; - let tokens = nodeText.split(" "); - let start = pos; - tokens.forEach((word) => { - if (terms.includes(word) && this._editorView) { - this._editorView.dispatch(this._editorView.state.tr.addMark(start, start + word.length, mark).removeStoredMark(mark)); - } - start += word.length + 1; - }); - } - }); + 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 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()); } } public unhighlightSearchTerms = () => { if (this._editorView && (this._editorView as any).docView) { - const doc = this._editorView.state.doc; const mark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight); - doc.nodesBetween(0, doc.content.size, (node: ProsNode, pos: number, parent: ProsNode, index: number) => { - if (node.isLeaf && node.isText && node.text) { - if (node.marks.includes(mark) && this._editorView) { - this._editorView.dispatch(this._editorView.state.tr.removeMark(pos, pos + node.nodeSize, mark)); - } - } - }); + const activeMark = this._editorView.state.schema.mark(this._editorView.state.schema.marks.search_highlight, { selected: true }); + let end = this._editorView!.state.doc.textContent.length - 1; + 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) => { @@ -299,7 +290,45 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { + let offset = 0 + + if (context === node) return { from: offset, to: offset + node.nodeSize } + if (node.isBlock) { + for (let i = 0; i < (context.content as any).content.length; i++) { + let result = this.getNodeEndpoints((context.content as any).content[i], node) + if (result) { + return { + from: result.from + offset + (context.type.name === "doc" ? 0 : 1), + to: result.to + offset + (context.type.name === "doc" ? 0 : 1) + } + } + offset += (context.content as any).content[i].nodeSize + } + return null; + } else { + return null; + } + } + + + //Recursively finds matches within a given node + findInNode(pm: EditorView, node: Node, find: string) { + let ret: TextSelection[] = []; + + if (node.isTextblock) { + let index = 0, foundAt, ep = this.getNodeEndpoints(pm.state.doc, node); + while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) { + let sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)) + ret.push(sel) + index = index + foundAt + find.length; + } + } else { + node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))) + } + return ret + } static _highlights: string[] = []; updateHighlights = () => { -- cgit v1.2.3-70-g09d2 From 3b1345616bf2f7101a21c182e0c9719b1e31f899 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 9 Oct 2019 22:35:47 -0400 Subject: semicolons and fix to unhighlighting searched text in text boxes. --- src/client/views/nodes/FormattedTextBox.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 77543d2e8..9ca77cc8b 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -232,7 +232,7 @@ 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.textContent.length - 1; + let end = this._editorView!.state.doc.nodeSize - 2; this._editorView!.dispatch(this._editorView!.state.tr.removeMark(0, end, mark).removeMark(0, end, activeMark)); } } @@ -291,20 +291,20 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } getNodeEndpoints(context: Node, node: Node): { from: number, to: number } | null { - let offset = 0 + let offset = 0; - if (context === node) return { from: offset, to: offset + node.nodeSize } + if (context === node) return { from: offset, to: offset + node.nodeSize }; if (node.isBlock) { for (let i = 0; i < (context.content as any).content.length; i++) { - let result = this.getNodeEndpoints((context.content as any).content[i], node) + let result = this.getNodeEndpoints((context.content as any).content[i], node); if (result) { return { from: result.from + offset + (context.type.name === "doc" ? 0 : 1), to: result.to + offset + (context.type.name === "doc" ? 0 : 1) - } + }; } - offset += (context.content as any).content[i].nodeSize + offset += (context.content as any).content[i].nodeSize; } return null; } else { @@ -320,14 +320,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (node.isTextblock) { let index = 0, foundAt, ep = this.getNodeEndpoints(pm.state.doc, node); while (ep && (foundAt = node.textContent.slice(index).search(RegExp(find, "i"))) > -1) { - let sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)) - ret.push(sel) + let sel = new TextSelection(pm.state.doc.resolve(ep.from + index + foundAt + 1), pm.state.doc.resolve(ep.from + index + foundAt + find.length + 1)); + ret.push(sel); index = index + foundAt + find.length; } } else { - node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))) + node.content.forEach((child, i) => ret = ret.concat(this.findInNode(pm, child, find))); } - return ret + return ret; } static _highlights: string[] = []; -- 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/nodes/FormattedTextBox.tsx') 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 From e6be0feda60963d28838d9fe7c79bf40cde6bc62 Mon Sep 17 00:00:00 2001 From: bob Date: Fri, 11 Oct 2019 10:15:29 -0400 Subject: added highlighting other's text. fixed some exceptions with text highlighitng. --- src/client/util/RichTextSchema.tsx | 6 ++++-- src/client/views/nodes/FormattedTextBox.tsx | 10 ++++++---- src/client/views/nodes/FormattedTextBoxComment.tsx | 2 +- 3 files changed, 11 insertions(+), 7 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 063686d58..cc3548e1a 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -18,6 +18,7 @@ import { Transform } from "./Transform"; import React = require("react"); import { BoolCast, NumCast } from "../../new_fields/Types"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; +import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; @@ -451,9 +452,10 @@ export const marks: { [index: string]: MarkSpec } = { let min = Math.round(node.attrs.modified / 12); let hr = Math.round(min / 60); let day = Math.round(hr / 60 / 24); + let remote = node.attrs.userid !== Doc.CurrentUserEmail ? " userMark-remote" : ""; return node.attrs.opened ? - ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] : - ['span', { class: "userMark-" + uid + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]]; + ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, 0] : + ['span', { class: "userMark-" + uid + remote + " userMark-min-" + min + " userMark-hr-" + hr + " userMark-day-" + day }, ['span', 0]]; } }, // the id of the user who entered the text diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index cbe4945af..b05d0046c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -238,9 +238,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } setAnnotation = (start: number, end: number, mark: Mark, opened: boolean, keep: boolean = false) => { let view = this._editorView!; - let mid = view.state.doc.resolve(Math.round((start + end) / 2)); let nmark = view.state.schema.marks.user_mark.create({ ...mark.attrs, userid: keep ? Doc.CurrentUserEmail : mark.attrs.userid, opened: opened }); - view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark).setSelection(new TextSelection(mid))); + view.dispatch(view.state.tr.removeMark(start, end, nmark).addMark(start, end, nmark)); } protected createDropTarget = (ele: HTMLDivElement) => { this._proseRef = ele; @@ -333,8 +332,11 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe updateHighlights = () => { clearStyleSheetRules(FormattedTextBox._userStyleSheet); + if (FormattedTextBox._highlights.indexOf("Text from Others") !== -1) { + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-remote", { background: "yellow" }); + } if (FormattedTextBox._highlights.indexOf("My Text") !== -1) { - addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "yellow" }); + addStyleSheetRule(FormattedTextBox._userStyleSheet, "userMark-" + Doc.CurrentUserEmail.replace(".", "").replace("@", ""), { background: "moccasin" }); } if (FormattedTextBox._highlights.indexOf("Todo Items") !== -1) { addStyleSheetRule(FormattedTextBox._userStyleSheet, "userTag-" + "todo", { outline: "black solid 1px" }); @@ -364,7 +366,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe specificContextMenu = (e: React.MouseEvent): void => { let funcs: ContextMenuProps[] = []; funcs.push({ description: "Dictate", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); - ["My Text", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => + ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => funcs.push({ description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { e.stopPropagation(); diff --git a/src/client/views/nodes/FormattedTextBoxComment.tsx b/src/client/views/nodes/FormattedTextBoxComment.tsx index a75bfd373..bde278be3 100644 --- a/src/client/views/nodes/FormattedTextBoxComment.tsx +++ b/src/client/views/nodes/FormattedTextBoxComment.tsx @@ -93,7 +93,7 @@ export class FormattedTextBoxComment { if (lastState && lastState.doc.eq(state.doc) && lastState.selection.eq(state.selection)) return; - if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props.isSelected()) return; + if (!FormattedTextBoxComment.textBox || !FormattedTextBoxComment.textBox.props || !FormattedTextBoxComment.textBox.props.isSelected()) return; let set = "none"; if (FormattedTextBoxComment.textBox && state.selection.$from) { let nbef = findStartOfMark(state.selection.$from, view, findOtherUserMark); -- cgit v1.2.3-70-g09d2 From 26e215b0cddbb4c14cfd8eb7a720a373e797c615 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Tue, 15 Oct 2019 00:23:00 -0400 Subject: cleaned up current_user_utils. cleaned up some interactions with colorBox, textbox height --- src/client/documents/Documents.ts | 19 +- src/client/views/DocComponent.tsx | 34 ++- src/client/views/InkingControl.tsx | 8 + src/client/views/MainView.tsx | 16 +- src/client/views/nodes/ColorBox.scss | 7 +- src/client/views/nodes/ColorBox.tsx | 12 +- src/client/views/nodes/FormattedTextBox.tsx | 13 +- src/client/views/nodes/PDFBox.tsx | 3 +- .../authentication/models/current_user_utils.ts | 295 ++++++++++----------- 9 files changed, 229 insertions(+), 178 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 1f89d2993..13cc7815c 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -40,7 +40,7 @@ import { ButtonBox } from "../views/nodes/ButtonBox"; import { FontIconBox } from "../views/nodes/FontIconBox"; import { SchemaHeaderField, RandomPastel } from "../../new_fields/SchemaHeaderField"; import { PresBox } from "../views/nodes/PresBox"; -import { ComputedField } from "../../new_fields/ScriptField"; +import { ComputedField, ScriptField } from "../../new_fields/ScriptField"; import { ProxyField } from "../../new_fields/Proxy"; import { DocumentType } from "./DocumentTypes"; import { LinkFollowBox } from "../views/linking/LinkFollowBox"; @@ -65,7 +65,10 @@ export interface DocumentOptions { page?: number; scale?: number; fitWidth?: boolean; + forceActive?: boolean; + preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view layout?: string | Doc; + hideHeadings?: boolean; // whether stacking view column headings should be hidden isTemplate?: boolean; templates?: List; viewType?: number; @@ -84,11 +87,21 @@ export interface DocumentOptions { documentText?: string; borderRounding?: string; boxShadow?: string; + sectionFilter?: string; // field key used to determine headings for sections in stacking and masonry views schemaColumns?: List; dockingConfig?: string; autoHeight?: boolean; + removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document dbDoc?: Doc; + onClick?: ScriptField; + onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop icon?: string; + gridGap?: number; // gap between items in masonry view + xMargin?: number; // gap between left edge of document and start of masonry/stacking layouts + yMargin?: number; // gap between top edge of dcoument and start of masonry/stacking layouts + panel?: Doc; // panel to display in 'targetContainer' as the result of a button onClick script + targetContainer?: Doc; // document whose proto will be set to 'panel' as the result of a onClick click script + convertToButtons?: boolean; // whether documents dropped onto a collection should be converted to buttons that will construct the dropped document // [key: string]: Opt; } @@ -457,6 +470,10 @@ export namespace Docs { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Freeform }, id); } + export function LinearDocument(documents: Array, options: DocumentOptions, id?: string) { + return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List([new SchemaHeaderField("title", "#f1efeb")]), ...options, viewType: CollectionViewType.Linear }, id); + } + export function SchemaDocument(schemaColumns: SchemaHeaderField[], documents: Array, options: DocumentOptions) { return InstanceFromProto(Prototypes.get(DocumentType.COL), new List(documents), { chromeStatus: "collapsed", schemaColumns: new List(schemaColumns), ...options, viewType: CollectionViewType.Schema }); } diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index 2c5992259..b05966bb5 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -1,10 +1,17 @@ import * as React from 'react'; import { Doc } from '../../new_fields/Doc'; import { computed, action } from 'mobx'; -import { Cast } from '../../new_fields/Types'; +import { Cast, BoolCast } from '../../new_fields/Types'; import { listSpec } from '../../new_fields/Schema'; +import { InkingControl } from './InkingControl'; +import { InkTool } from '../../new_fields/InkField'; -export function DocComponent

(schemaCtor: (doc: Doc) => T) { + +/// DocComponents returns a generic base class for React views of document fields that are not interactive +interface DocComponentProps { + Document: Doc; +} +export function DocComponent

(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 @@ -15,6 +22,27 @@ export function DocComponent

(schemaCtor: (doc: D return Component; } + +/// DocStaticProps return a base class for React views of document fields that are interactive only when selected (e.g. ColorBox) +interface DocStaticProps { + Document: Doc; + isSelected: () => boolean; + renderDepth: number; +} +export function DocStaticComponent

(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); + } + active = () => (this.props.Document.forceActive || this.props.isSelected() || this.props.renderDepth === 0) && !InkingControl.Instance.selectedTool; + } + return Component; +} + + +/// DocAnnotatbleComponent return a base class for React views of document fields that are annotatable *and* interactive when selected (e.g., pdf, image) interface DocAnnotatableProps { Document: Doc; DataDoc?: Doc; @@ -57,7 +85,7 @@ export function DocAnnotatableComponent

(schema 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; + active = () => (InkingControl.Instance.selectedTool === InkTool.None) && (BoolCast(this.props.Document.forceActive) || this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0); } return Component; } \ No newline at end of file diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 38734a03d..d42d8d2d9 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -48,6 +48,14 @@ export class InkingControl extends React.Component { let selected = SelectionManager.SelectedDocuments(); let oldColors = selected.map(view => { let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); + let sel = window.getSelection(); + if (StrCast(targetDoc.layout).indexOf("FormattedTextBox") !== -1 && (!sel || sel.toString() !== "")) { + targetDoc.color = this._selectedColor; + return { + target: targetDoc, + previous: StrCast(targetDoc.color) + }; + } let oldColor = StrCast(targetDoc.backgroundColor); let matchedColor = this._selectedColor; const cvd = view.props.ContainingCollectionDoc; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index ddea3e223..701f094e2 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -343,12 +343,12 @@ export class MainView extends React.Component { if (!(sidebarContent instanceof Doc)) { return (null); } - let libraryButtonDoc = Cast(CurrentUserUtils.UserDocument.libraryButtons, Doc) as Doc; - libraryButtonDoc.columnWidth = this.flyoutWidth / 3 - 30; + let sidebarButtonsDoc = Cast(CurrentUserUtils.UserDocument.sidebarButtons, Doc) as Doc; + sidebarButtonsDoc.columnWidth = this.flyoutWidth / 3 - 30; return

{ - Doc.AddDocToList(CurrentUserUtils.UserDocument, "docButtons", doc); + Doc.AddDocToList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); return true; } remButtonDoc = (doc: Doc) => { - Doc.RemoveDocFromList(CurrentUserUtils.UserDocument, "docButtons", doc); + Doc.RemoveDocFromList(CurrentUserUtils.UserDocument.expandingButtons as Doc, "data", doc); return true; } @computed get docButtons() { return
- ; +const ColorDocument = makeInterface(documentSchema); @observer -export class ColorBox extends React.Component { +export class ColorBox extends DocStaticComponent(ColorDocument) { public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); } render() { - return
+ return
e.button === 0 && !e.ctrlKey && e.stopPropagation()}>
; } -} \ No newline at end of file +} \ No newline at end of file diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index b05d0046c..181f37d36 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -43,7 +43,6 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format import React = require("react"); import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; -import { TextShadowProperty } from 'csstype'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -490,7 +489,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ); this._heightReactionDisposer = reaction( - () => this.props.Document[WidthSym](), + () => [this.props.Document[WidthSym](), this.props.Document.autoHeight], () => this.tryUpdateHeight() ); @@ -984,13 +983,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { - const ChromeHeight = this.props.ChromeHeight; - let sh = this._ref.current ? this._ref.current.scrollHeight : 0; - if (!this.props.Document.isAnimating && this.props.Document.autoHeight && sh !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { + let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0; + if (!this.props.Document.isAnimating && this.props.Document.autoHeight && scrollHeight !== 0 && + getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.props.Document.height, 0); - this.props.Document.height = Math.max(10, (nh ? dh / nh * sh : sh) + (ChromeHeight ? ChromeHeight() : 0)); - this.dataDoc.nativeHeight = nh ? sh : undefined; + this.props.Document.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); + this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; } } diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 63b412a23..19797400f 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -115,7 +115,6 @@ export class PDFBox extends DocAnnotatableComponent private newScriptChange = (e: React.ChangeEvent) => this._scriptValue = e.currentTarget.value; whenActiveChanged = (isActive: boolean) => this.props.whenActiveChanged(this._isChildActive = isActive); - active = () => this.props.isSelected() || this._isChildActive || this.props.renderDepth === 0; setPdfViewer = (pdfViewer: PDFViewer) => { this._pdfViewer = pdfViewer; }; searchStringChanged = (e: React.ChangeEvent) => this._searchString = e.currentTarget.value; @@ -205,7 +204,7 @@ export class PDFBox extends DocAnnotatableComponent _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"); + let classname = "pdfBox-cont" + (this.active() ? "-interactive" : ""); let noPdf = !(pdfUrl instanceof PdfField) || !this._pdf; if (this._initialScale === undefined) this._initialScale = this.props.ScreenToLocalTransform().Scale; if (this.props.isSelected() || this.props.Document.scrollY !== undefined) this._everActive = true; diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 73cac879e..8af6bbfd5 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -1,20 +1,17 @@ -import { action, computed, observable, runInAction, reaction } from "mobx"; +import { action, computed, observable, reaction, runInAction } from "mobx"; import * as rp from 'request-promise'; import { DocServer } from "../../../client/DocServer"; import { Docs } from "../../../client/documents/Documents"; import { Attribute, AttributeGroup, Catalog, Schema } from "../../../client/northstar/model/idea/idea"; import { ArrayUtil } from "../../../client/northstar/utils/ArrayUtil"; -import { CollectionViewType } from "../../../client/views/collections/CollectionBaseView"; -import { CollectionView } from "../../../client/views/collections/CollectionView"; +import { UndoManager } from "../../../client/util/UndoManager"; import { Doc, DocListCast } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; -import { Cast, StrCast, PromiseValue } from "../../../new_fields/Types"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { Cast, PromiseValue } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; import { RouteStore } from "../../RouteStore"; -import { ScriptField } from "../../../new_fields/ScriptField"; -import { ButtonBox } from "../../../client/views/nodes/ButtonBox"; -import { UndoManager } from "../../../client/util/UndoManager"; export class CurrentUserUtils { private static curr_id: string; @@ -22,176 +19,172 @@ export class CurrentUserUtils { private static mainDocId: string | undefined; public static get id() { return this.curr_id; } - @computed public static get UserDocument() { return Doc.UserDoc(); } public static get MainDocId() { return this.mainDocId; } public static set MainDocId(id: string | undefined) { this.mainDocId = id; } + @computed public static get UserDocument() { return Doc.UserDoc(); } @observable public static GuestTarget: Doc | undefined; @observable public static GuestWorkspace: Doc | undefined; private static createUserDocument(id: string): Doc { let doc = new Doc(id, true); - doc.viewType = CollectionViewType.Tree; - doc.layout = CollectionView.LayoutString(); doc.title = Doc.CurrentUserEmail; - doc.data = new List(); - doc.gridGap = 5; - doc.xMargin = 5; - doc.yMargin = 5; - doc.height = 42; - doc.boxShadow = "0 0"; - doc.convertToButtons = true; // for CollectionLinearView used as the docButton layout - doc.optionalRightCollection = Docs.Create.StackingDocument([], { title: "New mobile uploads" }); return this.updateUserDocument(doc);// this should be the last } - static updateUserDocument(doc: Doc) { + // a default set of note types .. not being used yet... + static setupNoteTypes(doc: Doc) { + let notes = [ + Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }), + Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }), + Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }), + Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true }) + ]; + doc.noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 }); + } - if (doc.undoBtn === undefined) { - doc.undoBtn = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Collection", icon: "undo-alt" }); - (doc.undoBtn as Doc).onClick = ScriptField.MakeScript('undo()'); - Doc.AddDocToList(doc, "docButtons", doc.undoBtn as Doc); - } - if (doc.redoBtn === undefined) { - doc.redoBtn = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Collection", icon: "redo-alt" }); - (doc.redoBtn as Doc).onClick = ScriptField.MakeScript('redo()'); - Doc.AddDocToList(doc, "docButtons", doc.redoBtn as Doc); - } + // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools + static setupCreatorButtons() { + let docProtoData = [ + { title: "collection", icon: "folder", script: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' }, + { title: "web page", icon: "globe-asia", script: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' }, + { title: "image", icon: "cat", script: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' }, + { title: "button", icon: "bolt", script: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' }, + { title: "presentation", icon: "tv", script: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })' }, + { title: "import folder", icon: "cloud-upload-alt", script: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' }, + ]; + return docProtoData.map(data => Docs.Create.FontIconDocument({ + nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, + title: data.title, icon: data.icon, onDragStart: ScriptField.MakeFunction(data.script) + })); + } + + // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. when clicked, this panel will be displayed in the target container (ie, sidebarContainer) + static setupCreatePanel(sidebarContainer: Doc) { + // setup a masonry view of all he creators + const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(), { + width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons" + }); + // setup a color picker + const color = Docs.Create.ColorDocument({ + title: "color picker", width: 400, removeDropProperties: new List(["dropAction", "forceActive"]) + }); + color.dropAction = "alias"; // these must be set on the view document so they can't be part of the creator above. + color.forceActive = true; + + return Docs.Create.ButtonDocument({ + width: 35, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Create", targetContainer: sidebarContainer, + panel: Docs.Create.StackingDocument([dragCreators, color], { + width: 500, height: 800, chromeStatus: "disabled", title: "creator stack" + }), + onClick: ScriptField.MakeScript("this.targetContainer.proto = this.panel") + }); + } + + // setup the Library button which will display the library panel. This panel includes a collection of workspaces, documents, and recently closed views + static setupLibraryPanel(sidebarContainer: Doc, doc: Doc) { // setup workspaces library item - if (doc.workspaces === undefined) { - const workspaces = Docs.Create.TreeDocument([], { title: "WORKSPACES", height: 100 }); - workspaces.boxShadow = "0 0"; - doc.workspaces = workspaces; - } - PromiseValue(Cast(doc.workspaces, Doc)).then(workspaces => { - if (workspaces) { - workspaces.backgroundColor = "#eeeeee"; - workspaces.preventTreeViewOpen = true; - workspaces.forceActive = true; - workspaces.lockedPosition = true; - if (StrCast(workspaces.title) === "Workspaces") { - workspaces.title = "WORKSPACES"; - } - } + doc.workspaces = Docs.Create.TreeDocument([], { + title: "WORKSPACES", height: 100, forceActive: true, boxShadow: "0 0", lockedPosition: true, backgroundColor: "#eeeeee" }); - // setup notes list - if (doc.noteTypes === undefined) { - let notes = [Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }), - Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }), - Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }), - Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true })]; - const noteTypes = Docs.Create.TreeDocument(notes, { title: "Note Types", height: 75 }); - doc.noteTypes = noteTypes; - } - PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(DocListCast)); + doc.documents = Docs.Create.TreeDocument([], { + title: "DOCUMENTS", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0", backgroundColor: "#eeeeee", preventTreeViewOpen: true, forceActive: true, lockedPosition: true + }); // setup Recently Closed library item - if (doc.recentlyClosed === undefined) { - const recentlyClosed = Docs.Create.TreeDocument([], { title: "Recently Closed".toUpperCase(), height: 75 }); - recentlyClosed.boxShadow = "0 0"; - doc.recentlyClosed = recentlyClosed; - } - PromiseValue(Cast(doc.recentlyClosed, Doc)).then(recent => { - if (recent) { - recent.backgroundColor = "#eeeeee"; - recent.preventTreeViewOpen = true; - recent.forceActive = true; - recent.lockedPosition = true; - if (StrCast(recent.title) === "Recently Closed") { - recent.title = "RECENTLY CLOSED"; - } - } + doc.recentlyClosed = Docs.Create.TreeDocument([], { + title: "Recently Closed".toUpperCase(), height: 75, boxShadow: "0 0", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, backgroundColor: "#eeeeee" }); + return Docs.Create.ButtonDocument({ + width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Library", + panel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], { + title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true + }), + targetContainer: sidebarContainer, + onClick: ScriptField.MakeScript("this.targetContainer.proto = this.panel") + }); + } - if (doc.curPresentation === undefined) { - const curPresentation = Docs.Create.PresDocument(new List(), { title: "Presentation" }); - curPresentation.boxShadow = "0 0"; - doc.curPresentation = curPresentation; - } + // setup the Search button which will display the search panel. + static setupSearchPanel(sidebarContainer: Doc) { + return Docs.Create.ButtonDocument({ + width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Search", + panel: Docs.Create.QueryDocument({ + title: "search stack", ignoreClick: true + }), + targetContainer: sidebarContainer, + onClick: ScriptField.MakeScript("this.targetContainer.proto = this.panel") + }); + } - if (doc.Library === undefined) { - let Search = Docs.Create.ButtonDocument({ width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Search" }); - let Library = Docs.Create.ButtonDocument({ width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Library" }); - let Create = Docs.Create.ButtonDocument({ width: 35, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Create" }); - if (doc.sidebarContainer === undefined) { - doc.sidebarContainer = new Doc(); - (doc.sidebarContainer as Doc).chromeStatus = "disabled"; - } + // setup the list of sidebar mode buttons which determine what is displayed in the sidebar + static setupSidebarButtons(doc: Doc) { + doc.sidebarContainer = new Doc(); + (doc.sidebarContainer as Doc).chromeStatus = "disabled"; - const library = Docs.Create.TreeDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], { title: "Library" }); - library.forceActive = true; - library.lockedPosition = true; - library.gridGap = 5; - library.xMargin = 5; - library.yMargin = 5; - library.dropAction = "alias"; - Library.targetContainer = doc.sidebarContainer; - Library.library = library; - Library.onClick = ScriptField.MakeScript("this.targetContainer.proto = this.library"); + doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc); + doc.LibraryBtn = this.setupLibraryPanel(doc.sidebarContainer as Doc, doc); + doc.SearchBtn = this.setupSearchPanel(doc.sidebarContainer as Doc); - const searchBox = Docs.Create.QueryDocument({ title: "search stack" }); - searchBox.ignoreClick = true; - Search.searchBox = searchBox; - Search.targetContainer = doc.sidebarContainer; - Search.onClick = ScriptField.MakeScript("this.targetContainer.proto = this.searchBox"); + // Finally, setup the list of buttons to display in the sidebar + doc.sidebarButtons = Docs.Create.StackingDocument([doc.SearchBtn as Doc, doc.LibraryBtn as Doc, doc.CreateBtn as Doc], { + width: 500, height: 80, boxShadow: "0 0", sectionFilter: "title", hideHeadings: true, ignoreClick: true, + backgroundColor: "lightgrey", chromeStatus: "disabled", title: "library stack" + }); + } - let createCollection = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Collection", icon: "folder" }); - createCollection.onDragStart = ScriptField.MakeFunction('Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })'); - let createWebPage = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Web Page", icon: "globe-asia" }); - createWebPage.onDragStart = ScriptField.MakeFunction('Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })'); - let createCatImage = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Image", icon: "cat" }); - createCatImage.onDragStart = ScriptField.MakeFunction('Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })'); - let createButton = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Button", icon: "bolt" }); - createButton.onDragStart = ScriptField.MakeFunction('Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })'); - let createPresentation = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Presentation", icon: "tv" }); - createPresentation.onDragStart = ScriptField.MakeFunction('Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })'); - let createFolderImport = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, title: "Import Folder", icon: "cloud-upload-alt" }); - createFolderImport.onDragStart = ScriptField.MakeFunction('Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })'); - const dragCreators = Docs.Create.MasonryDocument([createCollection, createWebPage, createCatImage, createButton, createPresentation, createFolderImport], { width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons" }); - const color = Docs.Create.ColorDocument({ title: "color picker", width: 400 }); - color.dropAction = "alias"; - color.ignoreClick = true; - color.removeDropProperties = new List(["dropAction", "ignoreClick"]); - const creators = Docs.Create.StackingDocument([dragCreators, color], { width: 500, height: 800, chromeStatus: "disabled", title: "creator stack" }); - Create.targetContainer = doc.sidebarContainer; - Create.creators = creators; - Create.onClick = ScriptField.MakeScript("this.targetContainer.proto = this.creators"); + /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window + static setupExpandingButtons(doc: Doc) { + doc.undoBtn = Docs.Create.FontIconDocument( + { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" }); + doc.redoBtn = Docs.Create.FontIconDocument( + { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" }); - const libraryButtons = Docs.Create.StackingDocument([Search, Library, Create], { width: 500, height: 80, chromeStatus: "disabled", title: "library stack" }); - libraryButtons.sectionFilter = "title"; - libraryButtons.boxShadow = "0 0"; - libraryButtons.ignoreClick = true; - libraryButtons.hideHeadings = true; - libraryButtons.backgroundColor = "lightgrey"; + doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], { + title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0", + backgroundColor: "#eeeeee", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, convertToButtons: true + }); + } - doc.libraryButtons = libraryButtons; - doc.Library = Library; - doc.Create = Create; - doc.Search = Search; - } - PromiseValue(Cast(doc.libraryButtons, Doc)).then(libraryButtons => { }); - PromiseValue(Cast(doc.Library, Doc)).then(library => library && library.library && library.targetContainer && (library.onClick as ScriptField).script.run({ this: library })); - PromiseValue(Cast(doc.Create, Doc)).then(async create => create && create.creators && create.targetContainer); - PromiseValue(Cast(doc.Search, Doc)).then(async search => search && search.searchBox && search.targetContainer); + // sets up the default set of documents to be shown in the Overlay layer + static setupOverlays(doc: Doc) { + doc.overlays = Docs.Create.FreeformDocument([], { title: "Overlays", backgroundColor: "#aca3a6" }); + doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" }); + Doc.AddDocToList(doc.overlays as Doc, "data", doc.linkFollowBox as Doc); + } - if (doc.overlays === undefined) { - const overlays = Docs.Create.FreeformDocument([], { title: "Overlays" }); - Doc.GetProto(overlays).backgroundColor = "#aca3a6"; - doc.overlays = overlays; - } + // the initial presentation Doc to use + static setupDefaultPresentation(doc: Doc) { + doc.curPresentation = Docs.Create.PresDocument(new List(), { title: "Presentation", boxShadow: "0 0" }); + } - if (doc.linkFollowBox === undefined) { - PromiseValue(Cast(doc.overlays, Doc)).then(overlays => overlays && Doc.AddDocToList(overlays, "data", doc.linkFollowBox = Docs.Create.LinkFollowBoxDocument({ x: 250, y: 20, width: 500, height: 370, title: "Link Follower" }))); - } + static setupMobileUploads(doc: Doc) { + doc.optionalRightCollection = Docs.Create.StackingDocument([], { title: "New mobile uploads" }); + } + + static updateUserDocument(doc: Doc) { + (doc.optionalRightCollection === undefined) && CurrentUserUtils.setupMobileUploads(doc); + (doc.noteTypes === undefined) && CurrentUserUtils.setupNoteTypes(doc); + (doc.overlays === undefined) && CurrentUserUtils.setupOverlays(doc); + (doc.expandingButtons === undefined) && CurrentUserUtils.setupExpandingButtons(doc); + (doc.curPresentation === undefined) && CurrentUserUtils.setupDefaultPresentation(doc); + (doc.sidebarButtons === undefined) && CurrentUserUtils.setupSidebarButtons(doc); + + // this is equivalent to using PrefetchProxies to make sure all the sidebarButtons and noteType internal Doc's have been retrieved. + PromiseValue(Cast(doc.noteTypes, Doc)).then(noteTypes => noteTypes && PromiseValue(noteTypes.data).then(DocListCast)); + PromiseValue(Cast(doc.sidebarButtons, Doc)).then(stackingDoc => { + stackingDoc && PromiseValue(Cast(stackingDoc.data, listSpec(Doc))).then(sidebarButtons => { + sidebarButtons && sidebarButtons.map((sidebarBtn, i) => { + sidebarBtn && PromiseValue(Cast(sidebarBtn, Doc)).then(async btn => { + btn && btn.panel && btn.targetContainer && i === 1 && (btn.onClick as ScriptField).script.run({ this: btn }); + }); + }); + }); + }); - doc.title = "DOCUMENTS"; - doc.backgroundColor = "#eeeeee"; - doc.width = 100; - doc.preventTreeViewOpen = true; - doc.forceActive = true; - doc.lockedPosition = true; + // setup reactions to change the highlights on the undo/redo buttons -- would be better to encode this in the undo/redo buttons, but the undo/redo stacks are not wired up that way yet doc.undoBtn && reaction(() => UndoManager.undoStack.slice(), () => (doc.undoBtn as Doc).opacity = UndoManager.CanUndo() ? 1 : 0.4, { fireImmediately: true }); doc.redoBtn && reaction(() => UndoManager.redoStack.slice(), () => (doc.redoBtn as Doc).opacity = UndoManager.CanRedo() ? 1 : 0.4, { fireImmediately: true }); @@ -215,12 +208,8 @@ export class CurrentUserUtils { await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => { if (id && id !== "guest") { return DocServer.GetRefField(id).then(async field => { - if (field instanceof Doc) { - await this.updateUserDocument(field); - runInAction(() => Doc.SetUserDoc(field)); - } else { - runInAction(() => Doc.SetUserDoc(this.createUserDocument(id))); - } + let userDoc = field instanceof Doc ? await this.updateUserDocument(field) : this.createUserDocument(id); + runInAction(() => Doc.SetUserDoc(userDoc)); }); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); -- cgit v1.2.3-70-g09d2 From 33811c112c7e479813908ba10f72813954a3e289 Mon Sep 17 00:00:00 2001 From: bob Date: Tue, 15 Oct 2019 16:25:10 -0400 Subject: working version of inking buttons --- src/client/documents/Documents.ts | 2 ++ src/client/util/DragManager.ts | 4 +-- src/client/util/SelectionManager.ts | 11 -------- src/client/views/CollectionLinearView.tsx | 4 +-- src/client/views/InkingControl.tsx | 6 ++-- src/client/views/MainView.tsx | 6 ++-- src/client/views/nodes/ColorBox.tsx | 33 ++++++++++++++++++++-- src/client/views/nodes/DocumentView.tsx | 7 +++-- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 16 ++++++----- src/new_fields/Doc.ts | 4 +++ .../authentication/models/current_user_utils.ts | 31 +++++++++++--------- 12 files changed, 78 insertions(+), 48 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 6df172f46..f4fce34ac 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -93,6 +93,8 @@ export interface DocumentOptions { autoHeight?: boolean; removeDropProperties?: List; // list of properties that should be removed from a document when it is dropped. e.g., a creator button may be forceActive to allow it be dragged, but the forceActive property can be removed from the dropped document dbDoc?: Doc; + unchecked?: ScriptField; // returns whether a check box is unchecked + activePen?: Doc; // which pen document is currently active (used as the radio button state for the 'unhecked' pen tool scripts) onClick?: ScriptField; onDragStart?: ScriptField; //script to execute at start of drag operation -- e.g., when a "creator" button is dragged this script generates a different document to drop icon?: string; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 169f2edec..92666c03c 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -234,9 +234,9 @@ export namespace DragManager { export let StartDragFunctions: (() => void)[] = []; - export async function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { + export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { runInAction(() => StartDragFunctions.map(func => func())); - await dragData.draggedDocuments.map(d => d.dragFactory); + dragData.draggedDocuments.map(d => d.dragFactory); // does this help? trying to make sure the dragFactory Doc is loaded StartDrag(eles, dragData, downX, downY, options, options && options.finishDrag ? options.finishDrag : (dropData: { [id: string]: any }) => { (dropData.droppedDocuments = diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index df1b46b33..2d717ca57 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -45,17 +45,6 @@ export namespace SelectionManager { } const manager = new Manager(); - reaction(() => manager.SelectedDocuments, sel => { - let targetColor = "#FFFFFF"; - if (sel.length > 0) { - let firstView = sel[0]; - let doc = firstView.props.Document; - let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc); - let stored = StrCast(targetDoc.backgroundColor); - stored.length > 0 && (targetColor = stored); - } - InkingControl.Instance.updateSelectedColor(targetColor); - }, { fireImmediately: true }); export function DeselectDoc(docView: DocumentView): void { manager.DeselectDoc(docView); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 9d1dd7749..eb3c768d0 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -48,7 +48,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { drop = action((e: Event, de: DragManager.DropEvent) => { (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => { let dbox = doc; - if (!doc.onDragStart && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { + if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: "bolt" }); dbox.dragFactory = doc; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; @@ -90,7 +90,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { return
(); @computed get docButtons() { - console.log("stuff = " + this.flyoutWidthFunc() + " " + this.getContentsHeight()); if (CurrentUserUtils.UserDocument.expandingButtons instanceof Doc) { - return
+ return
; const ColorDocument = makeInterface(documentSchema); @@ -14,9 +17,35 @@ const ColorDocument = makeInterface(documentSchema); @observer export class ColorBox extends DocStaticComponent(ColorDocument) { public static LayoutString(fieldKey?: string) { return FieldView.LayoutString(ColorBox, fieldKey); } + _selectedDisposer: IReactionDisposer | undefined; + componentDidMount() { + this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(), + action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"), + { fireImmediately: true }); + + // compare to this reaction that used to be in Selection Manager + // reaction(() => manager.SelectedDocuments, sel => { + // let targetColor = "#FFFFFF"; + // if (sel.length > 0) { + // let firstView = sel[0]; + // let doc = firstView.props.Document; + // let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc); + // let stored = StrCast(targetDoc.backgroundColor); + // stored.length > 0 && (targetColor = stored); + // } + // InkingControl.Instance.updateSelectedColor(targetColor); + // }, { fireImmediately: true }); + } + componentWillUnmount() { + this._selectedDisposer && this._selectedDisposer(); + } + + @observable _startupColor = "black"; + render() { - return
e.button === 0 && !e.ctrlKey && e.stopPropagation()}> - + return
e.button === 0 && !e.ctrlKey && e.stopPropagation()}> +
; } } \ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index c0e5185c1..6f99c541f 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -107,7 +107,7 @@ export const documentSchema = createSchema({ removeDropProperties: listSpec("string"), // properties that should be removed from the alias/copy/etc of this document when it is dropped onClick: ScriptField, // script to run when document is clicked (can be overriden by an onClick prop) onDragStart: ScriptField, // script to run when document is dragged (without being selected). the script should return the Doc to be dropped. - dragFactory: Doc, // the document that serves as the "template" for the onDragStart script + dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents isTemplate: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed @@ -676,6 +676,7 @@ export class DocumentView extends DocComponent(Docu const highlightColors = ["transparent", "maroon", "maroon", "yellow", "magenta", "cyan", "orange"]; const highlightStyles = ["solid", "dashed", "solid", "solid", "solid", "solid", "solid", "solid"]; + let highlighting = fullDegree && this.props.Document.type !== DocumentType.FONTICON && this.props.Document.viewType !== CollectionViewType.Linear return (
(Docu transition: this.props.Document.isAnimating !== undefined ? ".5s linear" : StrCast(this.Document.transition), pointerEvents: this.Document.isBackground && !this.isSelected() ? "none" : "all", color: StrCast(this.Document.color), - outline: fullDegree && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", - border: fullDegree && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, + outline: highlighting && !borderRounding ? `${highlightColors[fullDegree]} ${highlightStyles[fullDegree]} ${localScale}px` : "solid 0px", + border: highlighting && borderRounding ? `${highlightStyles[fullDegree]} ${highlightColors[fullDegree]} ${localScale}px` : undefined, background: this.props.Document.type === DocumentType.FONTICON || this.props.Document.viewType === CollectionViewType.Linear ? undefined : backgroundColor, width: animwidth, height: animheight, diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index aa442cd92..efe47d8a8 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -18,7 +18,7 @@ export class FontIconBox extends DocComponent( 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 181f37d36..1bdff3ec7 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -927,16 +927,18 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe 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) => { + let view = this._editorView as any; + // 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 (view.mouseDown) { + let originalUpHandler = view.mouseDown.up; + view.root.removeEventListener("mouseup", originalUpHandler); + view.mouseDown.up = (e: MouseEvent) => { !(e as any).formattedHandled && originalUpHandler(e); - e.stopPropagation(); + // e.stopPropagation(); (e as any).formattedHandled = true; }; - (this._editorView as any).root.addEventListener("mouseup", (this._editorView as any).mouseDown.up); + view.root.addEventListener("mouseup", view.mouseDown.up); } } diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 4bed113e3..276596fb8 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -525,6 +525,7 @@ export namespace Doc { export function MakeCopy(doc: Doc, copyProto: boolean = false, copyProtoId?: string): Doc { const copy = new Doc(copyProtoId, true); Object.keys(doc).forEach(key => { + let cfield = ComputedField.WithoutComputed(() => FieldValue(doc[key])); const field = ProxyField.WithoutProxy(() => doc[key]); if (key === "proto" && copyProto) { if (doc[key] instanceof Doc) { @@ -533,6 +534,8 @@ export namespace Doc { } else { if (field instanceof RefField) { copy[key] = field; + } else if (cfield instanceof ComputedField) { + copy[key] = ComputedField.MakeFunction(cfield.script.originalScript); } else if (field instanceof ObjectField) { copy[key] = ObjectField.MakeCopy(field); } else if (field instanceof Promise) { @@ -735,5 +738,6 @@ Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return Doc.Make Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); Scripting.addGlobal(function aliasDocs(field: any) { return new List(field.map((d: any) => Doc.MakeAlias(d))); }); Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); +Scripting.addGlobal(function sameDocs(doc1: any, doc2: any) { return Doc.AreProtosEqual(doc1, doc2) }); Scripting.addGlobal(function undo() { return UndoManager.Undo(); }); Scripting.addGlobal(function redo() { return UndoManager.Redo(); }); \ No newline at end of file diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index c4b91dadd..3c4a46ed8 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -8,7 +8,7 @@ import { UndoManager } from "../../../client/util/UndoManager"; import { Doc, DocListCast } from "../../../new_fields/Doc"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; -import { ScriptField } from "../../../new_fields/ScriptField"; +import { ScriptField, ComputedField } from "../../../new_fields/ScriptField"; import { Cast, PromiseValue } from "../../../new_fields/Types"; import { Utils } from "../../../Utils"; import { RouteStore } from "../../RouteStore"; @@ -45,29 +45,32 @@ export class CurrentUserUtils { } // setup the "creator" buttons for the sidebar-- eg. the default set of draggable document creation tools - static setupCreatorButtons() { - let docProtoData: { title: string, icon: string, drag?: string, click?: string }[] = [ + static setupCreatorButtons(doc: Doc) { + doc.activePen = doc; + let docProtoData: { title: string, icon: string, drag?: string, click?: string, unchecked?: string, activePen?: Doc, backgroundColor?: string }[] = [ { title: "collection", icon: "folder", drag: 'Docs.Create.FreeformDocument([], { nativeWidth: undefined, nativeHeight: undefined, width: 150, height: 100, title: "freeform" })' }, { title: "web page", icon: "globe-asia", drag: 'Docs.Create.WebDocument("https://en.wikipedia.org/wiki/Hedgehog", { width: 300, height: 300, title: "New Webpage" })' }, { title: "image", icon: "cat", drag: 'Docs.Create.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", { width: 200, title: "an image of a cat" })' }, { title: "button", icon: "bolt", drag: 'Docs.Create.ButtonDocument({ width: 150, height: 50, title: "Button" })' }, { title: "presentation", icon: "tv", drag: 'Doc.UserDoc().curPresentation = Docs.Create.PresDocument(new List(), { width: 200, height: 500, title: "a presentation trail" })' }, { title: "import folder", icon: "cloud-upload-alt", drag: 'Docs.Create.DirectoryImportDocument({ title: "Directory Import", width: 400, height: 400 })' }, - { title: "pen", icon: "pen-nib", click: 'activatePen(); setInkWidth(2);' }, - { title: "highlighter", icon: "pen", click: 'activateBrush(); setInkWidth(20);' }, - { title: "eraser", icon: "eraser", click: 'activateEraser();' }, - { title: "none", icon: "pause", click: 'deactivateInk();' }, + { title: "pen", icon: "pen-nib", click: 'activatePen(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,2, this.backgroundColor)', backgroundColor: "blue", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "highlighter", icon: "pen", click: 'activateBrush(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this,20,this.backgroundColor)', backgroundColor: "yellow", unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "eraser", icon: "eraser", click: 'activateEraser(this.activePen.pen = sameDocs(this.activePen.pen, this) ? undefined : this);', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, + { title: "none", icon: "pause", click: 'deactivateInk();this.activePen.pen = this;', unchecked: `!sameDocs(this.activePen.pen, this)`, activePen: doc }, ]; return docProtoData.map(data => Docs.Create.FontIconDocument({ - nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "alias" : undefined, - title: data.title, icon: data.icon, onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined + nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: data.click ? "copy" : undefined, title: data.title, icon: data.icon, + onDragStart: data.drag ? ScriptField.MakeFunction(data.drag) : undefined, onClick: data.click ? ScriptField.MakeScript(data.click) : undefined, + unchecked: data.unchecked ? ComputedField.MakeFunction(data.unchecked) : undefined, activePen: data.activePen, + backgroundColor: data.backgroundColor })); } // setup the Creator button which will display the creator panel. This panel will include the drag creators and the color picker. when clicked, this panel will be displayed in the target container (ie, sidebarContainer) - static setupCreatePanel(sidebarContainer: Doc) { + static setupCreatePanel(sidebarContainer: Doc, doc: Doc) { // setup a masonry view of all he creators - const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(), { + const dragCreators = Docs.Create.MasonryDocument(CurrentUserUtils.setupCreatorButtons(doc), { width: 500, autoHeight: true, columnWidth: 35, ignoreClick: true, lockedPosition: true, chromeStatus: "disabled", title: "buttons" }); // setup a color picker @@ -129,7 +132,7 @@ export class CurrentUserUtils { doc.sidebarContainer = new Doc(); (doc.sidebarContainer as Doc).chromeStatus = "disabled"; - doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc); + doc.CreateBtn = this.setupCreatePanel(doc.sidebarContainer as Doc, doc); doc.LibraryBtn = this.setupLibraryPanel(doc.sidebarContainer as Doc, doc); doc.SearchBtn = this.setupSearchPanel(doc.sidebarContainer as Doc); @@ -143,9 +146,9 @@ export class CurrentUserUtils { /// sets up the default list of buttons to be shown in the expanding button menu at the bottom of the Dash window static setupExpandingButtons(doc: Doc) { doc.undoBtn = Docs.Create.FontIconDocument( - { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" }); + { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("undo()"), title: "undo button", icon: "undo-alt" }); doc.redoBtn = Docs.Create.FontIconDocument( - { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" }); + { nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, dropAction: "alias", onClick: ScriptField.MakeScript("redo()"), title: "redo button", icon: "redo-alt" }); doc.expandingButtons = Docs.Create.LinearDocument([doc.undoBtn as Doc, doc.redoBtn as Doc], { title: "expanding buttons", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0", -- cgit v1.2.3-70-g09d2 From 57ccb3e236a7dad71848a06dad757c5a5a081ce8 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 16 Oct 2019 12:34:47 -0400 Subject: starting to switch document embedding to alias dragging. --- src/client/util/DragManager.ts | 1 + src/client/views/DocumentButtonBar.tsx | 21 ++++++++++----------- src/client/views/collections/CollectionSubView.tsx | 3 +++ .../collectionFreeForm/CollectionFreeFormView.tsx | 15 +++++++++++++++ src/client/views/nodes/FormattedTextBox.tsx | 13 +++++-------- 5 files changed, 34 insertions(+), 19 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index dcd19e50b..29a75b9ae 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -311,6 +311,7 @@ export namespace DragManager { this.urlField = embeddableSourceDoc.data instanceof URLField ? embeddableSourceDoc.data : undefined; } embeddableSourceDoc: Doc; + embeddedDoc?: Doc; urlField?: URLField; [id: string]: any; } diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 9e2d41621..5561d699c 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -164,8 +164,12 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], let dragDocView = this.props.views[0]; let dragData = new DragManager.EmbedDragData(dragDocView.props.Document); + const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); + dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top); DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, { + offsetX: dragData.offset[0], + offsetY: dragData.offset[1], handlers: { dragComplete: action(emptyFunction), }, @@ -199,17 +203,12 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], e.stopPropagation(); } - considerEmbed = () => { - let thisDoc = this.props.views[0].props.Document; - let canEmbed = thisDoc.data && thisDoc.data instanceof URLField; - // if (!canEmbed) return (null); - return ( -
-
- -
+ embedDragger = () => { + return (
+
+
- ); +
); } private get targetDoc() { @@ -353,7 +352,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
{this.metadataMenu} - {this.considerEmbed()} + {this.embedDragger()} {this.considerGoogleDocsPush()} {this.considerGoogleDocsPull()} { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 9919a9dc3..077142e88 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -143,6 +143,9 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { e.stopPropagation(); return added; } + else if (de.data instanceof DragManager.EmbedDragData) { + return this.props.addDocument(de.data.embeddedDoc = Doc.MakeAlias(de.data.embeddableSourceDoc)); + } else if (de.data instanceof DragManager.AnnotationDragData) { e.stopPropagation(); return this.props.addDocument(de.data.dropDocument); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index adbad5da5..46aaed509 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -153,6 +153,21 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]); } } + else if (de.data instanceof DragManager.EmbedDragData && de.data.embeddedDoc) { + const d = de.data.embeddedDoc; + d.x = xp; + d.y = yp; + if (!NumCast(d.width)) { + d.width = 300; + } + if (!NumCast(d.height)) { + let nw = NumCast(d.nativeWidth); + let nh = NumCast(d.nativeHeight); + d.height = nw && nh ? nh / nw * NumCast(d.width) : 300; + } + this.bringToFront(d); + this.updateCluster(d); + } else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 1bdff3ec7..8153cf61c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -249,16 +249,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - // We're dealing with a link to a document - if (de.data instanceof DragManager.EmbedDragData) { - let target = de.data.embeddableSourceDoc; - // We're dealing with an internal document drop - const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "ImgRef:" + target.title); - let alias = Doc.MakeAlias(target); - alias.fitToBox = true; + if (de.data instanceof DragManager.EmbedDragData || (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction)) { + let target = de.data instanceof DragManager.DocumentDragData ? de.data.droppedDocuments[0] : Doc.MakeAlias(de.data.embeddableSourceDoc); + const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); + target.fitToBox = true; let node = schema.nodes.dashDoc.create({ width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: alias[Id], + title: "dashDoc", docid: target[Id], float: "right" }); let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); -- cgit v1.2.3-70-g09d2 From fb817995eb94727b11324f298d0a30eebda8dcb7 Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 16 Oct 2019 12:47:09 -0400 Subject: removed DragEmbed -- subsumed by dragging aliases. --- src/client/util/DragManager.ts | 18 ++-------- src/client/views/DocumentButtonBar.tsx | 39 +++++++++++----------- src/client/views/collections/CollectionSubView.tsx | 3 -- .../collectionFreeForm/CollectionFreeFormView.tsx | 15 --------- src/client/views/nodes/FormattedTextBox.tsx | 4 +-- 5 files changed, 23 insertions(+), 56 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 29a75b9ae..12e0b11ba 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -211,6 +211,7 @@ export namespace DragManager { offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; + forceUserDropAction: dropActionType; moveDocument?: MoveFunction; isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts applyAsTemplate?: boolean; @@ -305,17 +306,6 @@ export namespace DragManager { [id: string]: any; } - export class EmbedDragData { - constructor(embeddableSourceDoc: Doc) { - this.embeddableSourceDoc = embeddableSourceDoc; - this.urlField = embeddableSourceDoc.data instanceof URLField ? embeddableSourceDoc.data : undefined; - } - embeddableSourceDoc: Doc; - embeddedDoc?: Doc; - urlField?: URLField; - [id: string]: any; - } - // for column dragging in schema view export class ColumnDragData { constructor(colKey: SchemaHeaderField) { @@ -329,10 +319,6 @@ export namespace DragManager { StartDrag([ele], dragData, downX, downY, options); } - export function StartEmbedDrag(ele: HTMLElement, dragData: EmbedDragData, downX: number, downY: number, options?: DragOptions) { - StartDrag([ele], dragData, downX, downY, options); - } - export function StartColumnDrag(ele: HTMLElement, dragData: ColumnDragData, downX: number, downY: number, options?: DragOptions) { StartDrag([ele], dragData, downX, downY, options); } @@ -428,7 +414,7 @@ export namespace DragManager { const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { - dragData.userDropAction = e.ctrlKey || e.altKey ? "alias" : undefined; + dragData.userDropAction = dragData.forceUserDropAction ? dragData.forceUserDropAction : e.ctrlKey || e.altKey ? "alias" : undefined; } if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) { AbortDrag(); diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index 5561d699c..a00a4298f 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -6,7 +6,6 @@ import { observer } from "mobx-react"; import { Doc } from "../../new_fields/Doc"; import { RichTextField } from '../../new_fields/RichTextField'; import { NumCast } from "../../new_fields/Types"; -import { URLField } from '../../new_fields/URLField'; import { emptyFunction } from "../../Utils"; import { Pulls, Pushes } from '../apis/google_docs/GoogleApiClientUtils'; import { DragLinksAsDocuments, DragManager } from "../util/DragManager"; @@ -45,7 +44,7 @@ const fetch: IconProp = "sync-alt"; export class DocumentButtonBar extends React.Component<{ views: DocumentView[], stack?: any }, {}> { private _linkButton = React.createRef(); private _linkerButton = React.createRef(); - private _embedButton = React.createRef(); + private _aliasButton = React.createRef(); private _tooltipoff = React.createRef(); private _textDoc?: Doc; private _linkDrag?: UndoManager.Batch; @@ -111,13 +110,13 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], document.addEventListener("pointerup", this.onLinkerButtonUp); } - onEmbedButtonDown = (e: React.PointerEvent): void => { + onAliasButtonDown = (e: React.PointerEvent): void => { e.stopPropagation(); e.preventDefault(); - document.removeEventListener("pointermove", this.onEmbedButtonMoved); - document.addEventListener("pointermove", this.onEmbedButtonMoved); - document.removeEventListener("pointerup", this.onEmbedButtonUp); - document.addEventListener("pointerup", this.onEmbedButtonUp); + document.removeEventListener("pointermove", this.onAliasButtonMoved); + document.addEventListener("pointermove", this.onAliasButtonMoved); + document.removeEventListener("pointerup", this.onAliasButtonUp); + document.addEventListener("pointerup", this.onAliasButtonUp); } onLinkerButtonUp = (e: PointerEvent): void => { @@ -126,9 +125,9 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], e.stopPropagation(); } - onEmbedButtonUp = (e: PointerEvent): void => { - document.removeEventListener("pointermove", this.onEmbedButtonMoved); - document.removeEventListener("pointerup", this.onEmbedButtonUp); + onAliasButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onAliasButtonMoved); + document.removeEventListener("pointerup", this.onAliasButtonUp); e.stopPropagation(); } @@ -157,17 +156,17 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], } @action - onEmbedButtonMoved = (e: PointerEvent): void => { - if (this._embedButton.current !== null) { - document.removeEventListener("pointermove", this.onEmbedButtonMoved); - document.removeEventListener("pointerup", this.onEmbedButtonUp); + onAliasButtonMoved = (e: PointerEvent): void => { + if (this._aliasButton.current !== null) { + document.removeEventListener("pointermove", this.onAliasButtonMoved); + document.removeEventListener("pointerup", this.onAliasButtonUp); let dragDocView = this.props.views[0]; - let dragData = new DragManager.EmbedDragData(dragDocView.props.Document); + let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]); const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top); - - DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, { + dragData.forceUserDropAction = "alias"; + DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, e.x, e.y, { offsetX: dragData.offset[0], offsetY: dragData.offset[1], handlers: { @@ -203,9 +202,9 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], e.stopPropagation(); } - embedDragger = () => { + aliasDragger = () => { return (
-
+
); @@ -352,7 +351,7 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[],
{this.metadataMenu} - {this.embedDragger()} + {this.aliasDragger()} {this.considerGoogleDocsPush()} {this.considerGoogleDocsPull()} { diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 077142e88..9919a9dc3 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -143,9 +143,6 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { e.stopPropagation(); return added; } - else if (de.data instanceof DragManager.EmbedDragData) { - return this.props.addDocument(de.data.embeddedDoc = Doc.MakeAlias(de.data.embeddableSourceDoc)); - } else if (de.data instanceof DragManager.AnnotationDragData) { e.stopPropagation(); return this.props.addDocument(de.data.dropDocument); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 46aaed509..adbad5da5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -153,21 +153,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { de.data.droppedDocuments.length === 1 && this.updateCluster(de.data.droppedDocuments[0]); } } - else if (de.data instanceof DragManager.EmbedDragData && de.data.embeddedDoc) { - const d = de.data.embeddedDoc; - d.x = xp; - d.y = yp; - if (!NumCast(d.width)) { - d.width = 300; - } - if (!NumCast(d.height)) { - let nw = NumCast(d.nativeWidth); - let nh = NumCast(d.nativeHeight); - d.height = nw && nh ? nh / nw * NumCast(d.width) : 300; - } - this.bringToFront(d); - this.updateCluster(d); - } else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 8153cf61c..ae67e94cc 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -249,8 +249,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.EmbedDragData || (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction)) { - let target = de.data instanceof DragManager.DocumentDragData ? de.data.droppedDocuments[0] : Doc.MakeAlias(de.data.embeddableSourceDoc); + if (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction) { + let target = de.data.droppedDocuments[0]; const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); target.fitToBox = true; let node = schema.nodes.dashDoc.create({ -- cgit v1.2.3-70-g09d2 From 8a3cfa8d6e72c9bfea4b38760e0b138b6525574c Mon Sep 17 00:00:00 2001 From: bob Date: Wed, 16 Oct 2019 16:42:37 -0400 Subject: a bunch of fixes to layouts to support templates in paticular. --- src/client/util/DocumentManager.ts | 2 + src/client/util/DragManager.ts | 4 +- src/client/views/CollectionLinearView.tsx | 7 +- src/client/views/DocumentButtonBar.tsx | 3 +- src/client/views/DocumentDecorations.tsx | 47 +++++----- src/client/views/InkingControl.tsx | 9 +- .../views/collections/CollectionStackingView.tsx | 11 ++- .../views/collections/CollectionTreeView.scss | 1 + .../views/collections/CollectionTreeView.tsx | 17 ++-- .../collectionFreeForm/CollectionFreeFormView.tsx | 63 ++++++------- .../collections/collectionFreeForm/MarqueeView.tsx | 31 ++++--- .../views/nodes/CollectionFreeFormDocumentView.tsx | 4 +- src/client/views/nodes/DocumentView.tsx | 6 +- src/client/views/nodes/FontIconBox.tsx | 5 +- src/client/views/nodes/FormattedTextBox.tsx | 101 +++++++++++---------- src/new_fields/Doc.ts | 11 ++- 16 files changed, 178 insertions(+), 144 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index 00de39671..e23ac55c2 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -72,6 +72,8 @@ export class DocumentManager { toReturn = view; } }); + } else { + break; } } diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 12e0b11ba..06dab024e 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -211,7 +211,7 @@ export namespace DragManager { offset: number[]; dropAction: dropActionType; userDropAction: dropActionType; - forceUserDropAction: dropActionType; + embedDoc?: boolean; moveDocument?: MoveFunction; isSelectionMove?: boolean; // indicates that an explicitly selected Document is being dragged. this will suppress onDragStart scripts applyAsTemplate?: boolean; @@ -414,7 +414,7 @@ export namespace DragManager { const moveHandler = (e: PointerEvent) => { e.preventDefault(); // required or dragging text menu link item ends up dragging the link button as native drag/drop if (dragData instanceof DocumentDragData) { - dragData.userDropAction = dragData.forceUserDropAction ? dragData.forceUserDropAction : e.ctrlKey || e.altKey ? "alias" : undefined; + dragData.userDropAction = e.ctrlKey ? "alias" : undefined; } if (((options && !options.withoutShiftDrag) || !options) && e.shiftKey && CollectionDockingView.Instance) { AbortDrag(); diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index af3b194ea..44d9b042e 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -15,6 +15,7 @@ import { CollectionViewType } from './collections/CollectionBaseView'; import { CollectionSubView } from './collections/CollectionSubView'; import { documentSchema, DocumentView } from './nodes/DocumentView'; import { translate } from 'googleapis/build/src/apis/translate'; +import { DocumentType } from '../documents/DocumentTypes'; type LinearDocument = makeInterface<[typeof documentSchema,]>; @@ -50,8 +51,10 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => { let dbox = doc; if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { - dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: "bolt" }); - dbox.dragFactory = doc; + let template = doc.layout instanceof Doc && doc.layout.isTemplate ? doc.layout : doc; + template.isTemplate = (template.type === DocumentType.TEXT || template.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction; + dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: template.isTemplate ? "font" : "bolt" }); + dbox.dragFactory = template; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); } else if (doc.viewType === CollectionViewType.Linear) { diff --git a/src/client/views/DocumentButtonBar.tsx b/src/client/views/DocumentButtonBar.tsx index a00a4298f..959b120ed 100644 --- a/src/client/views/DocumentButtonBar.tsx +++ b/src/client/views/DocumentButtonBar.tsx @@ -165,7 +165,8 @@ export class DocumentButtonBar extends React.Component<{ views: DocumentView[], let dragData = new DragManager.DocumentDragData([dragDocView.props.Document]); const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); dragData.offset = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.clientX - left, e.clientY - top); - dragData.forceUserDropAction = "alias"; + dragData.embedDoc = true; + dragData.dropAction = "alias"; DragManager.StartDocumentDrag([dragDocView.ContentDiv!], dragData, e.x, e.y, { offsetX: dragData.offset[0], offsetY: dragData.offset[1], diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 755739a11..2f40ea746 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -471,47 +471,48 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> SelectionManager.SelectedDocuments().forEach(element => { if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { let doc = PositionDocument(element.props.Document); + let layoutDoc = PositionDocument(element.props.Document.layout instanceof Doc ? element.props.Document.layout : element.props.Document); let nwidth = doc.nativeWidth || 0; let nheight = doc.nativeHeight || 0; - let width = (doc.width || 0); - let height = (doc.height || (nheight / nwidth * width)); + let width = (layoutDoc.width || 0); + let height = (layoutDoc.height || (nheight / nwidth * width)); let scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling(); let actualdW = Math.max(width + (dW * scale), 20); let actualdH = Math.max(height + (dH * scale), 20); - doc.x = (doc.x || 0) + dX * (actualdW - width); - doc.y = (doc.y || 0) + dY * (actualdH - height); + layoutDoc.x = (layoutDoc.x || 0) + dX * (actualdW - width); + layoutDoc.y = (layoutDoc.y || 0) + dY * (actualdH - height); let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... - let fixedAspect = e.ctrlKey || (!doc.ignoreAspect && nwidth && nheight); - if (fixedAspect && e.ctrlKey && doc.ignoreAspect) { - doc.ignoreAspect = false; - proto.nativeWidth = nwidth = doc.width || 0; - proto.nativeHeight = nheight = doc.height || 0; + let fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight); + if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) { + layoutDoc.ignoreAspect = false; + proto.nativeWidth = nwidth = layoutDoc.width || 0; + proto.nativeHeight = nheight = layoutDoc.height || 0; } if (fixedAspect && (!nwidth || !nheight)) { - proto.nativeWidth = nwidth = doc.width || 0; - proto.nativeHeight = nheight = doc.height || 0; + proto.nativeWidth = nwidth = layoutDoc.width || 0; + proto.nativeHeight = nheight = layoutDoc.height || 0; } - if (nwidth > 0 && nheight > 0 && !BoolCast(doc.ignoreAspect)) { + if (nwidth > 0 && nheight > 0 && !layoutDoc.ignoreAspect) { if (Math.abs(dW) > Math.abs(dH)) { if (!fixedAspect) { - Doc.SetInPlace(element.props.Document, "nativeWidth", actualdW / (doc.width || 1) * (doc.nativeWidth || 0), true); + Doc.SetInPlace(doc, "nativeWidth", actualdW / (layoutDoc.width || 1) * (layoutDoc.nativeWidth || 0), true); } - doc.width = actualdW; - if (fixedAspect && !doc.fitWidth) doc.height = nheight / nwidth * doc.width; - else doc.height = actualdH; + layoutDoc.width = actualdW; + if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.height = nheight / nwidth * layoutDoc.width; + else layoutDoc.height = actualdH; } else { if (!fixedAspect) { - Doc.SetInPlace(element.props.Document, "nativeHeight", actualdH / (doc.height || 1) * (doc.nativeHeight || 0), true); + Doc.SetInPlace(doc, "nativeHeight", actualdH / (layoutDoc.height || 1) * (doc.nativeHeight || 0), true); } - doc.height = actualdH; - if (fixedAspect && !doc.fitWidth) doc.width = nwidth / nheight * doc.height; - else doc.width = actualdW; + layoutDoc.height = actualdH; + if (fixedAspect && !layoutDoc.fitWidth) layoutDoc.width = nwidth / nheight * layoutDoc.height; + else layoutDoc.width = actualdW; } } else { - dW && (doc.width = actualdW); - dH && (doc.height = actualdH); - dH && element.props.Document.autoHeight && Doc.SetInPlace(element.props.Document, "autoHeight", false, true); + dW && (layoutDoc.width = actualdW); + dH && (layoutDoc.height = actualdH); + dH && layoutDoc.autoHeight && Doc.SetInPlace(layoutDoc, "autoHeight", false, true); } } }); diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 51fc7ca8f..46c6fae1c 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -41,7 +41,9 @@ export class InkingControl { if (InkingControl.Instance.selectedTool === InkTool.None) { let selected = SelectionManager.SelectedDocuments(); let oldColors = selected.map(view => { - let targetDoc = view.props.Document.layout instanceof Doc ? view.props.Document.layout : view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); + let targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory : + view.props.Document.layout instanceof Doc ? view.props.Document.layout : + view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); let sel = window.getSelection(); if (StrCast(targetDoc.layout).indexOf("FormattedTextBox") !== -1 && (!sel || sel.toString() !== "")) { targetDoc.color = this._selectedColor; @@ -79,7 +81,10 @@ export class InkingControl { ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined; ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } - !ruleProvider && (targetDoc.backgroundColor = matchedColor); + if (!ruleProvider) { + if (targetDoc) + targetDoc.backgroundColor = matchedColor; + } return { target: targetDoc, diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 90fc9c3e0..cde1a5036 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -110,9 +110,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } componentDidMount() { - // is there any reason this needs to exist? -syip. yes, it handles autoHeight for stacking views (masonry isn't yet supported). this._heightDisposer = reaction(() => { - if (BoolCast(this.props.Document.autoHeight)) { + if (this.props.Document.autoHeight) { let sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]); if (this.isStackingView) { return this.props.ContentScaling() * sectionsList.reduce((maxHght, s) => Math.max(maxHght, @@ -188,15 +187,17 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } getDocHeight(d?: Doc) { if (!d) return 0; + let layoutDoc = d.layout instanceof Doc ? d.layout : d; let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); - if (!d.ignoreAspect && !d.fitWidth && nw && nh) { + if (!layoutDoc.ignoreAspect && !layoutDoc.fitWidth && nw && nh) { let aspect = nw && nh ? nh / nw : 1; - if (!(d.nativeWidth && !d.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(d[WidthSym](), wid); + if (!(d.nativeWidth && !layoutDoc.ignoreAspect && this.props.Document.fillColumn)) wid = Math.min(layoutDoc[WidthSym](), wid); return wid * aspect; } - return d.fitWidth ? !d.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin : Math.min(wid * NumCast(d.scrollHeight, NumCast(d.nativeHeight)) / NumCast(d.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : d[HeightSym](); + return layoutDoc.fitWidth ? !d.nativeHeight ? this.props.PanelHeight() - 2 * this.yMargin : + Math.min(wid * NumCast(layoutDoc.scrollHeight, NumCast(d.nativeHeight)) / NumCast(d.nativeWidth, 1), this.props.PanelHeight() - 2 * this.yMargin) : layoutDoc[HeightSym](); } columnDividerDown = (e: React.PointerEvent) => { diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index ca0c321b7..bff8ce5c1 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -16,6 +16,7 @@ background: $light-color-secondary; font-size: 13px; overflow: auto; + cursor: default; ul { list-style: none; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index abaa9662c..403da0e54 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -251,19 +251,21 @@ class TreeView extends React.Component { } docWidth = () => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); - if (aspect) return Math.min(this.props.document[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); - return NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; + let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; + if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); + return NumCast(this.props.document.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; } docHeight = () => { let bounds = this.boundsOfCollectionDocument; return Math.min(this.MAX_EMBED_HEIGHT, (() => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); + let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; if (aspect) return this.docWidth() * aspect; if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); - return this.props.document.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) : - Math.min(this.docWidth() * NumCast(this.props.document.scrollHeight, NumCast(this.props.document.nativeHeight)) / NumCast(this.props.document.nativeWidth, + return layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) : + Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(this.props.document.nativeHeight)) / NumCast(this.props.document.nativeWidth, NumCast(this.props.containingCollection.height)))) : - NumCast(this.props.document.height) ? NumCast(this.props.document.height) : 50; + NumCast(layoutDoc.height) ? NumCast(layoutDoc.height) : 50; })()); } @@ -461,10 +463,11 @@ class TreeView extends React.Component { let rowWidth = () => panelWidth() - 20; return docs.map((child, i) => { - let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); + const pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); if (!pair.layout || pair.data instanceof Promise) { return (null); } + const childLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; let indent = i === 0 ? undefined : () => { if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) { @@ -482,7 +485,7 @@ class TreeView extends React.Component { }; let rowHeight = () => { let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0); - return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym](); + return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); }; return { - d.x = x + NumCast(d.x) - dropX; - d.y = y + NumCast(d.y) - dropY; - if (!NumCast(d.width)) { - d.width = 300; + let layoutDoc = d.layout instanceof Doc ? d.layout : d; + layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; + layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + if (!NumCast(layoutDoc.width)) { + layoutDoc.width = 300; } if (!NumCast(d.height)) { let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); - d.height = nw && nh ? nh / nw * NumCast(d.width) : 300; + layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300; } this.bringToFront(d); })); @@ -156,14 +157,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; + let layoutDoc = dragDoc.layout instanceof Doc ? dragDoc.layout : dragDoc; let x = xp - de.data.offset[0]; let y = yp - de.data.offset[1]; - let dropX = NumCast(de.data.dropDocument.x); - let dropY = NumCast(de.data.dropDocument.y); - dragDoc.x = x + NumCast(dragDoc.x) - dropX; - dragDoc.y = y + NumCast(dragDoc.y) - dropY; - de.data.targetContext = this.props.Document; - dragDoc.targetContext = this.props.Document; + let dropX = NumCast(layoutDoc.x); + let dropY = NumCast(layoutDoc.y); + layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; + layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation this.bringToFront(dragDoc); } } @@ -173,11 +174,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { pickCluster(probe: number[]) { return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { - let cx = NumCast(cd.x) - this._clusterDistance; - let cy = NumCast(cd.y) - this._clusterDistance; - let cw = NumCast(cd.width) + 2 * this._clusterDistance; - let ch = NumCast(cd.height) + 2 * this._clusterDistance; - return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? + let layoutDoc = cd.layout instanceof Doc ? cd.layout : cd; + let cx = NumCast(layoutDoc.x) - this._clusterDistance; + let cy = NumCast(layoutDoc.y) - this._clusterDistance; + let cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance; + let ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance; + return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? NumCast(cd.cluster) : cluster; }, -1); } @@ -185,14 +187,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let cluster = this.pickCluster(this.getTransform().transformPoint(e.clientX, e.clientY)); if (cluster !== -1) { let eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => NumCast(cd.cluster) === cluster); - - // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view - let prevSelected = SelectionManager.SelectedDocuments(); - this.selectDocuments(eles); - let clusterDocs = SelectionManager.SelectedDocuments(); - SelectionManager.DeselectAll(); - prevSelected.map(dv => SelectionManager.SelectDoc(dv, true)); - + let clusterDocs = eles.map(ele => DocumentManager.Instance.getDocumentView(ele, this.props.CollectionView)!); let de = new DragManager.DocumentDragData(eles); de.moveDocument = this.props.moveDocument; const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0); @@ -223,8 +218,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; this._clusterSets.map((set, i) => set.map(member => { - if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { + let memberLayout = member.layout instanceof Doc ? member.layout : member; + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(layoutDoc, memberLayout, this._clusterDistance)) { doc.cluster = i; } })); @@ -296,10 +293,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let x = this.Document.panX || 0; let y = this.Document.panY || 0; - let docs = this.childLayoutPairs.map(pair => pair.layout); + let docs = this.childLayoutPairs.map(pair => pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay) { - PDFMenu.Instance.fadeOut(true); let minx = docs.length ? NumCast(docs[0].x) : 0; let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; let miny = docs.length ? NumCast(docs[0].y) : 0; @@ -549,7 +545,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { - const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state }); + let finalLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; + const pos = this.getCalculatedPositions({ doc: finalLayout, index: i, collection: this.Document, docs: layoutDocs, state }); state = pos.state === undefined ? state : pos.state; layoutPoolData.set(pair, pos); }); @@ -592,7 +589,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let i = 0; const width = Math.max(...docs.map(doc => NumCast(doc.width))); const height = Math.max(...docs.map(doc => NumCast(doc.height))); - for (const doc of docs) { + for (const doc of docs.map(d => d.layout instanceof Doc ? d.layout : d)) { doc.x = x; doc.y = y; x += width + 20; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ecdd02b0f..1362736cf 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -281,7 +281,7 @@ export class MarqueeView extends React.Component let bounds = this.Bounds; let selected = this.marqueeSelect(false); if (e.key === "c") { - selected.map(d => { + selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -325,7 +325,7 @@ export class MarqueeView extends React.Component this.marqueeInkDelete(inkData); if (e.key === "s" || e.key === "S") { - selected.map(d => { + selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -394,20 +394,22 @@ export class MarqueeView extends React.Component let selRect = this.Bounds; let selection: Doc[] = []; this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { - var x = NumCast(doc.x); - var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; + var x = NumCast(layoutDoc.x); + var y = NumCast(layoutDoc.y); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { selection.push(doc); } }); if (!selection.length && selectBackgrounds) { this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => { - var x = NumCast(doc.x); - var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; + var x = NumCast(layoutDoc.x); + var y = NumCast(layoutDoc.y); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { selection.push(doc); } @@ -420,10 +422,11 @@ export class MarqueeView extends React.Component let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => { - var x = NumCast(doc.x); - var y = NumCast(doc.y); - var w = NumCast(doc.width); - var h = NumCast(doc.height); + let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; + var x = NumCast(layoutDoc.x); + var y = NumCast(layoutDoc.y); + var w = NumCast(layoutDoc.width); + var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { selection.push(doc); } diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 93f6dc468..5e8ac3ecd 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -36,8 +36,8 @@ export class CollectionFreeFormDocumentView extends DocComponent(Docu 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)); - let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.Document.width) / 2, NumCast(this.Document.height) / 2); + let scrpt = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(NumCast(this.layoutDoc.width) / 2, NumCast(this.layoutDoc.height) / 2); DocumentManager.Instance.animateBetweenPoint(scrpt, expandedDocs); } else { expandedDocs.forEach(maxDoc => (!this.props.addDocTab(maxDoc, undefined, "close") && this.props.addDocTab(maxDoc, undefined, maxLocation))); @@ -392,7 +392,7 @@ export class DocumentView extends DocComponent(Docu if (!anchors.find(anchor2 => anchor2 && anchor2.title === this.Document.title + ".portal" ? true : false)) { let portalID = (this.Document.title + ".portal").replace(/^-/, "").replace(/\([0-9]*\)$/, ""); DocServer.GetRefField(portalID).then(existingPortal => { - let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.Document.width || 0) + 10, height: this.Document.height || 0, title: portalID }); + let portal = existingPortal instanceof Doc ? existingPortal : Docs.Create.FreeformDocument([], { width: (this.layoutDoc.width || 0) + 10, height: this.layoutDoc.height || 0, title: portalID }); DocUtils.MakeLink({ doc: this.props.Document, ctx: this.props.ContainingCollectionDoc }, { doc: portal }, portalID, "portal link"); this.Document.isButton = true; }); @@ -491,7 +491,7 @@ export class DocumentView extends DocComponent(Docu layoutItems.push({ description: "Make View of Metadata Field", event: () => Doc.MakeMetadataFieldTemplate(this.props.Document, this.props.DataDoc!), icon: "concierge-bell" }); } layoutItems.push({ description: `${this.Document.chromeStatus !== "disabled" ? "Hide" : "Show"} Chrome`, event: () => this.Document.chromeStatus = (this.Document.chromeStatus !== "disabled" ? "disabled" : "enabled"), icon: "project-diagram" }); - layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.Document.autoHeight = !this.Document.autoHeight, icon: "plus" }); + layoutItems.push({ description: `${this.Document.autoHeight ? "Variable Height" : "Auto Height"}`, event: () => this.layoutDoc.autoHeight = !this.layoutDoc.autoHeight, icon: "plus" }); layoutItems.push({ description: this.Document.ignoreAspect || !this.Document.nativeWidth || !this.Document.nativeHeight ? "Freeze" : "Unfreeze", event: this.freezeNativeDimensions, icon: "snowflake" }); layoutItems.push({ description: this.Document.lockedPosition ? "Unlock Position" : "Lock Position", event: this.toggleLockPosition, icon: BoolCast(this.Document.lockedPosition) ? "unlock" : "lock" }); layoutItems.push({ description: "Center View", event: () => this.props.focus(this.props.Document, false), icon: "crosshairs" }); diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index 848afccf3..c5bf28d5b 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -8,6 +8,7 @@ import { FieldView, FieldViewProps } from './FieldView'; import { StrCast } from '../../../new_fields/Types'; import { Utils } from "../../../Utils"; import { runInAction, observable, reaction, IReactionDisposer } from 'mobx'; +import { Doc } from '../../../new_fields/Doc'; const FontIconSchema = createSchema({ icon: "string" }); @@ -35,8 +36,10 @@ export class FontIconBox extends DocComponent( this._backgroundReaction && this._backgroundReaction(); } render() { + let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document); + let referenceLayout = referenceDoc.layout instanceof Doc ? referenceDoc.layout : referenceDoc; return ; } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ae67e94cc..f157a953e 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -127,7 +127,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let view = this._editorView!; if (view.state.selection.from === view.state.selection.to) return false; if (view.state.selection.to - view.state.selection.from > view.state.doc.nodeSize - 3) { - this.props.Document.color = color; + this.layoutDoc.color = color; } let colorMark = view.state.schema.mark(view.state.schema.marks.pFontColor, { color: color }); view.dispatch(view.state.tr.addMark(view.state.selection.from, view.state.selection.to, colorMark)); @@ -143,7 +143,13 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @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); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); } + + // the document containing the view layout information - will be the Document itself unless the Document has + // a layout field. In that case, all layout information comes from there unless overriden by Document + @computed get layoutDoc(): Doc { + return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; + } linkOnDeselect: Map = new Map(); @@ -249,39 +255,41 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.DocumentDragData && de.data.userDropAction) { - let target = de.data.droppedDocuments[0]; - const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); - target.fitToBox = true; - let node = schema.nodes.dashDoc.create({ - width: target[WidthSym](), height: target[HeightSym](), - title: "dashDoc", docid: target[Id], - float: "right" - }); - let pos = this._editorView!.posAtCoords({ left: de.x, top: de.y }); - link && this._editorView!.dispatch(this._editorView!.state.tr.insert(pos!.pos, node)); - this.tryUpdateHeight(); - e.stopPropagation(); - } else if (de.data instanceof DragManager.DocumentDragData) { + if (de.data instanceof DragManager.DocumentDragData) { const draggedDoc = de.data.draggedDocuments.length && de.data.draggedDocuments[0]; - if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document)) { - if (de.mods === "AltKey") { - if (draggedDoc.data instanceof RichTextField) { - Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); - e.stopPropagation(); - } - } else if (de.mods === "CtrlKey") { - draggedDoc.isTemplate = true; - if (typeof (draggedDoc.layout) === "string") { - let layoutDelegateToOverrideFieldKey = Doc.MakeDelegate(draggedDoc); - layoutDelegateToOverrideFieldKey.layout = StrCast(layoutDelegateToOverrideFieldKey.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); - this.props.Document.layout = layoutDelegateToOverrideFieldKey; - } else { - this.props.Document.layout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; - } + // replace text contents whend dragging with Alt + if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "AltKey") { + if (draggedDoc.data instanceof RichTextField) { + Doc.GetProto(this.dataDoc)[this.props.fieldKey] = new RichTextField(draggedDoc.data.Data); + e.stopPropagation(); + } + // apply as template when dragging with Meta + } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") { + draggedDoc.isTemplate = true; + let newLayout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; + if (typeof (draggedDoc.layout) === "string") { + newLayout = Doc.MakeDelegate(draggedDoc); + newLayout.layout = StrCast(newLayout.layout).replace(/fieldKey={"[^"]*"}/, `fieldKey={"${this.props.fieldKey}"}`); } + this.props.Document.layout = newLayout; e.stopPropagation(); - } + // embed document when dragging with a userDropAction or an embedDoc flag set + } else if (de.data.userDropAction || de.data.embedDoc) { + let target = de.data.droppedDocuments[0]; + const link = DocUtils.MakeLink({ doc: this.dataDoc, ctx: this.props.ContainingCollectionDoc }, { doc: target }, "Embedded Doc:" + target.title); + if (link) { + target.fitToBox = true; + let node = schema.nodes.dashDoc.create({ + width: target[WidthSym](), height: target[HeightSym](), + title: "dashDoc", docid: target[Id], + float: "right" + }); + let view = this._editorView!; + view.dispatch(view.state.tr.insert(view.posAtCoords({ left: de.x, top: de.y })!.pos, node)); + this.tryUpdateHeight(); + e.stopPropagation(); + } + } // otherwise, fall through to outer collection to handle drop } } @@ -486,7 +494,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ); this._heightReactionDisposer = reaction( - () => [this.props.Document[WidthSym](), this.props.Document.autoHeight], + () => [this.layoutDoc[WidthSym](), this.layoutDoc.autoHeight], () => this.tryUpdateHeight() ); @@ -505,7 +513,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this.setupEditor(this.config, this.dataDoc, this.props.fieldKey); this._searchReactionDisposer = reaction(() => { - return StrCast(this.props.Document.search_string); + return StrCast(this.layoutDoc.search_string); }, searchString => { if (searchString) { this.highlightSearchTerms([searchString]); @@ -518,7 +526,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._rulesReactionDisposer = reaction(() => { let ruleProvider = this.props.ruleProvider; - let heading = NumCast(this.props.Document.heading); + let heading = NumCast(this.layoutDoc.heading); if (ruleProvider instanceof Doc) { return { align: StrCast(ruleProvider["ruleAlign_" + heading], ""), @@ -530,7 +538,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }, action((rules: any) => { this._fontFamily = rules ? rules.font : "Arial"; - this._fontSize = rules ? rules.size : NumCast(this.props.Document.fontSize, 13); + this._fontSize = rules ? rules.size : NumCast(this.layoutDoc.fontSize, 13); rules && setTimeout(() => { const view = this._editorView!; if (this._proseRef) { @@ -547,7 +555,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }), { fireImmediately: true } ); this._scrollToRegionReactionDisposer = reaction( - () => StrCast(this.props.Document.scrollToLinkID), + () => StrCast(this.layoutDoc.scrollToLinkID), async (scrollToLinkID) => { let findLinkFrag = (frag: Fragment, editor: EditorView) => { const nodes: Node[] = []; @@ -585,7 +593,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe setTimeout(() => editor.dispatch(editor.state.tr.addMark(selection.from, selection.to, mark)), 0); setTimeout(() => this.unhighlightSearchTerms(), 2000); } - this.props.Document.scrollToLinkID = undefined; + this.layoutDoc.scrollToLinkID = undefined; } }, @@ -885,7 +893,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } } 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) }); + let webDoc = Docs.Create.WebDocument(href, { x: NumCast(this.layoutDoc.x, 0) + NumCast(this.layoutDoc.width, 0), y: NumCast(this.layoutDoc.y) }); this.props.addDocument && this.props.addDocument(webDoc); } e.stopPropagation(); @@ -983,19 +991,19 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @action tryUpdateHeight() { let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0; - if (!this.props.Document.isAnimating && this.props.Document.autoHeight && scrollHeight !== 0 && + if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); - let dh = NumCast(this.props.Document.height, 0); - this.props.Document.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); + let dh = NumCast(this.layoutDoc.height, 0); + this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; } } render() { let style = "hidden"; - let rounded = StrCast(this.props.Document.borderRounding) === "100%" ? "-rounded" : ""; - let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.props.Document.isBackground + let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; + let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground ? "none" : "all"; Doc.UpdateDocumentExtensionForField(this.dataDoc, this.props.fieldKey); if (this.props.isSelected()) { @@ -1004,8 +1012,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return (
(field.map((d: any) => Doc.MakeAlias(d))); }); Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); -- cgit v1.2.3-70-g09d2 From 7763a08eb5ed931dbf854e2b72d07b7613791e2b Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 16 Oct 2019 21:27:25 -0400 Subject: some fixes to linearView for handling templates. fixed DOCUMENTS on library view. renaming a bunch of template stuff... still in progress. --- src/client/documents/Documents.ts | 1 + src/client/views/CollectionLinearView.tsx | 18 ++++++++++++++---- src/client/views/DocComponent.tsx | 2 +- src/client/views/DocumentDecorations.tsx | 4 ++-- src/client/views/InkingControl.tsx | 7 ++----- src/client/views/TemplateMenu.tsx | 2 +- src/client/views/collections/CollectionBaseView.tsx | 10 +++++----- .../views/collections/CollectionStackingView.tsx | 4 +++- src/client/views/collections/CollectionSubView.tsx | 11 ++++++++--- src/client/views/collections/CollectionTreeView.tsx | 3 ++- .../collectionFreeForm/CollectionFreeFormView.tsx | 2 +- src/client/views/nodes/ButtonBox.tsx | 6 +++++- src/client/views/nodes/DocumentView.tsx | 6 +++--- src/client/views/nodes/FormattedTextBox.tsx | 2 +- src/client/views/nodes/VideoBox.tsx | 2 +- src/new_fields/Doc.ts | 6 +++--- src/server/authentication/models/current_user_utils.ts | 16 ++++++++-------- 17 files changed, 61 insertions(+), 41 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index ccb08f4cd..2ffbc8394 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -70,6 +70,7 @@ export interface DocumentOptions { layout?: string | Doc; hideHeadings?: boolean; // whether stacking view column headings should be hidden isTemplate?: boolean; + isTemplateDoc?: boolean; templates?: List; viewType?: number; backgroundColor?: string; diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 44d9b042e..3e2ab1459 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -1,7 +1,7 @@ import { action, IReactionDisposer, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, HeightSym, WidthSym } from '../../new_fields/Doc'; +import { Doc, HeightSym, WidthSym, DocListCast } from '../../new_fields/Doc'; import { ObjectField } from '../../new_fields/ObjectField'; import { makeInterface } from '../../new_fields/Schema'; import { ScriptField } from '../../new_fields/ScriptField'; @@ -51,9 +51,19 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => { let dbox = doc; if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { - let template = doc.layout instanceof Doc && doc.layout.isTemplate ? doc.layout : doc; - template.isTemplate = (template.type === DocumentType.TEXT || template.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction; - dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: template.isTemplate ? "font" : "bolt" }); + let template = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; + if (template.type === DocumentType.COL) { + let layout = StrCast(template.layout).match(/fieldKey={"[^"]*"}/)![0]; + let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, ""); + let docs = DocListCast(template[fieldKey]); + docs.map(d => { + Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(template)); + }); + template.isTemplateDoc = true; + } else { + template.isTemplateDoc = (template.type === DocumentType.TEXT || template.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction; + } + dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: template.isTemplateDoc ? "font" : "bolt" }); dbox.dragFactory = template; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); diff --git a/src/client/views/DocComponent.tsx b/src/client/views/DocComponent.tsx index fbc27192c..b6b717be0 100644 --- a/src/client/views/DocComponent.tsx +++ b/src/client/views/DocComponent.tsx @@ -68,7 +68,7 @@ export function DocAnnotatableComponent

(schema 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 dataDoc() { return (this.props.DataDoc && this.props.Document.isTemplateField ? this.props.DataDoc : Doc.GetProto(this.props.Document)) as Doc; } @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 2f40ea746..6e8ba2d3d 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -380,7 +380,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> ruleProvider && heading && (Doc.GetProto(ruleProvider)["ruleRounding_" + heading] = `${Math.min(100, dist)}%`); usingRule = usingRule || (ruleProvider && heading ? true : false); }); - !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplate ? dv.props.Document : Doc.GetProto(dv.props.Document)). + !usingRule && SelectionManager.SelectedDocuments().map(dv => dv.props.Document.layout instanceof Doc ? dv.props.Document.layout : dv.props.Document.isTemplateField ? dv.props.Document : Doc.GetProto(dv.props.Document)). map(d => d.borderRounding = `${Math.min(100, dist)}%`); e.stopPropagation(); e.preventDefault(); @@ -481,7 +481,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let actualdH = Math.max(height + (dH * scale), 20); layoutDoc.x = (layoutDoc.x || 0) + dX * (actualdW - width); layoutDoc.y = (layoutDoc.y || 0) + dY * (actualdH - height); - let proto = doc.isTemplate ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... + let proto = doc.isTemplateField ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... let fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight); if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) { layoutDoc.ignoreAspect = false; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index 46c6fae1c..bc5249acd 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -43,7 +43,7 @@ export class InkingControl { let oldColors = selected.map(view => { let targetDoc = view.props.Document.dragFactory instanceof Doc ? view.props.Document.dragFactory : view.props.Document.layout instanceof Doc ? view.props.Document.layout : - view.props.Document.isTemplate ? view.props.Document : Doc.GetProto(view.props.Document); + view.props.Document.isTemplateField ? view.props.Document : Doc.GetProto(view.props.Document); let sel = window.getSelection(); if (StrCast(targetDoc.layout).indexOf("FormattedTextBox") !== -1 && (!sel || sel.toString() !== "")) { targetDoc.color = this._selectedColor; @@ -81,10 +81,7 @@ export class InkingControl { ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined; ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } - if (!ruleProvider) { - if (targetDoc) - targetDoc.backgroundColor = matchedColor; - } + (!ruleProvider && targetDoc) && (targetDoc.backgroundColor = matchedColor); return { target: targetDoc, diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index 9e5e62e03..da776f887 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -100,7 +100,7 @@ export class TemplateMenu extends React.Component { clearTemplates = (event: React.MouseEvent) => { Templates.TemplateList.forEach(template => this.props.docs.forEach(d => d.Document["show" + template.Name] = undefined)); ["backgroundColor", "borderRounding", "width", "height"].forEach(field => this.props.docs.forEach(d => { - if (d.Document.isTemplate && d.props.DataDoc) { + if (d.Document.isTemplateDoc && d.props.DataDoc) { d.Document[field] = undefined; } else if (d.Document["default" + field[0].toUpperCase() + field.slice(1)] !== undefined) { d.Document[field] = Doc.GetProto(d.Document)[field] = undefined; diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 7798964ea..15853fcae 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -84,7 +84,7 @@ export class CollectionBaseView extends React.Component { } } - @computed get dataDoc() { return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } + @computed get dataDoc() { return Doc.fieldExtensionDoc(this.props.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); } @computed get dataField() { return this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; } active = (): boolean => { @@ -106,8 +106,8 @@ export class CollectionBaseView extends React.Component { 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; + let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplateField ? this.extensionDoc : this.props.Document; + let targetField = (this.props.fieldExt || this.props.Document.isTemplateField) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; Doc.AddDocToList(targetDataDoc, targetField, doc); Doc.GetProto(doc).lastOpened = new DateField; return true; @@ -118,8 +118,8 @@ export class CollectionBaseView extends React.Component { let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView); docView && SelectionManager.DeselectDoc(docView); //TODO This won't create the field if it doesn't already exist - 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; + let targetDataDoc = this.props.fieldExt || this.props.Document.isTemplateField ? this.extensionDoc : this.props.Document; + let targetField = (this.props.fieldExt || this.props.Document.isTemplateField) && this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; 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); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index cde1a5036..e54374ad7 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -110,6 +110,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } componentDidMount() { + super.componentDidMount(); this._heightDisposer = reaction(() => { if (this.props.Document.autoHeight) { let sectionsList = Array.from(this.Sections.size ? this.Sections.values() : [this.filteredChildren]); @@ -137,6 +138,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { ); } componentWillUnmount() { + super.componentWillUnmount(); this._heightDisposer && this._heightDisposer(); this._sectionFilterDisposer && this._sectionFilterDisposer(); } @@ -167,7 +169,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { renderDepth={this.props.renderDepth} ruleProvider={this.props.Document.isRuleProvider && layoutDoc.type !== DocumentType.TEXT ? this.props.Document : this.props.ruleProvider} fitToBox={this.props.fitToBox} - onClick={layoutDoc.isTemplate ? this.onClickHandler : this.onChildClickHandler} + onClick={layoutDoc.isTemplateDoc ? this.onClickHandler : this.onChildClickHandler} PanelWidth={width} PanelHeight={height} getTransform={finalDxf} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 9919a9dc3..46fbb5910 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -57,8 +57,13 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { componentDidMount() { this._childLayoutDisposer = reaction(() => [this.childDocs, Cast(this.props.Document.childLayout, Doc)], - async (args) => args[1] instanceof Doc && - this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc)))); + async (args) => { + if (args[1] instanceof Doc) { + this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc))); + } else { + this.childDocs.map(async doc => doc.layout = undefined); + } + }); } componentWillUnmount() { @@ -70,7 +75,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { // to its children which may be templates. // The name of the data field comes from fieldExt if it's an extension, or fieldKey otherwise. @computed get dataField() { - return Doc.fieldExtensionDoc(this.props.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt)[this.props.fieldExt || this.props.fieldKey]; + return Doc.fieldExtensionDoc(this.props.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt)[this.props.fieldExt || this.props.fieldKey]; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 403da0e54..0e9c38fb4 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -520,7 +520,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { private treedropDisposer?: DragManager.DragDropDisposer; private _mainEle?: HTMLDivElement; - @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } + @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplateField) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } protected createTreeDropTarget = (ele: HTMLDivElement) => { this.treedropDisposer && this.treedropDisposer(); @@ -530,6 +530,7 @@ export class CollectionTreeView extends CollectionSubView(Document) { } componentWillUnmount() { + super.componentWillUnmount(); this.treedropDisposer && this.treedropDisposer(); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 229e7fffc..2b0ef8ada 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -627,7 +627,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; - if (this.childDocs.some(d => BoolCast(d.isTemplate))) { + if (this.childDocs.some(d => BoolCast(d.isTemplateDoc))) { layoutItems.push({ description: "Template Layout Instance", event: () => this.props.addDocTab(Doc.ApplyTemplate(this.props.Document)!, undefined, "onRight"), icon: "project-diagram" }); } layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); diff --git a/src/client/views/nodes/ButtonBox.tsx b/src/client/views/nodes/ButtonBox.tsx index 3cf8c3eb3..b4d33fb0f 100644 --- a/src/client/views/nodes/ButtonBox.tsx +++ b/src/client/views/nodes/ButtonBox.tsx @@ -32,7 +32,11 @@ export class ButtonBox extends DocComponent(Butt public static LayoutString() { return FieldView.LayoutString(ButtonBox); } private dropDisposer?: DragManager.DragDropDisposer; - @computed get dataDoc() { return this.props.DataDoc && (BoolCast(this.props.Document.isTemplate) || BoolCast(this.props.DataDoc.isTemplate) || this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); } + @computed get dataDoc() { + return this.props.DataDoc && + (BoolCast(this.props.Document.isTemplateField) || BoolCast(this.props.DataDoc.isTemplateField) || + this.props.DataDoc.layout === this.props.Document) ? this.props.DataDoc : Doc.GetProto(this.props.Document); + } protected createDropTarget = (ele: HTMLDivElement) => { diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index a071f782a..a7e78a7e8 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -110,7 +110,7 @@ export const documentSchema = createSchema({ dragFactory: Doc, // the document that serves as the "template" for the onDragStart script. ie, to drag out copies of the dragFactory document. ignoreAspect: "boolean", // whether aspect ratio should be ignored when laying out or manipulating the document autoHeight: "boolean", // whether the height of the document should be computed automatically based on its contents - isTemplate: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed + isTemplateField: "boolean", // whether this document acts as a template layout for describing how other documents should be displayed isBackground: "boolean", // whether document is a background element and ignores input events (can only selet with marquee) type: "string", // enumerated type of document maximizeLocation: "string", // flag for where to place content when following a click interaction (e.g., onRight, inPlace, inTab) @@ -196,7 +196,7 @@ export class DocumentView extends DocComponent(Docu SelectionManager.DeselectAll(); Doc.UnBrushDoc(this.props.Document); } else if (this.onClickHandler && this.onClickHandler.script) { - this.onClickHandler.script.run({ this: this.Document.isTemplate && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log); + this.onClickHandler.script.run({ this: this.Document.isTemplateField && this.props.DataDoc ? this.props.DataDoc : this.props.Document }, console.log); } else if (this.props.Document.type === DocumentType.BUTTON) { ScriptBox.EditButtonScript("On Button Clicked ...", this.props.Document, "onClick", e.clientX, e.clientY); } else if (this.Document.isButton) { @@ -376,7 +376,7 @@ export class DocumentView extends DocComponent(Docu @undoBatch @action freezeNativeDimensions = (): void => { - let proto = this.Document.isTemplate ? this.props.Document : Doc.GetProto(this.props.Document); + let proto = this.Document.isTemplateDoc ? this.props.Document : Doc.GetProto(this.props.Document); proto.autoHeight = this.Document.autoHeight = false; proto.ignoreAspect = !proto.ignoreAspect; if (!proto.ignoreAspect && !proto.nativeWidth) { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index f157a953e..ea82b1161 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -993,7 +993,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let scrollHeight = this._ref.current ? this._ref.current.scrollHeight : 0; if (!this.layoutDoc.isAnimating && this.layoutDoc.autoHeight && scrollHeight !== 0 && getComputedStyle(this._ref.current!.parentElement!).top === "0px") { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation - let nh = this.props.Document.isTemplate ? 0 : NumCast(this.dataDoc.nativeHeight, 0); + let nh = this.props.Document.isTemplateField ? 0 : NumCast(this.dataDoc.nativeHeight, 0); let dh = NumCast(this.layoutDoc.height, 0); this.layoutDoc.height = Math.max(10, (nh ? dh / nh * scrollHeight : scrollHeight) + (this.props.ChromeHeight ? this.props.ChromeHeight() : 0)); this.dataDoc.nativeHeight = nh ? scrollHeight : undefined; diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx index aa9b28118..19968e6e1 100644 --- a/src/client/views/nodes/VideoBox.tsx +++ b/src/client/views/nodes/VideoBox.tsx @@ -323,7 +323,7 @@ export class VideoBox extends DocAnnotatableComponent(field.map((d: any) => Doc.MakeAlias(d))); }); Scripting.addGlobal(function docList(field: any) { return DocListCast(field); }); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index ef977c89a..5ce707011 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -36,11 +36,11 @@ export class CurrentUserUtils { // a default set of note types .. not being used yet... static setupNoteTypes(doc: Doc) { return [ - Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplate: true }), - Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplate: true }), - Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplate: true }), - Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplate: true }), - Docs.Create.TextDocument({ title: "Todo", backgroundColor: "orange", isTemplate: true }) + Docs.Create.TextDocument({ title: "Note", backgroundColor: "yellow", isTemplateDoc: true }), + Docs.Create.TextDocument({ title: "Idea", backgroundColor: "pink", isTemplateDoc: true }), + Docs.Create.TextDocument({ title: "Topic", backgroundColor: "lightBlue", isTemplateDoc: true }), + Docs.Create.TextDocument({ title: "Person", backgroundColor: "lightGreen", isTemplateDoc: true }), + Docs.Create.TextDocument({ title: "Todo", backgroundColor: "orange", isTemplateDoc: true }) ]; } @@ -98,17 +98,17 @@ export class CurrentUserUtils { }); doc.documents = Docs.Create.TreeDocument([], { - title: "DOCUMENTS", gridGap: 5, xMargin: 5, yMargin: 5, height: 42, width: 100, boxShadow: "0 0", backgroundColor: "#eeeeee", preventTreeViewOpen: true, forceActive: true, lockedPosition: true + title: "DOCUMENTS", height: 42, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee" }); // setup Recently Closed library item doc.recentlyClosed = Docs.Create.TreeDocument([], { - title: "Recently Closed".toUpperCase(), height: 75, boxShadow: "0 0", preventTreeViewOpen: true, forceActive: true, lockedPosition: true, backgroundColor: "#eeeeee" + title: "RECENTLY CLOSED", height: 75, forceActive: true, boxShadow: "0 0", preventTreeViewOpen: true, lockedPosition: true, backgroundColor: "#eeeeee" }); return Docs.Create.ButtonDocument({ width: 50, height: 35, borderRounding: "50%", boxShadow: "2px 2px 1px", title: "Library", - panel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc, doc.recentlyClosed as Doc], { + panel: Docs.Create.TreeDocument([doc.workspaces as Doc, doc.documents as Doc, doc.recentlyClosed as Doc], { title: "Library", xMargin: 5, yMargin: 5, gridGap: 5, forceActive: true, dropAction: "alias", lockedPosition: true }), targetContainer: sidebarContainer, -- cgit v1.2.3-70-g09d2 From caebaeca6993ff48609ab16dcc390d4d0458b03a Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Wed, 16 Oct 2019 23:50:27 -0400 Subject: fixed applying templates to collections. fixed templates containing collections --- src/client/documents/Documents.ts | 2 +- src/client/views/CollectionLinearView.tsx | 35 ++++++++++++++-------- src/client/views/InkingCanvas.tsx | 3 +- src/client/views/InkingControl.tsx | 2 +- src/client/views/collections/CollectionSubView.tsx | 2 +- .../views/collections/CollectionViewChromes.tsx | 4 +-- src/client/views/nodes/ColorBox.tsx | 7 ++--- src/client/views/nodes/FormattedTextBox.tsx | 4 +-- src/new_fields/Doc.ts | 14 +++++++-- .../authentication/models/current_user_utils.ts | 14 +++------ 10 files changed, 51 insertions(+), 36 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2ffbc8394..62175cbe3 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -69,7 +69,7 @@ export interface DocumentOptions { preventTreeViewOpen?: boolean; // ignores the treeViewOpen Doc flag which allows a treeViewItem's expande/collapse state to be independent of other views of the same document in the tree view layout?: string | Doc; hideHeadings?: boolean; // whether stacking view column headings should be hidden - isTemplate?: boolean; + isTemplateField?: boolean; isTemplateDoc?: boolean; templates?: List; viewType?: number; diff --git a/src/client/views/CollectionLinearView.tsx b/src/client/views/CollectionLinearView.tsx index 3e2ab1459..04e131135 100644 --- a/src/client/views/CollectionLinearView.tsx +++ b/src/client/views/CollectionLinearView.tsx @@ -47,24 +47,35 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { } } + makeTemplate = (doc: Doc): boolean => { + let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; + let layout = StrCast(layoutDoc.layout).match(/fieldKey={"[^"]*"}/)![0]; + let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, ""); + let docs = DocListCast(layoutDoc[fieldKey]); + let any = false; + docs.map(d => { + if (!StrCast(d.title).startsWith("-")) { + any = true; + return Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(layoutDoc)); + } + if (d.type === DocumentType.COL) return this.makeTemplate(d); + return false; + }); + return any; + } + drop = action((e: Event, de: DragManager.DropEvent) => { (de.data as DragManager.DocumentDragData).draggedDocuments.map((doc, i) => { let dbox = doc; if (!doc.onDragStart && !doc.onClick && this.props.Document.convertToButtons && doc.viewType !== CollectionViewType.Linear) { - let template = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; - if (template.type === DocumentType.COL) { - let layout = StrCast(template.layout).match(/fieldKey={"[^"]*"}/)![0]; - let fieldKey = layout.replace('fieldKey={"', "").replace(/"}$/, ""); - let docs = DocListCast(template[fieldKey]); - docs.map(d => { - Doc.MakeMetadataFieldTemplate(d, Doc.GetProto(template)); - }); - template.isTemplateDoc = true; + let layoutDoc = doc.layout instanceof Doc && doc.layout.isTemplateField ? doc.layout : doc; + if (layoutDoc.type === DocumentType.COL) { + layoutDoc.isTemplateDoc = this.makeTemplate(layoutDoc); } else { - template.isTemplateDoc = (template.type === DocumentType.TEXT || template.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction; + layoutDoc.isTemplateDoc = (layoutDoc.type === DocumentType.TEXT || layoutDoc.layout instanceof Doc) && de.data instanceof DragManager.DocumentDragData && !de.data.userDropAction; } - dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: template.isTemplateDoc ? "font" : "bolt" }); - dbox.dragFactory = template; + dbox = Docs.Create.FontIconDocument({ nativeWidth: 100, nativeHeight: 100, width: 100, height: 100, backgroundColor: StrCast(doc.backgroundColor), title: "Custom", icon: layoutDoc.isTemplateDoc ? "font" : "bolt" }); + dbox.dragFactory = layoutDoc; dbox.removeDropProperties = doc.removeDropProperties instanceof ObjectField ? ObjectField.MakeCopy(doc.removeDropProperties) : undefined; dbox.onDragStart = ScriptField.MakeFunction('getCopy(this.dragFactory, true)'); } else if (doc.viewType === CollectionViewType.Linear) { diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 9ab320eab..920ebaedd 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -181,9 +181,10 @@ export class InkingCanvas extends React.Component { render() { let svgCanvasStyle = InkingControl.Instance.selectedTool !== InkTool.None && !this.props.Document.isBackground ? "canSelect" : "noSelect"; + let cursor = svgCanvasStyle === "canSelect" ? (InkingControl.Instance.selectedTool === InkTool.Eraser ? "pointer" : "default") : undefined; return (

-
+
{this.props.children()} {this.drawnPaths}
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index bc5249acd..b8f3c2875 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -94,7 +94,7 @@ export class InkingControl { redo: () => oldColors.forEach(pair => pair.target.backgroundColor = captured) }); } else { - CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen instanceof Doc && (CurrentUserUtils.UserDocument.activePen.pen.backgroundColor = this._selectedColor); + CurrentUserUtils.ActivePen && (CurrentUserUtils.ActivePen.backgroundColor = this._selectedColor); } }); @action diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 46fbb5910..1f8c0b18f 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -61,7 +61,7 @@ export function CollectionSubView(schemaCtor: (doc: Doc) => T) { if (args[1] instanceof Doc) { this.childDocs.map(async doc => !Doc.AreProtosEqual(args[1] as Doc, (await doc).layout as Doc) && Doc.ApplyTemplateTo(args[1] as Doc, (await doc))); } else { - this.childDocs.map(async doc => doc.layout = undefined); + this.childDocs.filter(d => !d.isTemplateField).map(async doc => doc.layout = undefined); } }); diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 3a66c05f4..a5b7f0181 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -40,9 +40,9 @@ export class CollectionViewBaseChrome extends React.Component this.props.CollectionView.props.Document.childLayout = draggedDocs.length ? draggedDocs[0] : undefined + immediate: (draggedDocs: Doc[]) => Doc.setChildLayout(this.props.CollectionView.props.Document, draggedDocs.length ? draggedDocs[0] : undefined) }; _contentCommand = { // title: "set content", script: "getProto(this.target).data = aliasDocs(this.source.map(async p => await p));", params: ["target", "source"], // bcz: doesn't look like we can do async stuff in scripting... diff --git a/src/client/views/nodes/ColorBox.tsx b/src/client/views/nodes/ColorBox.tsx index e4fef0922..30554ea36 100644 --- a/src/client/views/nodes/ColorBox.tsx +++ b/src/client/views/nodes/ColorBox.tsx @@ -25,9 +25,8 @@ export class ColorBox extends DocStaticComponent( this._selectedDisposer = reaction(() => SelectionManager.SelectedDocuments(), action(() => this._startupColor = SelectionManager.SelectedDocuments().length ? StrCast(SelectionManager.SelectedDocuments()[0].Document.backgroundColor, "black") : "black"), { fireImmediately: true }); - this._penDisposer = reaction(() => CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen, - action(() => this._startupColor = CurrentUserUtils.UserDocument.activePen instanceof Doc && CurrentUserUtils.UserDocument.activePen.pen instanceof Doc ? - StrCast(CurrentUserUtils.UserDocument.activePen.pen.backgroundColor, "black") : "black"), + this._penDisposer = reaction(() => CurrentUserUtils.ActivePen, + action(() => this._startupColor = CurrentUserUtils.ActivePen ? StrCast(CurrentUserUtils.ActivePen.backgroundColor, "black") : "black"), { fireImmediately: true }); // compare to this reaction that used to be in Selection Manager @@ -36,7 +35,7 @@ export class ColorBox extends DocStaticComponent( // if (sel.length > 0) { // let firstView = sel[0]; // let doc = firstView.props.Document; - // let targetDoc = doc.isTemplate ? doc : Doc.GetProto(doc); + // let targetDoc = doc.isTemplateField ? doc : Doc.GetProto(doc); // let stored = StrCast(targetDoc.backgroundColor); // stored.length > 0 && (targetColor = stored); // } diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index ea82b1161..045eee3f7 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -143,7 +143,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe @computed get extensionDoc() { return Doc.fieldExtensionDoc(this.dataDoc, this.props.fieldKey); } - @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplate ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); } + @computed get dataDoc() { return this.props.DataDoc && this.props.Document.isTemplateField ? Doc.GetProto(this.props.DataDoc) : Doc.GetProto(this.props.Document); } // the document containing the view layout information - will be the Document itself unless the Document has // a layout field. In that case, all layout information comes from there unless overriden by Document @@ -265,7 +265,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } // apply as template when dragging with Meta } else if (draggedDoc && draggedDoc.type === DocumentType.TEXT && !Doc.AreProtosEqual(draggedDoc, this.props.Document) && de.mods === "MetaKey") { - draggedDoc.isTemplate = true; + draggedDoc.isTemplateDoc = true; let newLayout = draggedDoc.layout instanceof Doc ? draggedDoc.layout : draggedDoc; if (typeof (draggedDoc.layout) === "string") { newLayout = Doc.MakeDelegate(draggedDoc); diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 51fcb818a..04f310785 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -599,7 +599,7 @@ export namespace Doc { return target; } - export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false) { + export function MakeMetadataFieldTemplate(fieldTemplate: Doc, templateDataDoc: Doc, suppressTitle: boolean = false): boolean { // move data doc fields to layout doc as needed (nativeWidth/nativeHeight, data, ??) let metadataFieldName = StrCast(fieldTemplate.title).replace(/^-/, ""); let backgroundLayout = StrCast(fieldTemplate.backgroundLayout); @@ -613,7 +613,7 @@ export namespace Doc { fieldTemplate.templateField = metadataFieldName; fieldTemplate.title = metadataFieldName; - fieldTemplate.isTemplate = true; + fieldTemplate.isTemplateField = true; fieldTemplate.backgroundLayout = backgroundLayout; /* move certain layout properties from the original data doc to the template layout to avoid inheriting them from the template's data doc which may also define these fields for its own use. @@ -636,6 +636,7 @@ export namespace Doc { if (fieldTemplate.backgroundColor !== templateDataDoc.defaultBackgroundColor) fieldTemplate.defaultBackgroundColor = fieldTemplate.backgroundColor; fieldTemplate.proto = templateDataDoc; }), 0); + return true; } export function overlapping(doc: Doc, doc2: Doc, clusterDistance: number) { @@ -735,9 +736,18 @@ export namespace Doc { export function UnBrushAllDocs() { manager.BrushedDoc.clear(); } + + export function setChildLayout(target: Doc, source?: Doc) { + target.childLayout = source && source.isTemplateDoc ? source : source && + source.dragFactory instanceof Doc && source.dragFactory.isTemplateDoc ? source.dragFactory : + source && source.layout instanceof Doc && source.layout.isTemplateDoc ? source.layout : undefined; + } } + + Scripting.addGlobal(function renameAlias(doc: any, n: any) { return StrCast(Doc.GetProto(doc).title).replace(/\([0-9]*\)/, "") + `(${n})`; }); Scripting.addGlobal(function getProto(doc: any) { return Doc.GetProto(doc); }); +Scripting.addGlobal(function setChildLayout(target: any, source: any) { Doc.setChildLayout(target, source); }); Scripting.addGlobal(function getAlias(doc: any) { return Doc.MakeAlias(doc); }); Scripting.addGlobal(function getCopy(doc: any, copyProto: any) { return doc.isTemplateDoc ? Doc.ApplyTemplate(doc) : Doc.MakeCopy(doc, copyProto); }); Scripting.addGlobal(function copyField(field: any) { return ObjectField.MakeCopy(field); }); diff --git a/src/server/authentication/models/current_user_utils.ts b/src/server/authentication/models/current_user_utils.ts index 5ce707011..e6f202685 100644 --- a/src/server/authentication/models/current_user_utils.ts +++ b/src/server/authentication/models/current_user_utils.ts @@ -23,16 +23,11 @@ export class CurrentUserUtils { public static get MainDocId() { return this.mainDocId; } public static set MainDocId(id: string | undefined) { this.mainDocId = id; } @computed public static get UserDocument() { return Doc.UserDoc(); } + @computed public static get ActivePen() { return Doc.UserDoc().activePen instanceof Doc && (Doc.UserDoc().activePen as Doc).pen as Doc; } @observable public static GuestTarget: Doc | undefined; @observable public static GuestWorkspace: Doc | undefined; - private static createUserDocument(id: string): Doc { - let doc = new Doc(id, true); - doc.title = Doc.CurrentUserEmail; - return this.updateUserDocument(doc);// this should be the last - } - // a default set of note types .. not being used yet... static setupNoteTypes(doc: Doc) { return [ @@ -174,6 +169,7 @@ export class CurrentUserUtils { } static updateUserDocument(doc: Doc) { + doc.title = Doc.CurrentUserEmail; new InkingControl(); (doc.optionalRightCollection === undefined) && CurrentUserUtils.setupMobileUploads(doc); (doc.overlays === undefined) && CurrentUserUtils.setupOverlays(doc); @@ -216,10 +212,8 @@ export class CurrentUserUtils { Doc.CurrentUserEmail = email; await rp.get(Utils.prepend(RouteStore.getUserDocumentId)).then(id => { if (id && id !== "guest") { - return DocServer.GetRefField(id).then(async field => { - let userDoc = field instanceof Doc ? await this.updateUserDocument(field) : this.createUserDocument(id); - runInAction(() => Doc.SetUserDoc(userDoc)); - }); + return DocServer.GetRefField(id).then(async field => + Doc.SetUserDoc(await this.updateUserDocument(field instanceof Doc ? field : new Doc(id, true)))); } else { throw new Error("There should be a user id! Why does Dash think there isn't one?"); } -- cgit v1.2.3-70-g09d2 From e21810a4097e724a378416135c7cc6def7ff022c Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Fri, 18 Oct 2019 11:35:22 -0400 Subject: cleaned up dictation into text notes --- src/client/util/DictationManager.ts | 3 +- src/client/views/MainView.tsx | 3 +- src/client/views/nodes/FormattedTextBox.scss | 16 ++++++---- src/client/views/nodes/FormattedTextBox.tsx | 46 +++++++++++++++++++++++----- 4 files changed, 53 insertions(+), 15 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/util/DictationManager.ts b/src/client/util/DictationManager.ts index 182cfb70a..ae991635f 100644 --- a/src/client/util/DictationManager.ts +++ b/src/client/util/DictationManager.ts @@ -64,7 +64,7 @@ export namespace DictationManager { const intraSession = ". "; const interSession = " ... "; - let isListening = false; + export let isListening = false; let isManuallyStopped = false; let current: string | undefined = undefined; @@ -200,6 +200,7 @@ export namespace DictationManager { if (!isListening || !recognizer) { return; } + isListening = false; isManuallyStopped = true; salvageSession ? recognizer.stop() : recognizer.abort(); // let main = MainView.Instance; diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 0e15784f7..9304f4bef 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -1,7 +1,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faArrowDown, faArrowUp, faBolt, faCaretUp, faCat, faCheck, faChevronRight, faClone, faCloudUploadAlt, faCommentAlt, faCut, faEllipsisV, faExclamation, faFilePdf, faFilm, faFont, faGlobeAsia, faLongArrowAltRight, - faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter + faMusic, faObjectGroup, faPause, faMousePointer, faPenNib, faFileAudio, faPen, faEraser, faPlay, faPortrait, faRedoAlt, faThumbtack, faTree, faTv, faUndoAlt, faHighlighter } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, reaction, runInAction } from 'mobx'; @@ -106,6 +106,7 @@ export class MainView extends React.Component { library.add(faPen); library.add(faHighlighter); library.add(faEraser); + library.add(faFileAudio); library.add(faPenNib); library.add(faFilm); library.add(faMusic); diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 0550f9708..a4acd3b82 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -10,8 +10,7 @@ outline: none !important; } -.formattedTextBox-cont-scroll, -.formattedTextBox-cont-hidden { +.formattedTextBox-cont { cursor: text; background: inherit; padding: 0; @@ -26,10 +25,15 @@ color: initial; height: 100%; pointer-events: all; -} - -.formattedTextBox-cont-hidden { - // pointer-events: none; + overflow-y: auto; + max-height: 100%; + .formattedTextBox-dictation { + height: 20px; + width: 20px; + top: 0px; + left: 0px; + position: absolute; + } } .formattedTextBox-inner-rounded { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 045eee3f7..6f4ced7f3 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,7 +1,7 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEdit, faSmile, faTextHeight, faUpload } from '@fortawesome/free-solid-svg-icons'; import _ from "lodash"; -import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction } from "mobx"; +import { action, computed, IReactionDisposer, Lambda, observable, reaction, runInAction, trace } from "mobx"; import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; @@ -43,6 +43,8 @@ import { FormattedTextBoxComment, formattedTextBoxCommentPlugin } from './Format import React = require("react"); import { ContextMenuProps } from '../ContextMenuItem'; import { ContextMenu } from '../ContextMenu'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { siteVerification } from 'googleapis/build/src/apis/siteVerification'; library.add(faEdit); library.add(faSmile, faTextHeight, faUpload); @@ -369,7 +371,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe specificContextMenu = (e: React.MouseEvent): void => { let funcs: ContextMenuProps[] = []; - funcs.push({ description: "Dictate", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); + funcs.push({ description: "Record Bullet", event: () => { e.stopPropagation(); this.recordBullet(); }, icon: "expand-arrows-alt" }); ["My Text", "Text from Others", "Todo Items", "Important Items", "Ignore Items", "Disagree Items", "By Recent Minute", "By Recent Hour"].forEach(option => funcs.push({ description: (FormattedTextBox._highlights.indexOf(option) === -1 ? "Highlight " : "Unhighlight ") + option, event: () => { @@ -386,6 +388,27 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: funcs, icon: "asterisk" }); } + @observable _recording = false; + + recordDictation = () => { + //this._editorView!.focus(); + if (this._recording) return; + runInAction(() => this._recording = true); + DictationManager.Controls.listen({ + interimHandler: this.setCurrentBulletContent, + continuous: { indefinite: false }, + }).then(results => { + if (results && [DictationManager.Controls.Infringed].includes(results)) { + DictationManager.Controls.stop(); + } + this._editorView!.focus(); + }); + } + stopDictation = (abort: boolean) => { + runInAction(() => this._recording = false); + DictationManager.Controls.stop(!abort); + } + recordBullet = async () => { let completedCue = "end session"; let results = await DictationManager.Controls.listen({ @@ -902,6 +925,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } this.hitBulletTargets(e.clientX, e.clientY, e.nativeEvent.offsetX, e.shiftKey); + if (this._recording) setTimeout(() => { this.stopDictation(true); setTimeout(() => this.recordDictation(), 500); }, 500); } // 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. @@ -965,7 +989,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } onBlur = (e: any) => { - DictationManager.Controls.stop(false); + //DictationManager.Controls.stop(false); if (this._undoTyping) { this._undoTyping.end(); this._undoTyping = undefined; @@ -986,6 +1010,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } + if (this._recording) { this.stopDictation(true); setTimeout(() => this.recordDictation(), 250); } } @action @@ -1001,7 +1026,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } render() { - let style = "hidden"; let rounded = StrCast(this.layoutDoc.borderRounding) === "100%" ? "-rounded" : ""; let interactive: "all" | "none" = InkingControl.Instance.selectedTool || this.layoutDoc.isBackground ? "none" : "all"; @@ -1010,10 +1034,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe FormattedTextBox._toolTipTextMenu!.updateFromDash(this._editorView!, undefined, this.props); } return ( -
this._entered = false)} >
+ +
{ + this._recording ? this.stopDictation(true) : this.recordDictation(); + setTimeout(() => this._editorView!.focus(), 500); + e.stopPropagation(); + }} > + +
); } -- cgit v1.2.3-70-g09d2 From 3d910bae8771e67cabc1500c49b77d425fdf62e9 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 19 Oct 2019 13:49:20 -0400 Subject: cleaned up a bunch layout vs doc issues related to templates --- src/client/views/DocumentDecorations.tsx | 6 +- src/client/views/InkingControl.tsx | 2 +- src/client/views/TemplateMenu.tsx | 4 +- .../views/collections/CollectionStackingView.tsx | 8 +-- .../views/collections/CollectionTreeView.tsx | 6 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 66 +++++++++++----------- .../collections/collectionFreeForm/MarqueeView.tsx | 24 ++++---- .../views/nodes/CollectionFreeFormDocumentView.tsx | 14 ++--- src/client/views/nodes/DocumentContentsView.tsx | 6 +- src/client/views/nodes/DocumentView.tsx | 11 ++-- src/client/views/nodes/FontIconBox.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 6 +- src/client/views/nodes/KeyValueBox.tsx | 2 +- src/new_fields/Doc.ts | 24 +++++--- 14 files changed, 86 insertions(+), 95 deletions(-) (limited to 'src/client/views/nodes/FormattedTextBox.tsx') diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 8409a34da..07af4799b 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -470,7 +470,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> SelectionManager.SelectedDocuments().forEach(element => { if (dX !== 0 || dY !== 0 || dW !== 0 || dH !== 0) { let doc = PositionDocument(element.props.Document); - let layoutDoc = PositionDocument(element.props.Document.layout instanceof Doc ? element.props.Document.layout : element.props.Document); + let layoutDoc = PositionDocument(Doc.Layout(element.props.Document)); let nwidth = doc.nativeWidth || 0; let nheight = doc.nativeHeight || 0; let width = (layoutDoc.width || 0); @@ -478,8 +478,8 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let scale = element.props.ScreenToLocalTransform().Scale * element.props.ContentScaling(); let actualdW = Math.max(width + (dW * scale), 20); let actualdH = Math.max(height + (dH * scale), 20); - layoutDoc.x = (layoutDoc.x || 0) + dX * (actualdW - width); - layoutDoc.y = (layoutDoc.y || 0) + dY * (actualdH - height); + doc.x = (doc.x || 0) + dX * (actualdW - width); + doc.y = (doc.y || 0) + dY * (actualdH - height); let proto = doc.isTemplateField ? doc : Doc.GetProto(element.props.Document); // bcz: 'doc' didn't work here... let fixedAspect = e.ctrlKey || (!layoutDoc.ignoreAspect && nwidth && nheight); if (fixedAspect && e.ctrlKey && layoutDoc.ignoreAspect) { diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index c3a617ffe..105adc03d 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -81,7 +81,7 @@ export class InkingControl { ruleProvider = (view.props.Document.heading && ruleProvider) ? ruleProvider : undefined; ruleProvider && ((Doc.GetProto(ruleProvider)["ruleColor_" + NumCast(view.props.Document.heading)] = Utils.toRGBAstr(color.rgb))); } - (!ruleProvider && targetDoc) && (view.props.Document.backgroundColor = matchedColor); + (!ruleProvider && targetDoc) && (Doc.Layout(view.props.Document).backgroundColor = matchedColor); return { target: targetDoc, diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx index da776f887..d76b033f0 100644 --- a/src/client/views/TemplateMenu.tsx +++ b/src/client/views/TemplateMenu.tsx @@ -117,13 +117,13 @@ export class TemplateMenu extends React.Component { @action toggleChrome = (): void => { this.props.docs.map(dv => { - let layout = dv.Document.layout instanceof Doc ? dv.Document.layout : dv.Document; + let layout = Doc.Layout(dv.Document); layout.chromeStatus = (layout.chromeStatus !== "disabled" ? "disabled" : "enabled"); }); } render() { - let layout = this.props.docs[0].Document.layout instanceof Doc ? this.props.docs[0].Document.layout : this.props.docs[0].Document; + let layout = Doc.Layout(this.props.docs[0].Document); let templateMenu: Array = []; this.props.templates.forEach((checked, template) => templateMenu.push()); diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index f9f040c6b..1a578f4fc 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -74,11 +74,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { this._heightMap.set(key, sectionHeight); } - get layoutDoc() { - // if this document's layout field contains a document (ie, a rendering template), then we will use that - // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. - return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; - } + get layoutDoc() { return Doc.Layout(this.props.Document); } get Sections() { if (!this.sectionFilter || this.sectionHeaders instanceof Promise) return new Map(); @@ -189,7 +185,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } getDocHeight(d?: Doc) { if (!d) return 0; - let layoutDoc = d.layout instanceof Doc ? d.layout : d; + let layoutDoc = Doc.Layout(d); let nw = NumCast(d.nativeWidth); let nh = NumCast(d.nativeHeight); let wid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 5d88e6290..2fbe8527e 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -251,7 +251,7 @@ class TreeView extends React.Component { } docWidth = () => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); - let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; + let layoutDoc = Doc.Layout(this.props.document); if (aspect) return Math.min(layoutDoc[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 20)); return NumCast(this.props.document.nativeWidth) ? Math.min(layoutDoc[WidthSym](), this.props.panelWidth() - 20) : this.props.panelWidth() - 20; } @@ -259,7 +259,7 @@ class TreeView extends React.Component { let bounds = this.boundsOfCollectionDocument; return Math.min(this.MAX_EMBED_HEIGHT, (() => { let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth); - let layoutDoc = this.props.document.layout instanceof Doc ? this.props.document.layout : this.props.document; + let layoutDoc = Doc.Layout(this.props.document); if (aspect) return this.docWidth() * aspect; if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); return layoutDoc.fitWidth ? (!this.props.document.nativeHeight ? NumCast(this.props.containingCollection.height) : @@ -467,7 +467,6 @@ class TreeView extends React.Component { if (!pair.layout || pair.data instanceof Promise) { return (null); } - const childLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; let indent = i === 0 ? undefined : () => { if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) { @@ -483,6 +482,7 @@ class TreeView extends React.Component { let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => { return add(doc, relativeTo ? relativeTo : docs[i], before !== undefined ? before : false); }; + const childLayout = Doc.Layout(pair.layout); let rowHeight = () => { let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0); return aspect ? Math.min(childLayout[WidthSym](), rowWidth()) / aspect : childLayout[HeightSym](); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 932b6722b..eff73b14e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -130,22 +130,22 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { if (super.drop(e, de)) { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { - let firstLayoutDoc = de.data.droppedDocuments[0].layout instanceof Doc ? de.data.droppedDocuments[0].layout : de.data.droppedDocuments[0]; - let z = NumCast(firstLayoutDoc.z); + let firstDoc = de.data.droppedDocuments[0]; + let z = NumCast(firstDoc.z); let x = (z ? xpo : xp) - de.data.offset[0]; let y = (z ? ypo : yp) - de.data.offset[1]; - let dropX = NumCast(firstLayoutDoc.x); - let dropY = NumCast(firstLayoutDoc.y); + let dropX = NumCast(firstDoc.x); + let dropY = NumCast(firstDoc.y); de.data.droppedDocuments.forEach(action((d: Doc) => { - let layoutDoc = d.layout instanceof Doc ? d.layout : d; - layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; - layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + let layoutDoc = Doc.Layout(d); + d.x = x + NumCast(d.x) - dropX; + d.y = y + NumCast(d.y) - dropY; if (!NumCast(layoutDoc.width)) { layoutDoc.width = 300; } - if (!NumCast(d.height)) { - let nw = NumCast(d.nativeWidth); - let nh = NumCast(d.nativeHeight); + if (!NumCast(layoutDoc.height)) { + let nw = NumCast(layoutDoc.nativeWidth); + let nh = NumCast(layoutDoc.nativeHeight); layoutDoc.height = nw && nh ? nh / nw * NumCast(layoutDoc.width) : 300; } this.bringToFront(d); @@ -157,13 +157,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; - let layoutDoc = dragDoc.layout instanceof Doc ? dragDoc.layout : dragDoc; let x = xp - de.data.offset[0]; let y = yp - de.data.offset[1]; - let dropX = NumCast(layoutDoc.x); - let dropY = NumCast(layoutDoc.y); - layoutDoc.x = x + NumCast(layoutDoc.x) - dropX; - layoutDoc.y = y + NumCast(layoutDoc.y) - dropY; + let dropX = NumCast(dragDoc.x); + let dropY = NumCast(dragDoc.y); + dragDoc.x = x + NumCast(dragDoc.x) - dropX; + dragDoc.y = y + NumCast(dragDoc.y) - dropY; de.data.targetContext = this.props.Document; // dropped a PDF annotation, so we need to set the targetContext on the dragData which the PDF view uses at the end of the drop operation this.bringToFront(dragDoc); } @@ -174,9 +173,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { pickCluster(probe: number[]) { return this.childLayoutPairs.map(pair => pair.layout).reduce((cluster, cd) => { - let layoutDoc = cd.layout instanceof Doc ? cd.layout : cd; - let cx = NumCast(layoutDoc.x) - this._clusterDistance; - let cy = NumCast(layoutDoc.y) - this._clusterDistance; + let layoutDoc = Doc.Layout(cd); + let cx = NumCast(cd.x) - this._clusterDistance; + let cy = NumCast(cd.y) - this._clusterDistance; let cw = NumCast(layoutDoc.width) + 2 * this._clusterDistance; let ch = NumCast(layoutDoc.height) + 2 * this._clusterDistance; return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? @@ -218,10 +217,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this._clusterSets.map(set => Doc.IndexOf(doc, set) !== -1 && set.splice(Doc.IndexOf(doc, set), 1)); let preferredInd = NumCast(doc.cluster); doc.cluster = -1; - let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; this._clusterSets.map((set, i) => set.map(member => { - let memberLayout = member.layout instanceof Doc ? member.layout : member; - if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(layoutDoc, memberLayout, this._clusterDistance)) { + if (doc.cluster === -1 && Doc.IndexOf(member, childLayouts) !== -1 && Doc.overlapping(doc, member, this._clusterDistance)) { doc.cluster = i; } })); @@ -293,18 +290,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } let x = this.Document.panX || 0; let y = this.Document.panY || 0; - let docs = this.childLayoutPairs.map(pair => pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout); + let docs = this.childLayoutPairs.map(pair => pair.layout); let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY); if (!this.isAnnotationOverlay) { let minx = docs.length ? NumCast(docs[0].x) : 0; - let maxx = docs.length ? NumCast(docs[0].width) + minx : minx; + let maxx = docs.length ? NumCast(Doc.Layout(docs[0]).width) + minx : minx; let miny = docs.length ? NumCast(docs[0].y) : 0; - let maxy = docs.length ? NumCast(docs[0].height) + miny : miny; + let maxy = docs.length ? NumCast(Doc.Layout(docs[0]).height) + miny : miny; let ranges = docs.filter(doc => doc).reduce((range, doc) => { + let layoutDoc = Doc.Layout(doc); let x = NumCast(doc.x); - let xe = x + NumCast(doc.width); + let xe = x + NumCast(layoutDoc.width); let y = NumCast(doc.y); - let ye = y + NumCast(doc.height); + let ye = y + NumCast(layoutDoc.height); return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]], [range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]]; }, [[minx, maxx], [miny, maxy]]); @@ -408,9 +406,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.props.Document.scrollY = NumCast(doc.y) - offset; } } else { - let layoutdoc = doc.layout instanceof Doc ? doc.layout : doc; - const newPanX = NumCast(layoutdoc.x) + NumCast(layoutdoc.width) / 2; - const newPanY = NumCast(layoutdoc.y) + NumCast(layoutdoc.height) / 2; + let layoutdoc = Doc.Layout(doc); + const newPanX = NumCast(doc.x) + NumCast(layoutdoc.width) / 2; + const newPanY = NumCast(doc.y) + NumCast(layoutdoc.height) / 2; const newState = HistoryUtil.getState(); newState.initializers![this.Document[Id]] = { panX: newPanX, panY: newPanY }; HistoryUtil.pushState(newState); @@ -471,10 +469,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, transition?: string, state?: any } { const script = this.Document.arrangeScript; const result = script && script.script.run(params, console.log); + const layoutDoc = Doc.Layout(params.doc); if (result && result.success) { return { ...result, transition: "transform 1s" }; } - return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(params.doc.width, "number"), height: Cast(params.doc.height, "number") }; + return { x: Cast(params.doc.x, "number"), y: Cast(params.doc.y, "number"), z: Cast(params.doc.z, "number"), width: Cast(layoutDoc.width, "number"), height: Cast(layoutDoc.height, "number") }; } viewDefsToJSX = (views: any[]) => { @@ -530,8 +529,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { - let finalLayout = pair.layout.layout instanceof Doc ? pair.layout.layout : pair.layout; - const pos = this.getCalculatedPositions({ doc: finalLayout, index: i, collection: this.Document, docs: layoutDocs, state }); + const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state }); state = pos.state === undefined ? state : pos.state; layoutPoolData.set(pair, pos); }); @@ -574,7 +572,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let i = 0; const width = Math.max(...docs.map(doc => NumCast(doc.width))); const height = Math.max(...docs.map(doc => NumCast(doc.height))); - for (const doc of docs.map(d => d.layout instanceof Doc ? d.layout : d)) { + for (const doc of docs) { doc.x = x; doc.y = y; x += width + 20; @@ -592,7 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { // find rule colorations when rule providing is turned on by looking at each document to see if it has a coloring -- if so, use it's color as the rule for its associated heading. this.Document.isRuleProvider && this.childLayoutPairs.map(pair => // iterate over the children of a displayed document (or if the displayed document is a template, iterate over the children of that template) - DocListCast(pair.layout.layout instanceof Doc ? pair.layout.layout.data : pair.layout.data).map(heading => { + DocListCast(Doc.Layout(pair.layout).data).map(heading => { let headingPair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, heading); let headingLayout = headingPair.layout && (pair.layout.data_ext instanceof Doc) && (pair.layout.data_ext[`Layout[${headingPair.layout[Id]}]`] as Doc) || headingPair.layout; if (headingLayout && NumCast(headingLayout.heading) > 0 && headingLayout.backgroundColor !== headingLayout.defaultBackgroundColor) { diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 1362736cf..637168f1b 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -281,7 +281,7 @@ export class MarqueeView extends React.Component let bounds = this.Bounds; let selected = this.marqueeSelect(false); if (e.key === "c") { - selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { + selected.map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -296,7 +296,7 @@ export class MarqueeView extends React.Component let palette = Array.from(Cast(this.props.container.props.Document.colorPalette, listSpec("string")) as string[]); let usedPaletted = new Map(); [...this.props.activeDocuments(), this.props.container.props.Document].map(child => { - let bg = StrCast(child.layout instanceof Doc ? child.layout.backgroundColor : child.backgroundColor); + let bg = StrCast(Doc.Layout(child).backgroundColor); if (palette.indexOf(bg) !== -1) { palette.splice(palette.indexOf(bg), 1); if (usedPaletted.get(bg)) usedPaletted.set(bg, usedPaletted.get(bg)! + 1); @@ -325,7 +325,7 @@ export class MarqueeView extends React.Component this.marqueeInkDelete(inkData); if (e.key === "s" || e.key === "S") { - selected.map(d => d.layout instanceof Doc ? d.layout : d).map(d => { + selected.map(d => { this.props.removeDocument(d); d.x = NumCast(d.x) - bounds.left - bounds.width / 2; d.y = NumCast(d.y) - bounds.top - bounds.height / 2; @@ -394,9 +394,9 @@ export class MarqueeView extends React.Component let selRect = this.Bounds; let selection: Doc[] = []; this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => { - let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; - var x = NumCast(layoutDoc.x); - var y = NumCast(layoutDoc.y); + let layoutDoc = Doc.Layout(doc); + var x = NumCast(doc.x); + var y = NumCast(doc.y); var w = NumCast(layoutDoc.width); var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { @@ -405,9 +405,9 @@ export class MarqueeView extends React.Component }); if (!selection.length && selectBackgrounds) { this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => { - let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; - var x = NumCast(layoutDoc.x); - var y = NumCast(layoutDoc.y); + let layoutDoc = Doc.Layout(doc); + var x = NumCast(doc.x); + var y = NumCast(doc.y); var w = NumCast(layoutDoc.width); var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, selRect)) { @@ -422,9 +422,9 @@ export class MarqueeView extends React.Component let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY); let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => { - let layoutDoc = doc.layout instanceof Doc ? doc.layout : doc; - var x = NumCast(layoutDoc.x); - var y = NumCast(layoutDoc.y); + let layoutDoc = Doc.Layout(doc); + var x = NumCast(doc.x); + var y = NumCast(doc.y); var w = NumCast(layoutDoc.width); var h = NumCast(layoutDoc.height); if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) { diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index 8153923de..2243a44d5 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -34,13 +34,13 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema); export class CollectionFreeFormDocumentView extends DocComponent(PositionDocument) { _disposer: IReactionDisposer | undefined = undefined; get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } - get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : NumCast(this.layoutDoc.x); } - get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : NumCast(this.layoutDoc.y); } + get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.dataProvider ? this.dataProvider.x : (this.Document.x || 0); } + get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.dataProvider ? this.dataProvider.y : (this.Document.y || 0); } get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.dataProvider && this.dataProvider ? this.dataProvider.width : this.layoutDoc[WidthSym](); } get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.dataProvider && this.dataProvider ? this.dataProvider.height : this.layoutDoc[HeightSym](); } @computed get dataProvider() { return this.props.dataProvider && this.props.dataProvider(this.props.Document, this.props.DataDoc) ? this.props.dataProvider(this.props.Document, this.props.DataDoc) : undefined; } - @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); } - @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); } + @computed get nativeWidth() { return NumCast(this.layoutDoc.nativeWidth); } + @computed get nativeHeight() { return NumCast(this.layoutDoc.nativeHeight); } @computed get renderScriptDim() { if (this.Document.renderScript) { @@ -92,11 +92,7 @@ export class CollectionFreeFormDocumentView extends DocComponent this.clusterColor; - get layoutDoc() { - // if this document's layout field contains a document (ie, a rendering template), then we will use that - // to determine the render JSX string, otherwise the layout field should directly contain a JSX layout string. - return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; - } + get layoutDoc() { return Doc.Layout(this.props.Document); } @observable _animPos: number[] | undefined = undefined; diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx index d375405b9..bb9315ca3 100644 --- a/src/client/views/nodes/DocumentContentsView.tsx +++ b/src/client/views/nodes/DocumentContentsView.tsx @@ -77,11 +77,7 @@ export class DocumentContentsView extends React.Component(Docu public get ContentDiv() { return this._mainCont.current; } @computed get active() { return SelectionManager.IsSelected(this) || this.props.parentActive(); } @computed get topMost() { return this.props.renderDepth === 0; } - @computed get nativeWidth() { return this.Document.nativeWidth || 0; } - @computed get nativeHeight() { return this.Document.nativeHeight || 0; } + @computed get nativeWidth() { return this.layoutDoc.nativeWidth || 0; } + @computed get nativeHeight() { return this.layoutDoc.nativeHeight || 0; } @computed get onClickHandler() { return this.props.onClick ? this.props.onClick : this.Document.onClick; } @action @@ -287,8 +287,9 @@ export class DocumentView extends DocComponent(Docu @undoBatch deleteClicked = (): void => { SelectionManager.DeselectAll(); this.props.removeDocument && this.props.removeDocument(this.props.Document); } - @undoBatch - static makeNativeViewClicked = async (doc: Doc): Promise => swapViews(doc, "layoutNative", "layoutCustom") + static makeNativeViewClicked = async (doc: Doc): Promise => { + undoBatch(() => swapViews(doc, "layoutNative", "layoutCustom"))(); + } static makeCustomViewClicked = async (doc: Doc, dataDoc: Opt) => { const batch = UndoManager.StartBatch("CustomViewClicked"); @@ -580,7 +581,7 @@ export class DocumentView extends DocComponent(Docu // the document containing the view layout information - will be the Document itself unless the Document has // a layout field. In that case, all layout information comes from there unless overriden by Document get layoutDoc(): Document { - return Document(this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document); + return Document(Doc.Layout(this.props.Document)); } // does Document set a layout prop diff --git a/src/client/views/nodes/FontIconBox.tsx b/src/client/views/nodes/FontIconBox.tsx index c5bf28d5b..fd6a475fb 100644 --- a/src/client/views/nodes/FontIconBox.tsx +++ b/src/client/views/nodes/FontIconBox.tsx @@ -37,7 +37,7 @@ export class FontIconBox extends DocComponent( } render() { let referenceDoc = (this.props.Document.dragFactory instanceof Doc ? this.props.Document.dragFactory : this.props.Document); - let referenceLayout = referenceDoc.layout instanceof Doc ? referenceDoc.layout : referenceDoc; + let referenceLayout = Doc.Layout(referenceDoc); return