From b9b7409af655a0b936ec884de62b8a243be1011b Mon Sep 17 00:00:00 2001 From: laurawilsonri Date: Mon, 22 Apr 2019 22:13:10 -0400 Subject: added a dropdown for bullet types --- src/client/util/TooltipTextMenu.tsx | 79 ++++++++++++++++++++++++++++++++----- 1 file changed, 70 insertions(+), 9 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 4f0eb7d63..95e7ba2bf 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -11,7 +11,8 @@ import React = require("react"); import "./TooltipTextMenu.scss"; const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands"); import { library } from '@fortawesome/fontawesome-svg-core'; -import { wrapInList, bulletList, liftListItem, listItem } from 'prosemirror-schema-list'; +import { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list'; +import { liftTarget } from 'prosemirror-transform'; import { faListUl, } from '@fortawesome/free-solid-svg-icons'; @@ -29,14 +30,17 @@ export class TooltipTextMenu { private view: EditorView; private fontStyles: MarkType[]; private fontSizes: MarkType[]; + private listTypes: NodeType[]; private editorProps: FieldViewProps; private state: EditorState; private fontSizeToNum: Map; private fontStylesToName: Map; + private listTypeToIcon: Map; private fontSizeIndicator: HTMLSpanElement = document.createElement("span"); //dropdown doms private fontSizeDom: Node; private fontStyleDom: Node; + private listTypeBtnDom: Node; constructor(view: EditorView, editorProps: FieldViewProps) { this.view = view; @@ -58,8 +62,9 @@ export class TooltipTextMenu { { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough") }, { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript") }, { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript") }, - { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }, - { command: lift, dom: this.icon("<", "lift") }, + // { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }, + // { command: wrapInList(schema.nodes.ordered_list), dom: this.icon("1)", "bullets") }, + // { command: lift, dom: this.icon("<", "lift") }, ]; //add menu items items.forEach(({ dom, command }) => { @@ -96,7 +101,11 @@ export class TooltipTextMenu { this.fontSizeToNum.set(schema.marks.p72, 72); this.fontSizes = Array.from(this.fontSizeToNum.keys()); - //this.addFontDropdowns(); + //list types + this.listTypeToIcon = new Map(); + this.listTypeToIcon.set(schema.nodes.bullet_list, ":"); + this.listTypeToIcon.set(schema.nodes.ordered_list, "1)"); + this.listTypes = Array.from(this.listTypeToIcon.keys()); this.update(view, undefined); } @@ -109,7 +118,7 @@ export class TooltipTextMenu { //font SIZES let fontSizeBtns: MenuItem[] = []; this.fontSizeToNum.forEach((number, mark) => { - fontSizeBtns.push(this.dropdownBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); + fontSizeBtns.push(this.dropdownMarkBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); }); if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); } @@ -128,7 +137,7 @@ export class TooltipTextMenu { //font STYLES let fontBtns: MenuItem[] = []; this.fontStylesToName.forEach((name, mark) => { - fontBtns.push(this.dropdownBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); + fontBtns.push(this.dropdownMarkBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); }); if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); } @@ -140,6 +149,29 @@ export class TooltipTextMenu { this.tooltip.appendChild(this.fontStyleDom); } + //will display a remove-list-type button if selection is in list, otherwise will show list type dropdown + updateListItemDropdown(label: string, listTypeBtn: Node) { + //remove old btn + if (listTypeBtn) { this.tooltip.removeChild(listTypeBtn); } + + //Make a dropdown of all list types + let toAdd: MenuItem[] = []; + this.listTypeToIcon.forEach((icon, type) => { + toAdd.push(this.dropdownNodeBtn(icon, "width: 40px;", type, this.view, this.listTypes, this.changeToNodeType)); + }); + //option to remove the list formatting + toAdd.push(this.dropdownNodeBtn("X", "width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType)); + + listTypeBtn = (new Dropdown(toAdd, { + label: label, + css: "color:white; width: 40px;" + }) as MenuItem).render(this.view).dom; + + //add this new button and return it + this.tooltip.appendChild(listTypeBtn); + return listTypeBtn; + } + //for a specific grouping of marks (passed in), remove all and apply the passed-in one to the selected text changeToMarkInGroup(markType: MarkType, view: EditorView, fontMarks: MarkType[]) { let { empty, $cursor, ranges } = view.state.selection as TextSelection; @@ -171,9 +203,18 @@ export class TooltipTextMenu { return toggleMark(markType)(view.state, view.dispatch, view); } - //makes a button for the drop down + //remove all node typeand apply the passed-in one to the selected text + changeToNodeType(nodeType: NodeType | undefined, view: EditorView, allNodes: NodeType[]) { + //remove old + liftListItem(schema.nodes.list_item)(view.state, view.dispatch); + if (nodeType) { //add new + wrapInList(nodeType)(view.state, view.dispatch); + } + } + + //makes a button for the drop down FOR MARKS //css is the style you want applied to the button - dropdownBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { + dropdownMarkBtn(label: string, css: string, markType: MarkType, view: EditorView, changeToMarkInGroup: (markType: MarkType, view: EditorView, groupMarks: MarkType[]) => any, groupMarks: MarkType[]) { return new MenuItem({ title: "", label: label, @@ -186,6 +227,23 @@ export class TooltipTextMenu { } }); } + + //makes a button for the drop down FOR NODE TYPES + //css is the style you want applied to the button + dropdownNodeBtn(label: string, css: string, nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[], changeToNodeInGroup: (nodeType: NodeType | undefined, view: EditorView, groupNodes: NodeType[]) => any) { + return new MenuItem({ + title: "", + label: label, + execEvent: "", + class: "menuicon", + css: css, + enable(state) { return true; }, + run() { + changeToNodeInGroup(nodeType, view, groupNodes); + } + }); + } + // Helper function to create menu icons icon(text: string, name: string) { let span = document.createElement("span"); @@ -262,6 +320,9 @@ export class TooltipTextMenu { this.tooltip.style.width = 225 + "px"; this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px"; + //UPDATE LIST ITEM DROPDOWN + this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom); + //UPDATE FONT STYLE DROPDOWN let activeStyles = this.activeMarksOnSelection(this.fontStyles); if (activeStyles.length === 1) { @@ -288,7 +349,7 @@ export class TooltipTextMenu { } } - //finds all active marks on selection + //finds all active marks on selection in given group activeMarksOnSelection(markGroup: MarkType[]) { //current selection let { empty, $cursor, ranges } = this.view.state.selection as TextSelection; -- cgit v1.2.3-70-g09d2 From 07c95bfdf0d72cc5ee6cd6612632ac91e47274d1 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sat, 27 Apr 2019 22:00:21 -0400 Subject: fixed dragdecorations titling. fixed some event issues with dragging and textbox's context menu on mac --- src/client/util/DragManager.ts | 32 ++++---- src/client/views/DocumentDecorations.tsx | 116 ++++++++++++++-------------- src/client/views/nodes/FormattedTextBox.tsx | 4 +- 3 files changed, 77 insertions(+), 75 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 5aa7ad8e2..136852e12 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -172,6 +172,8 @@ export namespace DragManager { StartDrag([ele], dragData, downX, downY, options); } + export let AbortDrag: () => void = emptyFunction; + function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { if (!dragDiv) { dragDiv = document.createElement("div"); @@ -272,23 +274,32 @@ export namespace DragManager { ); }; - AbortDrag = () => { + let hideDragElements = () => { + dragElements.map(dragElement => dragElement.parentNode == dragDiv && dragDiv.removeChild(dragElement)); + eles.map(ele => (ele.hidden = false)); + }; + let endDrag = () => { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); - dragElements.map(dragElement => { if (dragElement.parentNode == dragDiv) dragDiv.removeChild(dragElement); }); - eles.map(ele => (ele.hidden = false)); + if (options) { + options.handlers.dragComplete({}); + } + } + + AbortDrag = () => { + hideDragElements(); + endDrag(); }; const upHandler = (e: PointerEvent) => { - AbortDrag(); - FinishDrag(eles, e, dragData, options, finishDrag); + hideDragElements(); + dispatchDrag(eles, e, dragData, options, finishDrag); + endDrag(); }; document.addEventListener("pointermove", moveHandler, true); document.addEventListener("pointerup", upHandler); } - export let AbortDrag: () => void = emptyFunction; - - function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { + function dispatchDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { let removed = dragEles.map(dragEle => { let parent = dragEle.parentElement; if (parent) parent.removeChild(dragEle); @@ -313,11 +324,6 @@ export namespace DragManager { } }) ); - - if (options) { - options.handlers.dragComplete({}); - } } - DocumentDecorations.Instance.Hidden = false; } } diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index cacb9aead..d20df5f4a 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -39,30 +39,23 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> private _downY = 0; @observable private _minimizedX = 0; @observable private _minimizedY = 0; - //@observable private _title: string = this._documents[0].props.Document.Title; - @observable private _title: string = this._documents.length > 0 ? this._documents[0].props.Document.Title : ""; + @observable private _title: string = ""; + @observable private _edtingTitle = false; @observable private _fieldKey: Key = KeyStore.Title; @observable private _hidden = false; @observable private _opacity = 1; - @observable private _dragging = false; @observable private _iconifying = false; @observable public Interacting = false; - constructor(props: Readonly<{}>) { super(props); DocumentDecorations.Instance = this; - this.handleChange = this.handleChange.bind(this); this.keyinput = React.createRef(); } - @action - handleChange = (event: any) => { - this._title = event.target.value; - } - - @action - enterPressed = (e: any) => { + @action titleChanged = (event: any) => { this._title = event.target.value; } + @action titleBlur = () => { this._edtingTitle = false; } + @action titleEntered = (e: any) => { var key = e.keyCode || e.which; // enter pressed if (key === 13) { @@ -70,32 +63,49 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (text[0] === '#') { let command = text.slice(1, text.length); this._fieldKey = new Key(command); - // if (command === "Title" || command === "title") { - // this._fieldKey = KeyStore.Title; - // } - // else if (command === "Width" || command === "width") { - // this._fieldKey = KeyStore.Width; - // } - this._title = "changed"; - // TODO: Change field with switch statement + this._title = this.getTitle(); } else { if (this._documents.length > 0) { let field = this._documents[0].props.Document.Get(this._fieldKey); - if (field instanceof TextField) { + if (field instanceof NumberField) { this._documents.forEach(d => - d.props.Document.Set(this._fieldKey, new TextField(this._title))); - } - else if (field instanceof NumberField) { + d.props.Document.SetNumber(this._fieldKey, +this._title)); + } else if (field instanceof TextField || true) { this._documents.forEach(d => - d.props.Document.Set(this._fieldKey, new NumberField(+this._title))); + d.props.Document.SetText(this._fieldKey, this._title)); } - this._title = "changed"; } } e.target.blur(); } } + @action onTitleDown = (e: React.PointerEvent): void => { + this._downX = e.clientX; + this._downY = e.clientY; + e.stopPropagation(); + this.onBackgroundDown(e); + document.removeEventListener("pointermove", this.onTitleMove); + document.removeEventListener("pointerup", this.onTitleUp); + document.addEventListener("pointermove", this.onTitleMove); + document.addEventListener("pointerup", this.onTitleUp); + } + @action onTitleMove = (e: PointerEvent): void => { + if (Math.abs(e.clientX - this._downX) > 4 || Math.abs(e.clientY - this._downY) > 4) { + this.Interacting = true; + } + if (this.Interacting) this.onBackgroundMove(e); + e.stopPropagation(); + } + @action onTitleUp = (e: PointerEvent): void => { + if (Math.abs(e.clientX - this._downX) < 4 || Math.abs(e.clientY - this._downY) < 4) { + this._title = this.getTitle(); + this._edtingTitle = true; + } + document.removeEventListener("pointermove", this.onTitleMove); + document.removeEventListener("pointerup", this.onTitleUp); + this.onBackgroundUp(e); + } @computed get Bounds(): { x: number, y: number, b: number, r: number } { @@ -114,50 +124,40 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> }, { x: Number.MAX_VALUE, y: Number.MAX_VALUE, r: Number.MIN_VALUE, b: Number.MIN_VALUE }); } - - @computed - public get Hidden() { return this._hidden; } - public set Hidden(value: boolean) { this._hidden = value; } - - _lastDrag: number[] = [0, 0]; onBackgroundDown = (e: React.PointerEvent): void => { document.removeEventListener("pointermove", this.onBackgroundMove); - document.addEventListener("pointermove", this.onBackgroundMove); document.removeEventListener("pointerup", this.onBackgroundUp); + document.addEventListener("pointermove", this.onBackgroundMove); document.addEventListener("pointerup", this.onBackgroundUp); - this._lastDrag = [e.clientX, e.clientY]; e.stopPropagation(); - if (e.currentTarget.localName !== "input") { - e.preventDefault(); - } + e.preventDefault(); } @action onBackgroundMove = (e: PointerEvent): void => { let dragDocView = SelectionManager.SelectedDocuments()[0]; const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0); - let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document)); const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top); + let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document)); dragData.xOffset = xoff; dragData.yOffset = yoff; dragData.aliasOnDrop = false; - let move = SelectionManager.SelectedDocuments()[0].props.moveDocument; - dragData.moveDocument = move; - this.Interacting = this._dragging = true; + dragData.moveDocument = SelectionManager.SelectedDocuments()[0].props.moveDocument; + this.Interacting = true; + this._hidden = true; document.removeEventListener("pointermove", this.onBackgroundMove); document.removeEventListener("pointerup", this.onBackgroundUp); - this.Hidden = true; + document.removeEventListener("pointermove", this.onTitleMove); + document.removeEventListener("pointerup", this.onTitleUp); DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, { - handlers: { - dragComplete: action(() => this.Interacting = this._dragging = false), - }, + handlers: { dragComplete: action(() => this._hidden = this.Interacting = false) }, hideSource: true }); e.stopPropagation(); } + @action onBackgroundUp = (e: PointerEvent): void => { - this.Hidden = true; document.removeEventListener("pointermove", this.onBackgroundMove); document.removeEventListener("pointerup", this.onBackgroundUp); e.stopPropagation(); @@ -451,7 +451,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } } - getValue = (): string => { + getTitle = (): string => { if (this._documents.length > 0) { let field = this._documents[0].props.Document.Get(this._fieldKey); if (field instanceof TextField) { @@ -461,7 +461,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> return (field).GetValue().toString(); } } - return this._title; + return "-unset-"; } changeFlyoutContent = (): void => { @@ -473,21 +473,17 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> render() { var bounds = this.Bounds; let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; - if (bounds.x === Number.MAX_VALUE || !seldoc) { - return (null); - } - let minimizeIcon = ( -
- {SelectionManager.SelectedDocuments().length == 1 ? IconBox.DocumentIcon(SelectionManager.SelectedDocuments()[0].props.Document.GetText(KeyStore.Layout, "...")) : "..."} -
); - - if (this.Hidden) { + if (bounds.x === Number.MAX_VALUE || !seldoc || this._hidden) { return (null); } if (isNaN(bounds.r) || isNaN(bounds.b) || isNaN(bounds.x) || isNaN(bounds.y)) { console.log("DocumentDecorations: Bounds Error"); return (null); } + let minimizeIcon = ( +
+ {SelectionManager.SelectedDocuments().length == 1 ? IconBox.DocumentIcon(SelectionManager.SelectedDocuments()[0].props.Document.GetText(KeyStore.Layout, "...")) : "..."} +
); let linkButton = null; if (SelectionManager.SelectedDocuments().length > 0) { @@ -508,7 +504,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> height: (bounds.b - bounds.y + this._resizeBorderWidth) + "px", left: bounds.x - this._resizeBorderWidth / 2, top: bounds.y - this._resizeBorderWidth / 2, - pointerEvents: this._dragging ? "none" : "all", + pointerEvents: this.Interacting ? "none" : "all", zIndex: SelectionManager.SelectedDocuments().length > 1 ? 1000 : 0, }} onPointerDown={this.onBackgroundDown} onContextMenu={(e: React.MouseEvent) => { e.preventDefault(); e.stopPropagation(); }} > @@ -521,7 +517,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> }}> {minimizeIcon} - + {this._edtingTitle ? + : +
{`${this.getTitle()}`}
}
X
e.preventDefault()}>
e.preventDefault()}>
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 02d98de6b..d8cd28b6c 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -170,12 +170,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte @action onPointerDown = (e: React.PointerEvent): void => { if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { - console.log("first"); e.stopPropagation(); } - if (e.button === 2) { + if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { this._gotDown = true; - console.log("second"); e.preventDefault(); } } -- cgit v1.2.3-70-g09d2 From a5ab5caa244da261814bc10f0ae88b302da93951 Mon Sep 17 00:00:00 2001 From: Bob Zeleznik Date: Sun, 28 Apr 2019 22:00:32 -0400 Subject: fixed webbox interactions. got rid of pdf annotation. fixed formatted text menu appearing --- src/client/util/TooltipTextMenu.tsx | 2 +- src/client/views/nodes/FormattedTextBox.tsx | 20 ++++++++++++-------- src/client/views/nodes/PDFBox.tsx | 4 ++-- src/client/views/nodes/WebBox.scss | 17 +++++++++++++++-- src/client/views/nodes/WebBox.tsx | 6 ++++-- 5 files changed, 34 insertions(+), 15 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index 4f0eb7d63..38267f4f9 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -24,7 +24,7 @@ const SVG = "http://www.w3.org/2000/svg"; //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { - private tooltip: HTMLElement; + public tooltip: HTMLElement; private num_icons = 0; private view: EditorView; private fontStyles: MarkType[]; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index d8cd28b6c..fc8d757f6 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -1,4 +1,4 @@ -import { action, IReactionDisposer, reaction, trace, computed } from "mobx"; +import { action, IReactionDisposer, reaction, trace, computed, _allowStateChangesInsideComputed } from "mobx"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; @@ -22,8 +22,6 @@ import React = require("react"); import { SelectionManager } from "../../util/SelectionManager"; import { observer } from "mobx-react"; import { InkingControl } from "../InkingControl"; -const { buildMenuItems } = require("prosemirror-example-setup"); -const { menuBar } = require("prosemirror-menu"); // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // @@ -67,9 +65,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte _applyingChange: boolean = false; + _lastState: any = undefined; dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - const state = this._editorView.state.apply(tx); + const state = this._lastState = this._editorView.state.apply(tx); this._editorView.updateState(state); this._applyingChange = true; this.props.Document.SetDataOnPrototype( @@ -88,10 +87,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte schema, inpRules, //these currently don't do anything, but could eventually be helpful plugins: this.props.isOverlay ? [ + this.tooltipTextMenuPlugin(), history(), keymap(buildKeymap(schema)), keymap(baseKeymap), - this.tooltipTextMenuPlugin(), // this.tooltipLinkingMenuPlugin(), new Plugin({ props: { @@ -169,8 +168,10 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } @action onPointerDown = (e: React.PointerEvent): void => { - if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { + if (e.button === 0 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { e.stopPropagation(); + if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) + this._toolTipTextMenu.tooltip.style.opacity = "0"; } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { this._gotDown = true; @@ -178,7 +179,8 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte } } onPointerUp = (e: React.PointerEvent): void => { - console.log("pointer up"); + if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) + this._toolTipTextMenu.tooltip.style.opacity = "1"; if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } @@ -235,13 +237,15 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte tooltipTextMenuPlugin() { let myprops = this.props; + let self = this; return new Plugin({ view(_editorView) { - return new TooltipTextMenu(_editorView, myprops); + return self._toolTipTextMenu = new TooltipTextMenu(_editorView, myprops); } }); } + _toolTipTextMenu: TooltipTextMenu | undefined = undefined; tooltipLinkingMenuPlugin() { let myprops = this.props; return new Plugin({ diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx index 3d13c6f23..226dfba11 100644 --- a/src/client/views/nodes/PDFBox.tsx +++ b/src/client/views/nodes/PDFBox.tsx @@ -115,7 +115,7 @@ export class PDFBox extends React.Component { child.id = "highlighted"; //@ts-ignore obj.spans.push(child); - child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler + // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler } }); } @@ -138,7 +138,7 @@ export class PDFBox extends React.Component { child.id = "highlighted"; //@ts-ignore temp.spans.push(child); - child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler + // child.addEventListener("mouseover", this.onEnter); //adds mouseover annotation handler } }); diff --git a/src/client/views/nodes/WebBox.scss b/src/client/views/nodes/WebBox.scss index 2ad1129a4..eb09b0693 100644 --- a/src/client/views/nodes/WebBox.scss +++ b/src/client/views/nodes/WebBox.scss @@ -1,12 +1,19 @@ -.webBox-cont { +.webBox-cont, .webBox-cont-interactive{ padding: 0vw; position: absolute; top: 0; left:0; width: 100%; height: 100%; - overflow: scroll; + overflow: auto; + pointer-events: none ; +} +.webBox-cont-interactive { + pointer-events: all; + span { + user-select: text !important; + } } #webBox-htmlSpan { @@ -15,6 +22,12 @@ left:0; } +.webBox-overlay { + width: 100%; + height: 100%; + position: absolute; +} + .webBox-button { padding : 0vw; border: none; diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx index f7f5e31e6..a7c6fda8b 100644 --- a/src/client/views/nodes/WebBox.tsx +++ b/src/client/views/nodes/WebBox.tsx @@ -7,6 +7,7 @@ import { observer } from "mobx-react"; import { computed, reaction, IReactionDisposer } from 'mobx'; import { KeyStore } from '../../../fields/KeyStore'; import { DocumentDecorations } from "../DocumentDecorations"; +import { InkingControl } from "../InkingControl"; @observer export class WebBox extends React.Component { @@ -45,12 +46,13 @@ export class WebBox extends React.Component { let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting; + let classname = "webBox-cont" + (this.props.isSelected() && !InkingControl.Instance.selectedTool && !DocumentDecorations.Instance.Interacting ? "-interactive" : ""); return ( <> -
+
{content}
- {!frozen ? (null) :
} + {!frozen ? (null) :
} ); } } \ No newline at end of file -- cgit v1.2.3-70-g09d2 From 877b104a61d2ab072e3b6a006168ec03e2c46365 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Tue, 30 Apr 2019 00:11:27 -0400 Subject: Mostly fixed lists --- src/client/util/UndoManager.ts | 4 +- .../collectionFreeForm/CollectionFreeFormView.tsx | 1 + src/debug/Test.tsx | 8 +- src/new_fields/Doc.ts | 10 +- src/new_fields/List.ts | 192 +++++++++++++++++---- src/new_fields/util.ts | 8 + 6 files changed, 179 insertions(+), 44 deletions(-) (limited to 'src/client/util') diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index f7c3e5a7b..0b5280c4a 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -141,10 +141,10 @@ export namespace UndoManager { }); //TODO Make this return the return value - export function RunInBatch(fn: () => void, batchName: string) { + export function RunInBatch(fn: () => T, batchName: string) { let batch = StartBatch(batchName); try { - runInAction(fn); + return runInAction(fn); } finally { batch.end(); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index dcded7648..d796bd8d5 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -272,6 +272,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { get views() { let curPage = FieldValue(this.Document.curPage, -1); let docviews = (this.children || []).filter(doc => doc).reduce((prev, doc) => { + if (!FieldValue(doc)) return prev; var page = Cast(doc.page, "number", -1); if (page === curPage || page === -1) { let minim = Cast(doc.isMinimized, "boolean"); diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx index 7415d4b28..04ef00722 100644 --- a/src/debug/Test.tsx +++ b/src/debug/Test.tsx @@ -61,14 +61,12 @@ class Test extends React.Component { assert(test2.testDoc === undefined); test2.url = 35; assert(test2.url === 35); - const l = new List(); + const l = new List(); //TODO push, and other array functions don't go through the proxy - l.push(1); + l.push(doc2); //TODO currently length, and any other string fields will get serialized - l.length = 3; - l[2] = 5; + doc.list = l; console.log(l.slice()); - console.log(SerializationHelper.Serialize(l)); } render() { diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index d15b6309d..6ddb0df89 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -3,7 +3,7 @@ import { serializable, primitive, map, alias, list } from "serializr"; import { autoObject, SerializationHelper, Deserializable } from "../client/util/SerializationHelper"; import { Utils } from "../Utils"; import { DocServer } from "../client/DocServer"; -import { setter, getter, getField, updateFunction } from "./util"; +import { setter, getter, getField, updateFunction, deleteProperty } from "./util"; import { Cast, ToConstructor, PromiseValue, FieldValue } from "./Types"; import { UndoManager, undoBatch } from "../client/util/UndoManager"; import { listSpec } from "./Schema"; @@ -34,7 +34,7 @@ export class Doc extends RefField { set: setter, get: getter, ownKeys: target => Object.keys(target.__fields), - deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); }, + deleteProperty: deleteProperty, defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, }); if (!id || forceSave) { @@ -151,8 +151,8 @@ export namespace Doc { } export function MakeLink(source: Doc, target: Doc): Doc { - let linkDoc = new Doc; - UndoManager.RunInBatch(() => { + return UndoManager.RunInBatch(() => { + let linkDoc = new Doc; linkDoc.title = "New Link"; linkDoc.linkDescription = ""; linkDoc.linkTags = "Default"; @@ -171,8 +171,8 @@ export namespace Doc { source.linkedToDocs = linkedTo = new List(); } linkedTo.push(linkDoc); + return linkDoc; }, "make link"); - return linkDoc; } export function MakeDelegate(doc: Doc): Doc; diff --git a/src/new_fields/List.ts b/src/new_fields/List.ts index e4a80f7a1..ec1bf44a9 100644 --- a/src/new_fields/List.ts +++ b/src/new_fields/List.ts @@ -1,21 +1,173 @@ import { Deserializable, autoObject } from "../client/util/SerializationHelper"; import { Field, Update, Self } from "./Doc"; -import { setter, getter } from "./util"; +import { setter, getter, deleteProperty } from "./util"; import { serializable, alias, list } from "serializr"; import { observable, observe, IArrayChange, IArraySplice, IObservableArray, Lambda, reaction } from "mobx"; import { ObjectField, OnUpdate } from "./ObjectField"; +import { RefField } from "./RefField"; +import { ProxyField } from "./Proxy"; const listHandlers: any = { - push(...items: any[]) { - // console.log("push"); - // console.log(...items); - return this[Self].__fields.push(...items); + /// Mutator methods + copyWithin() { + throw new Error("copyWithin not supported yet"); + }, + fill(value: any, start?: number, end?: number) { + if (value instanceof RefField) { + throw new Error("fill with RefFields not supported yet"); + } + const res = this[Self].__fields.fill(value, start, end); + this[Update](); + return res; }, pop(): any { - return this[Self].__fields.pop(); + const field = toRealField(this[Self].__fields.pop()); + this[Update](); + return field; + }, + push(...items: any[]) { + items = items.map(toObjectField); + const res = this[Self].__fields.push(...items); + this[Update](); + return res; + }, + reverse() { + const res = this[Self].__fields.reverse(); + this[Update](); + return res; + }, + shift() { + const res = toRealField(this[Self].__fields.shift()); + this[Update](); + return res; + }, + sort(cmpFunc: any) { + const res = this[Self].__fields.sort(cmpFunc ? (first: any, second: any) => cmpFunc(toRealField(first), toRealField(second)) : undefined); + this[Update](); + return res; + }, + splice(start: number, deleteCount: number, ...items: any[]) { + items = items.map(toObjectField); + const res = this[Self].__fields.splice(start, deleteCount, ...items); + this[Update](); + return res.map(toRealField); + }, + unshift(...items: any[]) { + items = items.map(toObjectField); + const res = this[Self].__fields.unshift(...items); + this[Update](); + return res; + + }, + /// Accessor methods + concat(...items: any[]) { + return this[Self].__fields.map(toRealField).concat(...items); + }, + includes(valueToFind: any, fromIndex: number) { + const fields = this[Self].__fields; + if (valueToFind instanceof RefField) { + return fields.map(toRealField).includes(valueToFind, fromIndex); + } else { + return fields.includes(valueToFind, fromIndex); + } + }, + indexOf(valueToFind: any, fromIndex: number) { + const fields = this[Self].__fields; + if (valueToFind instanceof RefField) { + return fields.map(toRealField).indexOf(valueToFind, fromIndex); + } else { + return fields.indexOf(valueToFind, fromIndex); + } + }, + join(separator: any) { + return this[Self].__fields.map(toRealField).join(separator); + }, + lastIndexOf(valueToFind: any, fromIndex: number) { + const fields = this[Self].__fields; + if (valueToFind instanceof RefField) { + return fields.map(toRealField).lastIndexOf(valueToFind, fromIndex); + } else { + return fields.lastIndexOf(valueToFind, fromIndex); + } + }, + slice(begin: number, end: number) { + return this[Self].__fields.slice(begin, end).map(toRealField); + }, + + /// Iteration methods + entries() { + return this[Self].__fields.map(toRealField).entries(); + }, + every(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).every(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.every((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + filter(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).filter(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.filter((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + find(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).find(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.find((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + findIndex(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).findIndex(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.findIndex((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + forEach(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).forEach(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.forEach((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + map(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).map(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.map((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + reduce(callback: any, initialValue: any) { + return this[Self].__fields.map(toRealField).reduce(callback, initialValue); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.reduce((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); + }, + reduceRight(callback: any, initialValue: any) { + return this[Self].__fields.map(toRealField).reduceRight(callback, initialValue); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.reduceRight((acc:any, element:any, index:number, array:any) => callback(acc, toRealField(element), index, array), initialValue); + }, + some(callback: any, thisArg: any) { + return this[Self].__fields.map(toRealField).some(callback, thisArg); + // TODO This is probably more efficient, but technically the callback can take the array, which would mean we would have to map the actual array anyway. + // If we don't want to support the array parameter, we should use this version instead + // return this[Self].__fields.some((element:any, index:number, array:any) => callback(toRealField(element), index, array), thisArg); + }, + values() { + return this[Self].__fields.map(toRealField).values(); + }, + [Symbol.iterator]() { + return this[Self].__fields.map(toRealField).values(); } }; +function toObjectField(field: Field) { + return field instanceof RefField ? new ProxyField(field) : field; +} + +function toRealField(field: Field) { + return field instanceof ProxyField ? field.value() : field; +} + function listGetter(target: any, prop: string | number | symbol, receiver: any): any { if (listHandlers.hasOwnProperty(prop)) { return listHandlers[prop]; @@ -38,36 +190,15 @@ interface ListIndexUpdate { type ListUpdate = ListSpliceUpdate | ListIndexUpdate; -const ObserveDisposer = Symbol("Observe Disposer"); - -function listObserver(this: ListImpl, change: IArrayChange | IArraySplice) { - if (change.type === "splice") { - this[Update]({ - index: change.index, - removedCount: change.removedCount, - added: change.added, - type: change.type - }); - } else { - //This should already be handled by the getter for the Proxy - // this[Update]({ - // index: change.index, - // newValue: change.newValue, - // type: change.type - // }); - } -} - @Deserializable("list") class ListImpl extends ObjectField { constructor(fields: T[] = []) { super(); this.___fields = fields; - this[ObserveDisposer] = observe(this.__fields as IObservableArray, listObserver.bind(this)); const list = new Proxy(this, { set: setter, - get: getter, - deleteProperty: () => { throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); }, + get: listGetter, + deleteProperty: deleteProperty, defineProperty: () => { throw new Error("Currently properties can't be defined on documents using Object.defineProperty"); }, }); return list; @@ -82,8 +213,6 @@ class ListImpl extends ObjectField { private set __fields(value) { this.___fields = value; - this[ObserveDisposer](); - this[ObserveDisposer] = observe(this.__fields as IObservableArray, listObserver.bind(this)); } // @serializable(alias("fields", list(autoObject()))) @@ -97,7 +226,6 @@ class ListImpl extends ObjectField { update && update(); } - private [ObserveDisposer]: Lambda; private [Self] = this; } export type List = ListImpl & T[]; diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index 511820115..128817ab8 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -78,6 +78,14 @@ export function getField(target: any, prop: string | number, ignoreProto: boolea return field; } +export function deleteProperty(target: any, prop: string | number | symbol) { + if (typeof prop === "symbol") { + delete target[prop]; + return true; + } + throw new Error("Currently properties can't be deleted from documents, assign to undefined instead"); +} + export function updateFunction(target: any, prop: any, value: any) { return (diff?: any) => { if (!diff) diff = { '$set': { ["fields." + prop]: SerializationHelper.Serialize(value) } }; -- cgit v1.2.3-70-g09d2