From 00416cdb70aa8dd9698972ab0df8ca0a6c8575f9 Mon Sep 17 00:00:00 2001 From: andrewdkim Date: Wed, 2 Oct 2019 15:59:56 -0400 Subject: Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into animationtimeline_two --- .../views/nodes/CollectionFreeFormDocumentView.tsx | 27 ++- src/client/views/nodes/DocumentView.tsx | 21 +- src/client/views/nodes/FieldView.tsx | 3 +- src/client/views/nodes/FormattedTextBox.scss | 20 +- src/client/views/nodes/FormattedTextBox.tsx | 149 +++++++++---- src/client/views/nodes/ImageBox.tsx | 1 + src/client/views/nodes/PDFBox.scss | 132 ++++++++---- src/client/views/nodes/PDFBox.tsx | 230 +++++++++++---------- src/client/views/nodes/PresBox.tsx | 6 +- 9 files changed, 361 insertions(+), 228 deletions(-) (limited to 'src/client/views/nodes') diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index fcf483659..dd063ec9d 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -12,6 +12,7 @@ import { Doc, WidthSym, HeightSym } from "../../../new_fields/Doc"; import { random } from "animejs"; export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps { + dataProvider?: (doc: Doc, dataDoc?: Doc) => { x: number, y: number, width: number, height: number, z: number, transition?: string } | undefined; x?: number; y?: number; width?: number; @@ -32,14 +33,14 @@ export const PositionDocument = makeInterface(documentSchema, positionSchema); @observer export class CollectionFreeFormDocumentView extends DocComponent(PositionDocument) { _disposer: IReactionDisposer | undefined = undefined; - @computed get transform() { return `scale(${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) rotate(${random(-1, 1) * this.props.jitterRotation}deg)`; } - @computed get X() { return this._animPos !== undefined ? this._animPos[0] : this.renderScriptDim ? this.renderScriptDim.x : this.props.x !== undefined ? this.props.x : this.Document.x || 0; } - @computed get Y() { return this._animPos !== undefined ? this._animPos[1] : this.renderScriptDim ? this.renderScriptDim.y : this.props.y !== undefined ? this.props.y : this.Document.y || 0; } - @computed get width() { return this.renderScriptDim ? this.renderScriptDim.width : this.props.width !== undefined ? this.props.width : this.props.Document[WidthSym](); } - @computed get height() { return this.renderScriptDim ? this.renderScriptDim.height : this.props.height !== undefined ? this.props.height : this.props.Document[HeightSym](); } + 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 : 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.props.Document[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.props.Document[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 scaleToOverridingWidth() { return this.width / FieldValue(this.Document.width, this.width); } @computed get renderScriptDim() { if (this.Document.renderScript) { @@ -72,11 +73,12 @@ export class CollectionFreeFormDocumentView extends DocComponent this.props.PanelHeight(); getTransform = (): Transform => this.props.ScreenToLocalTransform() .translate(-this.X, -this.Y) - .scale(1 / this.contentScaling()).scale(1 / this.scaleToOverridingWidth) + .scale(1 / this.contentScaling()) borderRounding = () => { let ruleRounding = this.props.ruleProvider ? StrCast(this.props.ruleProvider["ruleRounding_" + this.Document.heading]) : undefined; - let br = StrCast(((this.layoutDoc.layout as Doc) || this.Document).borderRounding); + let ld = this.layoutDoc.layout instanceof Doc ? this.layoutDoc.layout : undefined; + let br = StrCast((ld || this.props.Document).borderRounding); br = !br && ruleRounding ? ruleRounding : br; if (br.endsWith("%")) { let nativeDim = Math.min(NumCast(this.layoutDoc.nativeWidth), NumCast(this.layoutDoc.nativeHeight)); @@ -98,6 +100,9 @@ export class CollectionFreeFormDocumentView extends DocComponent this.dataProvider ? this.dataProvider.width : this.panelWidth(); + finalPanelHeight = () => this.dataProvider ? this.dataProvider.height : this.panelHeight(); + render() { return (
); diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2ac68fea3..ea669b23c 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -227,15 +227,18 @@ export class DocumentView extends DocComponent(Docu else if (linkedDocs.length) { SelectionManager.DeselectAll(); let first = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor1 as Doc, this.props.Document) && !d.anchor1anchored); + let second = linkedDocs.filter(d => Doc.AreProtosEqual(d.anchor2 as Doc, this.props.Document) && !d.anchor2anchored); let firstUnshown = first.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor2 as Doc).length === 0); + let secondUnshown = second.filter(d => DocumentManager.Instance.getDocumentViews(d.anchor1 as Doc).length === 0); if (firstUnshown.length) first = [firstUnshown[0]]; - let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : [expandedDocs[0], expandedDocs[0]]; + if (secondUnshown.length) second = [secondUnshown[0]]; + let linkedFwdDocs = first.length ? [first[0].anchor2 as Doc, first[0].anchor1 as Doc] : second.length ? [second[0].anchor1 as Doc, second[0].anchor1 as Doc] : undefined; // @TODO: shouldn't always follow target context let linkedFwdContextDocs = [first.length ? await (first[0].targetContext) as Doc : undefined, undefined]; let linkedFwdPage = [first.length ? NumCast(first[0].anchor2Page, undefined) : undefined, undefined]; - if (!linkedFwdDocs.some(l => l instanceof Promise)) { + if (linkedFwdDocs && !linkedFwdDocs.some(l => l instanceof Promise)) { let maxLocation = StrCast(linkedFwdDocs[0].maximizeLocation, "inTab"); let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionDoc) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, false, @@ -258,7 +261,7 @@ export class DocumentView extends DocComponent(Docu this._hitTemplateDrag = true; } } - if (this.active) e.stopPropagation(); // events stop at the lowest document that is active. + if (this.active && e.button === 0 && !this.Document.lockedPosition) e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag); document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); document.addEventListener("pointermove", this.onPointerMove); @@ -266,11 +269,11 @@ export class DocumentView extends DocComponent(Docu } onPointerMove = (e: PointerEvent): void => { if (e.cancelBubble && this.active) { - document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointermove", this.onPointerMove); // stop listening to pointerMove if something else has stopPropagated it (e.g., the MarqueeView) } - else if (!e.cancelBubble && this.active) { + else if (!e.cancelBubble && (SelectionManager.IsSelected(this) || this.props.parentActive()) && !this.Document.lockedPosition) { if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) { - if (!e.altKey && !this.topMost && e.buttons === 1 && !BoolCast(this.Document.lockedPosition)) { + if (!e.altKey && !this.topMost && e.buttons === 1) { document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitTemplateDrag); @@ -300,7 +303,7 @@ export class DocumentView extends DocComponent(Docu await swapViews(this.props.Document, "", "layoutNative"); let options = { title: "data", width: (this.Document.width || 0), x: -(this.Document.width || 0) / 2, y: - (this.Document.height || 0) / 2, }; - let fieldTemplate = this.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : + let fieldTemplate = this.Document.type === DocumentType.TEXT ? Docs.Create.TextDocument(options) : this.Document.type === DocumentType.PDF ? Docs.Create.PdfDocument("http://www.msn.com", options) : this.Document.type === DocumentType.VID ? Docs.Create.VideoDocument("http://www.cs.brown.edu", options) : Docs.Create.ImageDocument("http://www.cs.brown.edu", options); @@ -354,7 +357,7 @@ export class DocumentView extends DocComponent(Docu // const docs = await SearchUtil.Search(`data_l:"${destDoc[Id]}"`, true); // const views = docs.map(d => DocumentManager.Instance.getDocumentView(d)).filter(d => d).map(d => d as DocumentView); de.data.linkSourceDocument !== this.props.Document && - (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc)); + (de.data.linkDocument = DocUtils.MakeLink(de.data.linkSourceDocument, this.props.Document, this.props.ContainingCollectionDoc, undefined, "in-text link being created")); // TODODO this is where in text links get passed } } @@ -605,7 +608,7 @@ export class DocumentView extends DocComponent(Docu ruleColor && !colorSet ? ruleColor : StrCast(this.layoutDoc.backgroundColor) || this.props.backgroundColor(this.Document); const nativeWidth = this.nativeWidth > 0 && !this.Document.ignoreAspect ? `${this.nativeWidth}px` : "100%"; - const nativeHeight = this.Document.ignoreAspect ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : "100%"; + const nativeHeight = this.Document.ignoreAspect || this.props.Document.fitWidth ? this.props.PanelHeight() / this.props.ContentScaling() : this.nativeHeight > 0 ? `${this.nativeHeight}px` : nativeWidth !== "100%" ? nativeWidth : "100%"; const showOverlays = this.props.showOverlays ? this.props.showOverlays(this.Document) : undefined; const showTitle = showOverlays && "title" in showOverlays ? showOverlays.title : this.getLayoutPropStr("showTitle"); const showCaption = showOverlays && "caption" in showOverlays ? showOverlays.caption : this.getLayoutPropStr("showCaption"); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 49fc2263d..b93c78cfd 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -50,7 +50,6 @@ export interface FieldViewProps { PanelWidth: () => number; PanelHeight: () => number; setVideoBox?: (player: VideoBox) => void; - setPdfBox?: (player: PDFBox) => void; ContentScaling: () => number; ChromeHeight?: () => number; } @@ -96,7 +95,7 @@ export class FieldView extends React.Component { return

{field.date.toLocaleString()}

; } else if (field instanceof Doc) { - return

{field.title}

; + return

{field.title && field.title.toString()}

; //return

{field.title + " : id= " + field[Id]}

; // let returnHundred = () => 100; // return ( diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index 0d7277cbe..45e516015 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -164,13 +164,13 @@ ol { counter-reset: deci1 0;} .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;} -.decimal1:before { content: counter(deci1) ")"; counter-increment: deci1; display:inline-block; width: 30} -.decimal2:before { content: counter(deci1) "." counter(deci2) ")"; counter-increment: deci2; display:inline-block; width: 35} -.decimal3:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) ")"; counter-increment: deci3; display:inline-block; width: 35} -.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ")"; counter-increment: deci4; display:inline-block; width: 40} -.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ")"; counter-increment: deci5; display:inline-block; width: 40} -.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ")"; counter-increment: deci6; display:inline-block; width: 45} -.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ")"; counter-increment: deci7; display:inline-block; width: 50} -.upper-alpha:before { content: counter(deci1) "." counter(ualph, upper-alpha) ")"; counter-increment: ualph; display:inline-block; width: 35 } -.lower-roman:before { content: counter(deci1) "." counter(ualph, upper-alpha) "." counter(lroman, lower-roman) ")"; counter-increment: lroman;display:inline-block; 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; width: 35} +.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} +.decimal4:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) ") "; counter-increment: deci4; display:inline-block; min-width: 40} +.decimal5:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) ") "; counter-increment: deci5; display:inline-block; min-width: 40} +.decimal6:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) ") "; counter-increment: deci6; display:inline-block; min-width: 45} +.decimal7:before { content: counter(deci1) "." counter(deci2) "." counter(deci3) "." counter(deci4) "." counter(deci5) "." counter(deci6) "." counter(deci7) ") "; counter-increment: deci7; display:inline-block; min-width: 50} +.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 eb4718581..63a16f90c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -80,6 +80,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe private _nodeClicked: any; private _undoTyping?: UndoManager.Batch; private _searchReactionDisposer?: Lambda; + private _scrollToRegionReactionDisposer: Opt; private _reactionDisposer: Opt; private _textReactionDisposer: Opt; private _heightReactionDisposer: Opt; @@ -138,6 +139,52 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this.props.isOverlay) { DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined); } + + 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!; } @@ -696,6 +743,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._editorView && this._editorView.destroy(); this._editorView = new EditorView(this._proseRef, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), + handleScrollToSelection: (editorView) => { + let ref = editorView.domAtPos(editorView.state.selection.from); + let refNode = ref.node as any; + while (refNode && !("getBoundingClientRect" in refNode)) refNode = refNode.parentElement; + let r1 = refNode && refNode.getBoundingClientRect(); + let r3 = self._ref.current!.getBoundingClientRect(); + r1 && (self._ref.current!.scrollTop += (r1.top - r3.top) * self.props.ScreenToLocalTransform().Scale); + return true; + }, dispatchTransaction: this.dispatchTransaction, nodeViews: { image(node, view, getPos) { return new ImageResizeView(node, view, getPos, self.props.addDocTab); }, @@ -736,6 +792,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } componentWillUnmount() { + this._scrollToRegionReactionDisposer && this._scrollToRegionReactionDisposer(); this._rulesReactionDisposer && this._rulesReactionDisposer(); this._reactionDisposer && this._reactionDisposer(); this._proxyReactionDisposer && this._proxyReactionDisposer(); @@ -757,6 +814,40 @@ 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(); + } + } + + onPointerUp = (e: React.PointerEvent): void => { + FormattedTextBoxComment.textBox = this; + if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { + e.stopPropagation(); + } + } + + @action + onFocused = (e: React.FocusEvent): void => { + 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 + if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { + e.stopPropagation(); + } + } + + 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; @@ -771,8 +862,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe let node = pcords && this._editorView!.state.doc.nodeAt(pcords.pos); if (node) { let link = node.marks.find(m => m.type === this._editorView!.state.schema.marks.link); - href = link && link.attrs.href; - location = link && link.attrs.location; + if (link && !(link.attrs.docref && link.attrs.title)) { // bcz: getting hacky. this indicates that we clicked on a PDF excerpt quotation. In this case, we don't want to follow the link (we follow only the actual hyperlink for the quotation which is handled above). + href = link && link.attrs.href; + location = link && link.attrs.location; + } } if (href) { if (href.indexOf(Utils.prepend("/doc/")) === 0) { @@ -783,14 +876,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe 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, undefined, undefined, NumCast((jumpToDoc === linkDoc.anchor2 ? linkDoc.anchor2Page : linkDoc.anchor1Page))); return; } } - if (targetContext) { + if (targetContext && (!jumpToDoc || targetContext !== await jumpToDoc.annotationOn)) { DocumentManager.Instance.jumpToDocument(targetContext, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); } else if (jumpToDoc) { DocumentManager.Instance.jumpToDocument(jumpToDoc, ctrlKey, false, document => this.props.addDocTab(document, undefined, location ? location : "inTab")); @@ -812,39 +905,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } - if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { - e.preventDefault(); - } - } - - onPointerUp = (e: React.PointerEvent): void => { - FormattedTextBoxComment.textBox = this; - if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { - e.stopPropagation(); - } - } - - @action - onFocused = (e: React.FocusEvent): void => { - 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 - if (this.props.isSelected() || e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { - e.stopPropagation(); - } - } - - onClick = (e: React.MouseEvent): void => { // 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 }); @@ -852,7 +912,18 @@ 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)) { - this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + 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 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) { + this._editorView!.dispatch(this._editorView!.state.tr.setSelection(new NodeSelection(this._editorView!.state.doc.resolve(pos.pos - 2)))); + } else { + this._editorView!.dispatch(this._editorView!.state.tr.setNodeMarkup(pos.pos - 1, node2.type, { ...node2.attrs, visibility: !node2.attrs.visibility })); + } + } } } } @@ -903,7 +974,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (e.key === "Tab" || e.key === "Enter") { e.preventDefault(); } - this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() })); + this._editorView!.dispatch(this._editorView!.state.tr.removeStoredMark(schema.marks.user_mark.create({})).addStoredMark(schema.marks.user_mark.create({ userid: Doc.CurrentUserEmail, modified: timenow() }))); if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx index 624593245..004f50590 100644 --- a/src/client/views/nodes/ImageBox.tsx +++ b/src/client/views/nodes/ImageBox.tsx @@ -38,6 +38,7 @@ library.add(faFileAudio, faAsterisk); export const pageSchema = createSchema({ curPage: "number", + fitWidth: "boolean" }); interface Window { diff --git a/src/client/views/nodes/PDFBox.scss b/src/client/views/nodes/PDFBox.scss index c88a94c28..2917c81cb 100644 --- a/src/client/views/nodes/PDFBox.scss +++ b/src/client/views/nodes/PDFBox.scss @@ -1,77 +1,67 @@ .pdfBox-cont, .pdfBox-cont-interactive { - display: flex; + display: inline-block; flex-direction: row; height: 100%; - overflow-y: scroll; - overflow-x: hidden; - .pdfBox-scrollHack { - pointer-events: none; - } + width:100%; + overflow: hidden; + position:absolute; + z-index: -1; } .pdfBox-cont { pointer-events: none; - .pdfPage-textlayer { - span { - pointer-events: none !important; - user-select: none; + .collectionFreeFormView-none { + pointer-events: none; + } + .pdfViewer-text { + .textLayer { + span { + user-select: none; + } } } } .pdfBox-cont-interactive { pointer-events: all; - .pdfPage-textlayer { - span { - pointer-events: all !important; - user-select: text; + .pdfViewer-text { + .textLayer { + span { + user-select: text; + } } } } -.react-pdf__Page { - transform-origin: left top; - position: absolute; - top: 0; - left: 0; -} - -.react-pdf__Page__textContent span { - user-select: text; -} - -.react-pdf__Document { - position: absolute; -} - .pdfBox-settingsCont { position: absolute; right: 0; - top: 0; + top: 3; + pointer-events: all; .pdfBox-settingsButton { border-bottom-left-radius: 50%; display: flex; justify-content: space-evenly; align-items: center; - height: 70px; + height: 30px; background: none; padding: 0; .pdfBox-settingsButton-arrow { width: 0; height: 0; - border-top: 25px solid transparent; - border-bottom: 25px solid transparent; - border-right: 25px solid #121721; + border-top: 15px solid transparent; + border-bottom: 15px solid transparent; + border-right: 15px solid #121721; transition: all 0.5s; } .pdfBox-settingsButton-iconCont { background: #121721; - height: 50px; + height: 30px; width: 70px; display: flex; justify-content: center; @@ -86,16 +76,15 @@ } .pdfBox-settingsFlyout { - width: 600px; position: absolute; background: #323232; box-shadow: 3px 3px 3px rgba(0, 0, 0, 0.25); - left: -400px; + right: 20px; border-radius: 7px; padding: 20px; display: flex; flex-direction: column; - font-size: 30px; + font-size: 14px; transition: all 0.5s; .pdfBox-settingsFlyout-title { @@ -108,4 +97,69 @@ grid-template-columns: 47.5% 5% 47.5%; } } -} \ No newline at end of file +} + +.pdfBox-overlayCont { + position: absolute; + width: 100%; + height: 40px; + background: #121721; + bottom: 0; + display: flex; + justify-content: center; + align-items: center; + padding: 20px; + overflow: hidden; + transition: left .5s; + pointer-events: all; + + .pdfBox-searchBar { + width: 70%; + font-size: 14px; + } +} + +.pdfBox-overlayButton { + border-bottom-left-radius: 50%; + display: flex; + justify-content: space-evenly; + align-items: center; + height: 30px; + background: none; + padding: 0; + position: absolute; + pointer-events: all; + + .pdfBox-overlayButton-arrow { + width: 0; + height: 0; + border-top: 15px solid transparent; + border-bottom: 15px solid transparent; + border-right: 15px solid #121721; + transition: all 0.5s; + } + + .pdfBox-overlayButton-iconCont, + .pdfBox-nextIcon, + .pdfBox-prevIcon { + background: #121721; + height: 30px; + width: 70px; + display: flex; + justify-content: center; + align-items: center; + margin-left: -2px; + border-radius: 3px; + } +} + +.pdfBox-overlayButton:hover { + background: none; +} + +.pdfBox-nextIcon { + left: 20; top: 5; height: 30px; position: absolute; +} +.pdfBox-prevIcon { + left: 50; top: 5; height: 30px; position: absolute; +} diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 764051d62..fe71e76fd 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -1,12 +1,12 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; +import { action, computed, IReactionDisposer, observable, reaction, runInAction, untracked, trace } 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 { makeInterface } from "../../../new_fields/Schema"; -import { ComputedField, ScriptField } from '../../../new_fields/ScriptField'; +import { ScriptField } from '../../../new_fields/ScriptField'; import { Cast, NumCast } from "../../../new_fields/Types"; import { PdfField } from "../../../new_fields/URLField"; import { KeyCodes } from '../../northstar/utils/KeyCodes'; @@ -19,82 +19,59 @@ 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) { - public static LayoutString() { return FieldView.LayoutString(PDFBox); } - private _mainCont: React.RefObject = React.createRef(); - private _reactionDisposer?: IReactionDisposer; + 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 _pdfViewer: PDFViewer | undefined; private _keyRef: React.RefObject = React.createRef(); private _valueRef: React.RefObject = React.createRef(); private _scriptRef: React.RefObject = React.createRef(); + @observable private _searching: boolean = false; @observable private _flyout: boolean = false; - @observable private _alt = false; @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() { - this.props.setPdfBox && this.props.setPdfBox(this); - - this.props.Document.curPage = ComputedField.MakeFunction("Math.floor(Number(this.panY) / Number(this.nativeHeight) + 1)"); - const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); if (pdfUrl instanceof PdfField) { Pdfjs.getDocument(pdfUrl.url.pathname).promise.then(pdf => runInAction(() => this._pdf = pdf)); } - this._reactionDisposer = reaction( - () => this.Document.panY, - () => this._mainCont.current && this._mainCont.current.scrollTo({ top: this.Document.panY || 0, behavior: "auto" }) - ); - } - - componentWillUnmount() { - this._reactionDisposer && this._reactionDisposer(); - } - - public GetPage() { - return Math.floor((this.Document.panY || 0) / (this.Document.nativeHeight || 0)) + 1; - } - - @action - public BackPage() { - let cp = Math.ceil((this.Document.panY || 0) / (this.Document.nativeHeight || 0)) + 1; - cp = cp - 1; - if (cp > 0) { - this.Document.panY = (cp - 1) * (this.Document.nativeHeight || 0); - } } - - @action - public GotoPage = (p: number) => { - if (p > 0 && p <= NumCast(this.dataDoc.numPages)) { - this.Document.panY = (p - 1) * (this.Document.nativeHeight || 0); + loaded = (nw: number, nh: number, np: number) => { + this.dataDoc.numPages = np; + if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) { + let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); + this.Document.nativeWidth = nw * 96 / 72; + this.Document.nativeHeight = this.Document.nativeHeight ? nw * 96 / 72 * oldaspect : nh * 96 / 72; } + !this.Document.fitWidth && !this.Document.ignoreAspect && (this.Document.height = this.Document[WidthSym]() * (nh / nw)); } - @action - public ForwardPage() { - let cp = this.GetPage() + 1; - if (cp <= NumCast(this.dataDoc.numPages)) { - this.Document.panY = (cp - 1) * (this.Document.nativeHeight || 0); - } - } - - @action - setPanY = (y: number) => { - this.Document.panY = y; - } + public search(string: string, fwd: boolean) { this._pdfViewer && this._pdfViewer.search(string, fwd); } + public prevAnnotation() { this._pdfViewer && this._pdfViewer.prevAnnotation(); } + public nextAnnotation() { this._pdfViewer && this._pdfViewer.nextAnnotation(); } + public backPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) - 1); } + public gotoPage = (p: number) => { this._pdfViewer!.gotoPage(p); }; + public forwardPage() { this._pdfViewer!.gotoPage(NumCast(this.props.Document.curPage) + 1); } + @undoBatch @action private applyFilter = () => { let scriptText = this._scriptValue ? this._scriptValue : @@ -102,10 +79,6 @@ export class PDFBox extends DocComponent(PdfDocumen this.props.Document.filterScript = ScriptField.MakeFunction(scriptText); } - scrollTo = (y: number) => { - this._mainCont.current && this._mainCont.current.scrollTo({ top: Math.max(y - (this._mainCont.current.offsetHeight / 2), 0), behavior: "auto" }); - } - private resetFilters = () => { this._keyValue = this._valueValue = this._scriptValue = ""; this._keyRef.current && (this._keyRef.current.value = ""); @@ -117,83 +90,112 @@ export class PDFBox extends DocComponent(PdfDocumen private newValueChange = (e: React.ChangeEvent) => this._valueValue = e.currentTarget.value; 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; + settingsPanel() { + let pageBtns = <> + + + ; return !this.props.active() ? (null) : - (
e.stopPropagation()}> - + + +
+ -
-
- Annotation View Settings -
-
- - -
-
- -
-
- - + this.gotoPage(Number(e.currentTarget.value))} + style={{ left: 20, top: 5, height: "30px", width: "30px", position: "absolute", pointerEvents: "all" }} + onClick={action(() => this._pageControls = !this._pageControls)} /> + {this._pageControls ? pageBtns : (null)} +
e.stopPropagation()}> + +
+
+ Annotation View Settings +
+
+ + +
+
+ +
+
+ + +
); } - loaded = (nw: number, nh: number, np: number) => { - this.dataDoc.numPages = np; - if (!this.Document.nativeWidth || !this.Document.nativeHeight || !this.Document.scrollHeight) { - let oldaspect = (this.Document.nativeHeight || 0) / (this.Document.nativeWidth || 1); - this.Document.nativeWidth = nw; - this.Document.nativeHeight = this.Document.nativeHeight ? nw * oldaspect : nh; - this.Document.height = this.Document[WidthSym]() * (nh / nw); - this.Document.scrollHeight = np * this.Document.nativeHeight; - } - } + specificContextMenu = (e: React.MouseEvent): void => { + const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); + let funcs: ContextMenuProps[] = []; + pdfUrl && funcs.push({ description: "Copy path", event: () => Utils.CopyText(pdfUrl.url.pathname), icon: "expand-arrows-alt" }); + funcs.push({ description: "Toggle Fit Width " + (this.Document.fitWidth ? "Off" : "On"), event: () => this.Document.fitWidth = !this.Document.fitWidth, icon: "expand-arrows-alt" }); - @action - onScroll = (e: React.UIEvent) => { - if (e.currentTarget && this.props.ContainingCollectionDoc) { - this.props.Document.panTransformType = "None"; - this.Document.panY = e.currentTarget.scrollTop; - } + ContextMenu.Instance.addItem({ description: "Pdf Funcs...", subitems: funcs, icon: "asterisk" }); } - render() { const pdfUrl = Cast(this.dataDoc[this.props.fieldKey], PdfField); - let classname = "pdfBox-cont" + (this.props.active() && !InkingControl.Instance.selectedTool && !this._alt ? "-interactive" : ""); + let classname = "pdfBox-cont" + (InkingControl.Instance.selectedTool || !this.active ? "" : "-interactive"); return (!(pdfUrl instanceof PdfField) || !this._pdf ?
{`pdf, ${this.dataDoc[this.props.fieldKey]}, not found`}
: -
-
- { + let hit = document.elementFromPoint(e.clientX, e.clientY); + if (hit && hit.localName === "span" && this.props.isSelected()) { // drag selecting text stops propagation + e.button === 0 && e.stopPropagation(); + } + }}> + {this.settingsPanel()}
); diff --git a/src/client/views/nodes/PresBox.tsx b/src/client/views/nodes/PresBox.tsx index e376fbddb..5afd85430 100644 --- a/src/client/views/nodes/PresBox.tsx +++ b/src/client/views/nodes/PresBox.tsx @@ -161,10 +161,8 @@ export class PresBox extends React.Component { //FieldViewProps? if (zoomOut || this.presElementsMappings.get(docAtCurrent)!.showButton) { let prevScale = NumCast(this.childrenDocs[prevSelected].viewScale, null); let curScale = DocumentManager.Instance.getScaleOfDocView(this.childrenDocs[current]); - if (prevScale !== undefined) { - if (prevScale !== curScale) { - DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); - } + if (prevScale !== undefined && prevScale !== curScale) { + DocumentManager.Instance.zoomIntoScale(docAtCurrent, prevScale); } } } -- cgit v1.2.3-70-g09d2