From 46d57bc21cda4703855b85a4603bd471975d845b Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 19 Jun 2019 14:25:47 -0400 Subject: deleting annotations --- src/client/views/pdf/PDFMenu.tsx | 40 +++++++++++++++++---- src/client/views/pdf/PDFViewer.tsx | 74 ++++++++++++++++++++++++++++++++------ src/client/views/pdf/Page.tsx | 1 + 3 files changed, 97 insertions(+), 18 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 2462a5f94..5dd7b4bcd 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -19,9 +19,11 @@ export default class PDFMenu extends React.Component { StartDrag: (e: PointerEvent) => void = emptyFunction; Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction; - @observable Highlighting: boolean = false; + Delete: () => void = emptyFunction; + + @observable public Highlighting: boolean = false; + @observable public Status: "pdf" | "annotation" | "" = ""; - private _timeout: NodeJS.Timeout | undefined; private _offsetY: number = 0; private _offsetX: number = 0; private _mainCont: React.RefObject; @@ -66,8 +68,8 @@ export default class PDFMenu extends React.Component { } @action - jumpTo = (x: number, y: number) => { - if (!this._pinned) { + jumpTo = (x: number, y: number, forceJump: boolean = false) => { + if (!this._pinned || forceJump) { this._transition = this._transitionDelay = ""; this._opacity = 1; this._left = x; @@ -159,11 +161,35 @@ export default class PDFMenu extends React.Component { } } + deleteClicked = (e: React.PointerEvent) => { + this.Delete(); + } + + handleContextMenu = (e: React.MouseEvent) => { + e.stopPropagation(); + e.preventDefault(); + } + render() { + let buttons = this.Status === "pdf" ? [ + , + , + + ] : [ + + ]; + return ( -
- @@ -171,7 +197,7 @@ export default class PDFMenu extends React.Component { + */}
); diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index f0e55705d..ea4c0bca2 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -7,7 +7,7 @@ import { Dictionary } from "typescript-collections"; import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; -import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast, FieldValue } from "../../../new_fields/Types"; import { emptyFunction } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { Docs, DocUtils } from "../../documents/Documents"; @@ -138,6 +138,7 @@ class Viewer extends React.Component { makeAnnotationDocument = (sourceDoc: Doc | undefined, s: number, color: string): Doc => { let annoDocs: Doc[] = []; + let mainAnnoDoc = new Doc(); this._savedAnnotations.forEach((key: number, value: HTMLDivElement[]) => { for (let anno of value) { let annoDoc = new Doc(); @@ -147,6 +148,7 @@ class Viewer extends React.Component { if (anno.style.width) annoDoc.width = parseInt(anno.style.width) / scale; annoDoc.page = key; annoDoc.target = sourceDoc; + annoDoc.group = mainAnnoDoc; annoDoc.color = color; annoDoc.type = AnnotationTypes.Region; annoDocs.push(annoDoc); @@ -154,13 +156,12 @@ class Viewer extends React.Component { } }); - let annoDoc = new Doc(); - annoDoc.annotations = new List(annoDocs); + mainAnnoDoc.annotations = new List(annoDocs); if (sourceDoc) { - DocUtils.MakeLink(sourceDoc, annoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title)); + DocUtils.MakeLink(sourceDoc, mainAnnoDoc, undefined, `Annotation from ${StrCast(this.props.parent.Document.title)}`, "", StrCast(this.props.parent.Document.title)); } this._savedAnnotations.clear(); - return annoDoc; + return mainAnnoDoc; } drop = async (e: Event, de: DragManager.DropEvent) => { @@ -508,6 +509,57 @@ interface IAnnotationProps { class RegionAnnotation extends React.Component { @observable private _backgroundColor: string = "red"; + private _reactionDisposer?: IReactionDisposer; + private _mainCont: React.RefObject; + + constructor(props: IAnnotationProps) { + super(props); + + this._mainCont = React.createRef(); + } + + componentDidMount() { + this._reactionDisposer = reaction( + () => BoolCast(this.props.document.delete), + () => { + if (BoolCast(this.props.document.delete)) { + if (this._mainCont.current) { + this._mainCont.current.style.display = "none"; + } + } + }, + { fireImmediately: true } + ); + } + + componentWillUnmount() { + this._reactionDisposer && this._reactionDisposer(); + } + + deleteAnnotation = () => { + let annotation = DocListCast(this.props.parent.props.parent.Document.annotations); + let group = FieldValue(Cast(this.props.document.group, Doc)); + if (group && annotation.indexOf(group) !== -1) { + let newAnnotations = annotation.filter(a => a !== FieldValue(Cast(this.props.document.group, Doc))); + this.props.parent.props.parent.Document.annotations = new List(newAnnotations); + } + + if (group) { + let groupAnnotations = DocListCast(group.annotations); + groupAnnotations.forEach(anno => anno.delete = true); + } + } + + + // annotateThis = (e: PointerEvent) => { + // e.preventDefault(); + // e.stopPropagation(); + // // document that this annotation is linked to + // let targetDoc = Docs.TextDocument({ width: 200, height: 200, title: "New Annotation" }); + // let group = FieldValue(Cast(this.props.document.group, Doc)); + // } + + @action onPointerDown = (e: React.PointerEvent) => { if (e.button === 0) { let targetDoc = Cast(this.props.document.target, Doc, null); @@ -515,16 +567,16 @@ class RegionAnnotation extends React.Component { DocumentManager.Instance.jumpToDocument(targetDoc); } } - // if (e.button === 2) { - // console.log("right"); - // e.stopPropagation(); - // e.preventDefault(); - // } + if (e.button === 2) { + PDFMenu.Instance.Status = "annotation"; + PDFMenu.Instance.Delete = this.deleteAnnotation; + PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true); + } } render() { return ( -
); } diff --git a/src/client/views/pdf/Page.tsx b/src/client/views/pdf/Page.tsx index 39e737c32..734dff7fc 100644 --- a/src/client/views/pdf/Page.tsx +++ b/src/client/views/pdf/Page.tsx @@ -191,6 +191,7 @@ export default class Page extends React.Component { else if (e.button === 0) { PDFMenu.Instance.StartDrag = this.startDrag; PDFMenu.Instance.Highlight = this.highlight; + PDFMenu.Instance.Status = "pdf"; PDFMenu.Instance.fadeOut(true); let target: any = e.target; if (target && target.parentElement === this._textLayer.current) { -- cgit v1.2.3-70-g09d2 From b960a876d6a31b3eaebb0ac6eca6f191a0d4c900 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 19 Jun 2019 14:38:43 -0400 Subject: oop --- src/client/views/pdf/PDFMenu.tsx | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) (limited to 'src') diff --git a/src/client/views/pdf/PDFMenu.tsx b/src/client/views/pdf/PDFMenu.tsx index 5dd7b4bcd..39b15fb11 100644 --- a/src/client/views/pdf/PDFMenu.tsx +++ b/src/client/views/pdf/PDFMenu.tsx @@ -15,7 +15,8 @@ export default class PDFMenu extends React.Component { @observable private _opacity: number = 1; @observable private _transition: string = "opacity 0.5s"; @observable private _transitionDelay: string = ""; - @observable private _pinned: boolean = false; + + @observable public Pinned: boolean = false; StartDrag: (e: PointerEvent) => void = emptyFunction; Highlight: (d: Doc | undefined, color: string | undefined) => void = emptyFunction; @@ -69,7 +70,7 @@ export default class PDFMenu extends React.Component { @action jumpTo = (x: number, y: number, forceJump: boolean = false) => { - if (!this._pinned || forceJump) { + if (!this.Pinned || forceJump) { this._transition = this._transitionDelay = ""; this._opacity = 1; this._left = x; @@ -79,7 +80,7 @@ export default class PDFMenu extends React.Component { @action fadeOut = (forceOut: boolean) => { - if (!this._pinned) { + if (!this.Pinned) { if (this._opacity === 0.2) { this._transition = "opacity 0.1s"; this._transitionDelay = ""; @@ -98,7 +99,7 @@ export default class PDFMenu extends React.Component { @action pointerLeave = (e: React.PointerEvent) => { - if (!this._pinned) { + if (!this.Pinned) { this._transition = "opacity 0.5s"; this._transitionDelay = "1s"; this._opacity = 0.2; @@ -115,8 +116,8 @@ export default class PDFMenu extends React.Component { @action togglePin = (e: React.MouseEvent) => { - this._pinned = !this._pinned; - if (!this._pinned) { + this.Pinned = !this.Pinned; + if (!this.Pinned) { this.Highlighting = false; } } @@ -152,7 +153,7 @@ export default class PDFMenu extends React.Component { @action highlightClicked = (e: React.MouseEvent) => { - if (!this._pinned) { + if (!this.Pinned) { this.Highlight(undefined, "#f4f442"); } else { @@ -178,8 +179,8 @@ export default class PDFMenu extends React.Component { , , ] : [ @@ -198,7 +199,7 @@ export default class PDFMenu extends React.Component { style={this._pinned ? { backgroundColor: "#121212" } : {}}> */} -
+
); } -- cgit v1.2.3-70-g09d2 From b9849810231e540a5898a56012abd32c197b23b5 Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 19 Jun 2019 14:39:15 -0400 Subject: anna --- src/client/views/pdf/PDFViewer.tsx | 1 + 1 file changed, 1 insertion(+) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index ea4c0bca2..f3aa2f5a0 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -570,6 +570,7 @@ class RegionAnnotation extends React.Component { if (e.button === 2) { PDFMenu.Instance.Status = "annotation"; PDFMenu.Instance.Delete = this.deleteAnnotation; + PDFMenu.Instance.Pinned = false; PDFMenu.Instance.jumpTo(e.clientX, e.clientY, true); } } -- cgit v1.2.3-70-g09d2 From 9ab47393a2ce3d174ad3238422c2c310764be9af Mon Sep 17 00:00:00 2001 From: yipstanley Date: Wed, 19 Jun 2019 14:40:28 -0400 Subject: interaction improvements with delete button --- src/client/views/pdf/PDFViewer.tsx | 2 ++ 1 file changed, 2 insertions(+) (limited to 'src') diff --git a/src/client/views/pdf/PDFViewer.tsx b/src/client/views/pdf/PDFViewer.tsx index f3aa2f5a0..7000352e7 100644 --- a/src/client/views/pdf/PDFViewer.tsx +++ b/src/client/views/pdf/PDFViewer.tsx @@ -548,6 +548,8 @@ class RegionAnnotation extends React.Component { let groupAnnotations = DocListCast(group.annotations); groupAnnotations.forEach(anno => anno.delete = true); } + + PDFMenu.Instance.fadeOut(true); } -- cgit v1.2.3-70-g09d2 From 35e73f369a2145d8a042e0011a43e71763d57998 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Wed, 19 Jun 2019 15:02:48 -0400 Subject: added better search to context menu --- src/client/views/ContextMenu.scss | 23 ++++++++++ src/client/views/ContextMenu.tsx | 83 +++++++++++++++++++++++++----------- src/client/views/ContextMenuItem.tsx | 7 +-- src/client/views/MainView.tsx | 2 +- 4 files changed, 84 insertions(+), 31 deletions(-) (limited to 'src') diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index e363c5158..a1a2b06f1 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -53,6 +53,29 @@ font-size: 20px; } +.contextMenu-group { + // width: 11vw; //10vw + height: 30px; //2vh + background: rgb(200, 200, 200); + display: flex; //comment out to allow search icon to be inline with search text + justify-content: left; + align-items: center; + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; + transition: all .1s; + border-width: .11px; + border-style: none; + border-color: $intermediate-color; // rgb(187, 186, 186); + border-bottom-style: solid; + // padding: 10px 0px 10px 0px; + white-space: nowrap; + font-size: 20px; +} + .contextMenu-item:hover { transition: all 0.1s; background: $lighter-alt-accent; diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx index eb1937683..1133f70a1 100644 --- a/src/client/views/ContextMenu.tsx +++ b/src/client/views/ContextMenu.tsx @@ -1,6 +1,6 @@ import React = require("react"); import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem"; -import { observable, action } from "mobx"; +import { observable, action, computed } from "mobx"; import { observer } from "mobx-react"; import "./ContextMenu.scss"; import { library } from '@fortawesome/fontawesome-svg-core'; @@ -17,11 +17,12 @@ export class ContextMenu extends React.Component { @observable private _items: Array = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault(), icon: "smile" }]; @observable private _pageX: number = 0; @observable private _pageY: number = 0; - @observable private _display: string = "none"; + @observable private _display: boolean = false; @observable private _searchString: string = ""; // afaik displaymenu can be called before all the items are added to the menu, so can't determine in displayMenu what the height of the menu will be @observable private _yRelativeToTop: boolean = true; + private _searchRef = React.createRef(); private ref: React.RefObject; @@ -36,7 +37,6 @@ export class ContextMenu extends React.Component { @action clearItems() { this._items = []; - this._display = "none"; } @action @@ -62,34 +62,70 @@ export class ContextMenu extends React.Component { this._searchString = ""; - this._display = "flex"; - } - - intersects = (x: number, y: number): boolean => { - if (this.ref.current && this._display !== "none") { - let menuSize = { width: this.ref.current.getBoundingClientRect().width, height: this.ref.current.getBoundingClientRect().height }; + this._display = true; - let upperLeft = { x: this._pageX, y: this._yRelativeToTop ? this._pageY : window.innerHeight - (this._pageY + menuSize.height) }; - let bottomRight = { x: this._pageX + menuSize.width, y: this._yRelativeToTop ? this._pageY + menuSize.height : window.innerHeight - this._pageY }; - - if (x >= upperLeft.x && x <= bottomRight.x) { - if (y >= upperLeft.y && y <= bottomRight.y) { - return true; - } - } + if (this._searchRef.current) { + this._searchRef.current.focus(); } - return false; } @action closeMenu = () => { this.clearItems(); + this._display = false; } - render() { - let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY, display: this._display } : - { left: this._pageX, bottom: this._pageY, display: this._display }; + @computed get filteredItems() { + const searchString = this._searchString.toLowerCase().split(" "); + const matches = (descriptions: string[]): boolean => { + return searchString.every(s => descriptions.some(desc => desc.includes(s))); + }; + const createGroupHeader = (contents: any) => { + return ( +
+
{contents}
+
+ ); + }; + const createItem = (item: ContextMenuProps) => ; + const flattenItems = (items: ContextMenuProps[], groupFunc: (contents: any) => JSX.Element, getPath: () => string[]) => { + let eles: JSX.Element[] = []; + + for (const item of items) { + const description = item.description.toLowerCase(); + const path = [...getPath(), description]; + if ("subitems" in item) { + const children = flattenItems(item.subitems, contents => groupFunc(<>{item.description} -> {contents}), () => path); + if (children.length || matches(path)) { + eles.push(groupFunc(item.description)); + eles = eles.concat(children); + } + } else { + if (!matches(path)) { + continue; + } + eles.push(createItem(item)); + } + } + return eles; + }; + return flattenItems(this._items, createGroupHeader, () => []); + } + + @computed get menuItems() { + if (!this._searchString) { + return this._items.map(item => ); + } + return this.filteredItems; + } + + render() { + if (!this._display) { + return null; + } + let style = this._yRelativeToTop ? { left: this._pageX, top: this._pageY } : + { left: this._pageX, bottom: this._pageY }; return (
@@ -97,10 +133,9 @@ export class ContextMenu extends React.Component { - + - {this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1). - map(prop => )} + {this.menuItems}
); } diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx index dc0751049..88ebd95bc 100644 --- a/src/client/views/ContextMenuItem.tsx +++ b/src/client/views/ContextMenuItem.tsx @@ -21,9 +21,6 @@ export interface SubmenuProps { closeMenu?: () => void; } -export interface ContextMenuItemProps { - type: ContextMenuProps | SubmenuProps; -} export type ContextMenuProps = OriginalMenuProps | SubmenuProps; @observer @@ -67,7 +64,6 @@ export class ContextMenuItem extends React.Component { return; } this.currentTimeout = setTimeout(action(() => this.overItem = false), ContextMenuItem.timeout); - } render() { @@ -84,8 +80,7 @@ export class ContextMenuItem extends React.Component {
); - } - else { + } else if ("subitems" in this.props) { let submenu = !this.overItem ? (null) :
{this._items.map(prop => )} diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 7f979cd3b..e3d4ff8b5 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -118,7 +118,7 @@ export class MainView extends React.Component { const targets = document.elementsFromPoint(e.x, e.y); if (targets && targets.length && targets[0].className.toString().indexOf("contextMenu") === -1) { - ContextMenu.Instance.clearItems(); + ContextMenu.Instance.closeMenu(); } }), true); } -- cgit v1.2.3-70-g09d2