diff options
31 files changed, 828 insertions, 303 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index 611c61135..e8a80bdc3 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -15,7 +15,7 @@ export class Utils { return v5(seed, v5.URL); } - public static GetScreenTransform(ele: HTMLElement): { scale: number, translateX: number, translateY: number } { + public static GetScreenTransform(ele?: HTMLElement): { scale: number, translateX: number, translateY: number } { if (!ele) { return { scale: 1, translateX: 1, translateY: 1 }; } diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 2ae127e21..b1df89dbd 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -166,7 +166,7 @@ export namespace Docs { } function CreateTextPrototype(): Doc { let textProto = setupPrototypeOptions(textProtoId, "TEXT_PROTO", FormattedTextBox.LayoutString(), - { x: 0, y: 0, width: 300, height: 150, backgroundColor: "#f1efeb" }); + { x: 0, y: 0, width: 300, backgroundColor: "#f1efeb" }); return textProto; } function CreatePdfPrototype(): Doc { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 4c9f798a8..ab00fabe1 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,9 +1,11 @@ -import { action, runInAction } from "mobx"; +import { action, runInAction, observable } from "mobx"; import { Doc, DocListCastAsync } from "../../new_fields/Doc"; import { Cast } from "../../new_fields/Types"; import { emptyFunction } from "../../Utils"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import * as globalCssVariables from "../views/globalCssVariables.scss"; +import { URLField } from "../../new_fields/URLField"; +import { SelectionManager } from "./SelectionManager"; export type dropActionType = "alias" | "copy" | undefined; export function SetupDrag(_reference: React.RefObject<HTMLElement>, docFunc: () => Doc | Promise<Doc>, moveFunc?: DragManager.MoveFunction, dropAction?: dropActionType, options?: any, dontHideOnDrop?: boolean) { @@ -180,10 +182,24 @@ export namespace DragManager { [id: string]: any; } + export class EmbedDragData { + constructor(embeddableSourceDoc: Doc) { + this.embeddableSourceDoc = embeddableSourceDoc; + this.urlField = embeddableSourceDoc.data instanceof URLField ? embeddableSourceDoc.data : undefined; + } + embeddableSourceDoc: Doc; + urlField?: URLField; + [id: string]: any; + } + export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) { StartDrag([ele], dragData, downX, downY, options); } + export function StartEmbedDrag(ele: HTMLElement, dragData: EmbedDragData, downX: number, downY: number, options?: DragOptions) { + StartDrag([ele], dragData, downX, downY, options); + } + export let AbortDrag: () => void = emptyFunction; function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { @@ -194,7 +210,7 @@ export namespace DragManager { dragDiv.style.pointerEvents = "none"; DragManager.Root().appendChild(dragDiv); } - + SelectionManager.SetIsDragging(true); let scaleXs: number[] = []; let scaleYs: number[] = []; let xs: number[] = []; @@ -293,6 +309,7 @@ export namespace DragManager { }; let hideDragElements = () => { + SelectionManager.SetIsDragging(false); dragElements.map(dragElement => dragElement.parentNode === dragDiv && dragDiv.removeChild(dragElement)); eles.map(ele => (ele.hidden = false)); }; diff --git a/src/client/util/ProsemirrorCopy/prompt.js b/src/client/util/ProsemirrorCopy/prompt.js new file mode 100644 index 000000000..b9068195f --- /dev/null +++ b/src/client/util/ProsemirrorCopy/prompt.js @@ -0,0 +1,179 @@ +const prefix = "ProseMirror-prompt" + +export function openPrompt(options) { + let wrapper = document.body.appendChild(document.createElement("div")) + wrapper.className = prefix + wrapper.style.zIndex = 1000; + wrapper.style.width = 250; + wrapper.style.textAlign = "center"; + + let mouseOutside = e => { if (!wrapper.contains(e.target)) close() } + setTimeout(() => window.addEventListener("mousedown", mouseOutside), 50) + let close = () => { + window.removeEventListener("mousedown", mouseOutside) + if (wrapper.parentNode) wrapper.parentNode.removeChild(wrapper) + } + + let domFields = [] + for (let name in options.fields) domFields.push(options.fields[name].render()) + + let submitButton = document.createElement("button") + submitButton.type = "submit" + submitButton.className = prefix + "-submit" + submitButton.textContent = "OK" + let cancelButton = document.createElement("button") + cancelButton.type = "button" + cancelButton.className = prefix + "-cancel" + cancelButton.textContent = "Cancel" + cancelButton.addEventListener("click", close) + + let form = wrapper.appendChild(document.createElement("form")) + let title = document.createElement("h5") + title.style.marginBottom = 15 + title.style.marginTop = 10 + if (options.title) form.appendChild(title).textContent = options.title + domFields.forEach(field => { + form.appendChild(document.createElement("div")).appendChild(field) + }) + let b = document.createElement("div"); + b.style.marginTop = 15; + let buttons = form.appendChild(b) + // buttons.className = prefix + "-buttons" + buttons.appendChild(submitButton) + buttons.appendChild(document.createTextNode(" ")) + buttons.appendChild(cancelButton) + + let box = wrapper.getBoundingClientRect() + wrapper.style.top = options.flyout_top + "px" + wrapper.style.left = options.flyout_left + "px" + + let submit = () => { + let params = getValues(options.fields, domFields) + if (params) { + close() + options.callback(params) + } + } + + form.addEventListener("submit", e => { + e.preventDefault() + submit() + }) + + form.addEventListener("keydown", e => { + if (e.keyCode == 27) { + e.preventDefault() + close() + } else if (e.keyCode == 13 && !(e.ctrlKey || e.metaKey || e.shiftKey)) { + e.preventDefault() + submit() + } else if (e.keyCode == 9) { + window.setTimeout(() => { + if (!wrapper.contains(document.activeElement)) close() + }, 500) + } + }) + + let input = form.elements[0] + if (input) input.focus() +} + +function getValues(fields, domFields) { + let result = Object.create(null), i = 0 + for (let name in fields) { + let field = fields[name], dom = domFields[i++] + let value = field.read(dom), bad = field.validate(value) + if (bad) { + reportInvalid(dom, bad) + return null + } + result[name] = field.clean(value) + } + return result +} + +function reportInvalid(dom, message) { + // FIXME this is awful and needs a lot more work + let parent = dom.parentNode + let msg = parent.appendChild(document.createElement("div")) + msg.style.left = (dom.offsetLeft + dom.offsetWidth + 2) + "px" + msg.style.top = (dom.offsetTop - 5) + "px" + msg.className = "ProseMirror-invalid" + msg.textContent = message + setTimeout(() => parent.removeChild(msg), 1500) +} + +// ::- The type of field that `FieldPrompt` expects to be passed to it. +export class Field { + // :: (Object) + // Create a field with the given options. Options support by all + // field types are: + // + // **`value`**`: ?any` + // : The starting value for the field. + // + // **`label`**`: string` + // : The label for the field. + // + // **`required`**`: ?bool` + // : Whether the field is required. + // + // **`validate`**`: ?(any) → ?string` + // : A function to validate the given value. Should return an + // error message if it is not valid. + constructor(options) { this.options = options } + + // render:: (state: EditorState, props: Object) → dom.Node + // Render the field to the DOM. Should be implemented by all subclasses. + + // :: (dom.Node) → any + // Read the field's value from its DOM node. + read(dom) { return dom.value } + + // :: (any) → ?string + // A field-type-specific validation function. + validateType(_value) { } + + validate(value) { + if (!value && this.options.required) + return "Required field" + return this.validateType(value) || (this.options.validate && this.options.validate(value)) + } + + clean(value) { + return this.options.clean ? this.options.clean(value) : value + } +} + +// ::- A field class for single-line text fields. +export class TextField extends Field { + render() { + let input = document.createElement("input") + input.type = "text" + input.placeholder = this.options.label + input.value = this.options.value || "" + input.autocomplete = "off" + input.style.marginBottom = 4 + input.style.border = "1px solid black" + input.style.padding = "4px 4px" + return input + } +} + + +// ::- A field class for dropdown fields based on a plain `<select>` +// tag. Expects an option `options`, which should be an array of +// `{value: string, label: string}` objects, or a function taking a +// `ProseMirror` instance and returning such an array. +export class SelectField extends Field { + render() { + let select = document.createElement("select") + this.options.options.forEach(o => { + let opt = select.appendChild(document.createElement("option")) + opt.value = o.value + opt.selected = o.value == this.options.value + opt.label = o.label + }) + return select + } +} diff --git a/src/client/util/ProsemirrorKeymap.ts b/src/client/util/ProsemirrorExampleTransfer.ts index 00d086b97..091926d0a 100644 --- a/src/client/util/ProsemirrorKeymap.ts +++ b/src/client/util/ProsemirrorExampleTransfer.ts @@ -1,4 +1,4 @@ -import { Schema } from "prosemirror-model"; +import { Schema, NodeType } from "prosemirror-model"; import { wrapIn, setBlockType, chainCommands, toggleMark, exitCode, joinUp, joinDown, lift, selectParentNode @@ -50,7 +50,7 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: } if (type = schema.nodes.bullet_list) { - bind("Ctrl-b", wrapInList(type)); + bind("Ctrl-.", wrapInList(type)); } if (type = schema.nodes.ordered_list) { bind("Ctrl-n", wrapInList(type)); @@ -97,4 +97,4 @@ export default function buildKeymap<S extends Schema<any>>(schema: S, mapKeys?: } return keys; -}
\ No newline at end of file +} diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 3e3e98206..e1e595925 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,9 +1,9 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"; -import { joinUp, lift, setBlockType, toggleMark, wrapIn } from 'prosemirror-commands'; +import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType, Slice } from "prosemirror-model"; +import { joinUp, lift, setBlockType, toggleMark, wrapIn, selectNodeForward, deleteSelection } from 'prosemirror-commands'; import { redo, undo } from 'prosemirror-history'; import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list'; -import { EditorState, Transaction, NodeSelection, } from "prosemirror-state"; +import { EditorState, Transaction, NodeSelection, TextSelection, Selection, } from "prosemirror-state"; import { EditorView, } from "prosemirror-view"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], @@ -26,6 +26,14 @@ export const nodes: { [index: string]: NodeSpec } = { toDOM() { return pDOM; } }, + // starmine: { + // inline: true, + // attrs: { oldtext: { default: "" } }, + // group: "inline", + // toDOM() { return ["star", "㊉"]; }, + // parseDOM: [{ tag: "star" }] + // }, + // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks. blockquote: { content: "block+", @@ -77,6 +85,30 @@ export const nodes: { [index: string]: NodeSpec } = { group: "inline" }, + star: { + inline: true, + attrs: { + visibility: { default: false }, + oldtext: { default: undefined }, + oldtextslice: { default: undefined }, + oldtextlen: { default: 0 } + + }, + group: "inline", + toDOM(node) { + const attrs = { style: `width: 40px` }; + return ["span", { ...node.attrs, ...attrs }]; + }, + // parseDOM: [{ + // tag: "star", getAttrs(dom: any) { + // return { + // visibility: dom.getAttribute("visibility"), + // oldtext: dom.getAttribute("oldtext"), + // oldtextlen: dom.getAttribute("oldtextlen"), + // } + // } + // }] + }, // :: NodeSpec An inline image (`<img>`) node. Supports `src`, // `alt`, and `href` attributes. The latter two default to the empty // string. @@ -107,6 +139,32 @@ export const nodes: { [index: string]: NodeSpec } = { } }, + video: { + inline: true, + attrs: { + src: {}, + width: { default: "100px" }, + alt: { default: null }, + title: { default: null } + }, + group: "inline", + draggable: true, + parseDOM: [{ + tag: "video[src]", getAttrs(dom: any) { + return { + src: dom.getAttribute("src"), + title: dom.getAttribute("title"), + alt: dom.getAttribute("alt"), + width: Math.min(100, Number(dom.getAttribute("width"))), + } + } + }], + toDOM(node) { + const attrs = { style: `width: ${node.attrs.width}` } + return ["video", { ...node.attrs, ...attrs }] + } + }, + // :: NodeSpec A hard line break, represented in the DOM as `<br>`. hard_break: { inline: true, @@ -222,6 +280,15 @@ export const marks: { [index: string]: MarkSpec } = { toDOM: () => ['sup'] }, + collapse: { + parseDOM: [{ style: 'color: blue' }], + toDOM() { + return ['span', { + style: 'color: blue' + }] + } + }, + // :: MarkSpec Code font mark. Represented as a `<code>` element. code: { @@ -280,6 +347,7 @@ export const marks: { [index: string]: MarkSpec } = { }] }, + /** FONT SIZES */ p10: { @@ -407,6 +475,49 @@ export class ImageResizeView { this._handle.style.display = "none"; } } + +export class SummarizedView { + // TODO: highlight text that is summarized. to find end of region, walk along mark + _collapsed: HTMLElement; + constructor(node: any, view: any, getPos: any) { + this._collapsed = document.createElement("span"); + this._collapsed.textContent = "㊉"; + this._collapsed.style.opacity = "0.5"; + // this._collapsed.style.background = "yellow"; + this._collapsed.style.position = "relative"; + this._collapsed.style.width = "40px"; + this._collapsed.style.height = "20px"; + this._collapsed.onpointerdown = function (e: any) { + console.log("star pressed!"); + if (node.attrs.visibility) { + node.attrs.visibility = !node.attrs.visibility; + console.log("content is visible"); + let y = getPos(); + view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1 + node.attrs.oldtextlen))); + view.dispatch(view.state.tr.deleteSelection(view.state, () => { })); + //this._collapsed.textContent = "㊀"; + } else { + node.attrs.visibility = !node.attrs.visibility; + console.log("content is invisible"); + let y = getPos(); + let mark = view.state.schema.mark(view.state.schema.marks.underline); + console.log("PASTING " + node.attrs.oldtext.toString()); + view.dispatch(view.state.tr.setSelection(TextSelection.create(view.state.doc, y + 1, y + 1))); + view.dispatch(view.state.tr.replaceSelection(node.attrs.oldtext).addMark(view.state.selection.from, view.state.selection.from + node.attrs.oldtextlen, mark)); + //this._collapsed.textContent = "㊉"; + } + e.preventDefault(); + e.stopPropagation(); + }; + (this as any).dom = this._collapsed; + + } + selectNode() { + } + + deselectNode() { + } +} // :: Schema // This schema rougly corresponds to the document schema used by // [CommonMark](http://commonmark.org/), minus the list elements, @@ -415,4 +526,14 @@ export class ImageResizeView { // // To reuse elements from this schema, extend or read from its // `spec.nodes` and `spec.marks` [properties](#model.Schema.spec). -export const schema = new Schema({ nodes, marks });
\ No newline at end of file +export const schema = new Schema({ nodes, marks }); + +const fromJson = schema.nodeFromJSON; + +schema.nodeFromJSON = (json: any) => { + let node = fromJson(json); + if (json.type === "star") { + node.attrs.oldtext = Slice.fromJSON(schema, node.attrs.oldtextslice); + } + return node; +}
\ No newline at end of file diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 0e22d576c..09bccb1a0 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,4 +1,4 @@ -import { observable, action } from "mobx"; +import { observable, action, runInAction } from "mobx"; import { Doc } from "../../new_fields/Doc"; import { DocumentView } from "../views/nodes/DocumentView"; import { FormattedTextBox } from "../views/nodes/FormattedTextBox"; @@ -6,19 +6,27 @@ import { NumCast } from "../../new_fields/Types"; export namespace SelectionManager { class Manager { - @observable - SelectedDocuments: Array<DocumentView> = []; + @observable IsDragging: boolean = false; + @observable SelectedDocuments: Array<DocumentView> = []; @action - SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { + SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it - if (!ctrlPressed) { - this.DeselectAll(); - } + if (manager.SelectedDocuments.indexOf(docView) === -1) { + if (!ctrlPressed) { + this.DeselectAll(); + } - if (manager.SelectedDocuments.indexOf(doc) === -1) { - manager.SelectedDocuments.push(doc); - doc.props.whenActiveChanged(true); + manager.SelectedDocuments.push(docView); + docView.props.whenActiveChanged(true); + } + } + @action + DeselectDoc(docView: DocumentView): void { + let ind = manager.SelectedDocuments.indexOf(docView); + if (ind !== -1) { + manager.SelectedDocuments.splice(ind, 1); + docView.props.whenActiveChanged(false); } } @action @@ -31,8 +39,11 @@ export namespace SelectionManager { const manager = new Manager(); - export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { - manager.SelectDoc(doc, ctrlPressed); + export function DeselectDoc(docView: DocumentView): void { + manager.DeselectDoc(docView); + } + export function SelectDoc(docView: DocumentView, ctrlPressed: boolean): void { + manager.SelectDoc(docView, ctrlPressed); } export function IsSelected(doc: DocumentView): boolean { @@ -51,6 +62,9 @@ export namespace SelectionManager { if (found) manager.SelectDoc(found, false); } + export function SetIsDragging(dragging: boolean) { runInAction(() => manager.IsDragging = dragging); } + export function GetIsDragging() { return manager.IsDragging; } + export function SelectedDocuments(): Array<DocumentView> { return manager.SelectedDocuments; } diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 437da0d63..4d4eb386d 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -36,7 +36,7 @@ position: relative; padding-right: 15px; margin: 3px; - background: #333333; + background: white; border-radius: 3px; text-align: center; } @@ -69,6 +69,7 @@ .ProseMirror-menu-dropdown-menu { z-index: 15; min-width: 6em; + background: white; } .linking { @@ -82,7 +83,7 @@ } .ProseMirror-menu-dropdown-item:hover { - background: #2e2b2b; + background: white; } .ProseMirror-menu-submenu-wrap { @@ -132,7 +133,7 @@ position: relative; min-height: 1em; color: white; - padding: 1px 6px; + padding: 10px 10px; top: 0; left: 0; right: 0; border-bottom: 1px solid silver; background:$dark-color; @@ -155,7 +156,7 @@ } .ProseMirror-icon svg { - fill: currentColor; + fill:white; height: 1em; } @@ -184,7 +185,7 @@ position: fixed; border-radius: 3px; z-index: 11; - box-shadow: -.5px 2px 5px rgba(0, 0, 0, .2); + box-shadow: -.5px 2px 5px white(255, 255, 255, 0.2); } .ProseMirror-prompt h5 { @@ -196,7 +197,7 @@ .ProseMirror-prompt input[type="text"], .ProseMirror-prompt textarea { - background: #eee; + background: white; border: none; outline: none; } @@ -233,15 +234,18 @@ .tooltipMenu { position: absolute; - z-index: 200; - background: $dark-color; + z-index: 50; + background: whitesmoke; border: 1px solid silver; - border-radius: 4px; + border-radius: 15px; padding: 2px 10px; - margin-bottom: 7px; - -webkit-transform: translateX(-50%); - transform: translateX(-50%); + //margin-bottom: 100px; + //-webkit-transform: translateX(-50%); + //transform: translateX(-50%); + transform: translateY(-50%); pointer-events: all; + height: auto; + width:inherit; .ProseMirror-example-setup-style hr { padding: 2px 10px; border: none; @@ -257,34 +261,34 @@ } } -.tooltipMenu:before { - content: ""; - height: 0; width: 0; - position: absolute; - left: 50%; - margin-left: -5px; - bottom: -6px; - border: 5px solid transparent; - border-bottom-width: 0; - border-top-color: silver; - } - .tooltipMenu:after { - content: ""; - height: 0; width: 0; - position: absolute; - left: 50%; - margin-left: -5px; - bottom: -4.5px; - border: 5px solid transparent; - border-bottom-width: 0; - border-top-color: $dark-color; - } +// .tooltipMenu:before { +// content: ""; +// height: 0; width: 0; +// position: absolute; +// left: 50%; +// margin-left: -5px; +// bottom: -6px; +// border: 5px solid transparent; +// border-bottom-width: 0; +// border-top-color: silver; +// } +// .tooltipMenu:after { +// content: ""; +// height: 0; width: 0; +// position: absolute; +// left: 50%; +// margin-left: -5px; +// bottom: -4.5px; +// border: 5px solid transparent; +// border-bottom-width: 0; +// border-top-color: $dark-color; +// } .menuicon { display: inline-block; - border-right: 1px solid rgba(0, 0, 0, 0.2); + border-right: 1px solid white(0, 0, 0, 0.2); //color: rgb(19, 18, 18); - color: white; + color: rgb(226, 21, 21); line-height: 1; padding: 0px 2px; margin: 1px; diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index f517f757a..0a61b7e7d 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -1,6 +1,6 @@ import { action, IReactionDisposer, reaction } from "mobx"; import { Dropdown, DropdownSubmenu, MenuItem, MenuItemSpec, renderGrouped, icons, } from "prosemirror-menu"; //no import css -import { baseKeymap, lift } from "prosemirror-commands"; +import { baseKeymap, lift, deleteSelection } from "prosemirror-commands"; import { history, redo, undo } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; import { EditorState, Transaction, NodeSelection, TextSelection } from "prosemirror-state"; @@ -14,20 +14,22 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { wrapInList, bulletList, liftListItem, listItem, } from 'prosemirror-schema-list'; import { liftTarget, RemoveMarkStep, AddMarkStep } from 'prosemirror-transform'; import { - faListUl, + faListUl, faGrinTongueSquint, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { FieldViewProps } from "../views/nodes/FieldView"; import { throwStatement } from "babel-types"; +const { openPrompt, TextField } = require("./ProsemirrorCopy/prompt.js"); import { View } from "@react-pdf/renderer"; import { DragManager } from "./DragManager"; import { Doc, Opt, Field } from "../../new_fields/Doc"; -import { Utils } from "../northstar/utils/Utils"; import { DocServer } from "../DocServer"; import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { DocumentManager } from "./DocumentManager"; import { Id } from "../../new_fields/FieldSymbols"; +import { Utils } from "../../Utils"; +// import { wrap } from "module"; const SVG = "http://www.w3.org/2000/svg"; @@ -46,6 +48,8 @@ export class TooltipTextMenu { private fontStylesToName: Map<MarkType, string>; private listTypeToIcon: Map<NodeType, string>; private fontSizeIndicator: HTMLSpanElement = document.createElement("span"); + private link: HTMLAnchorElement; + private linkEditor?: HTMLDivElement; private linkText?: HTMLDivElement; private linkDrag?: HTMLImageElement; @@ -68,12 +72,13 @@ export class TooltipTextMenu { library.add(faListUl); //add the buttons to the tooltip let items = [ - { command: toggleMark(schema.marks.strong), dom: this.icon("B", "strong") }, - { command: toggleMark(schema.marks.em), dom: this.icon("i", "em") }, - { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline") }, - { 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: toggleMark(schema.marks.strong), dom: this.icon("B", "strong", "Bold") }, + { command: toggleMark(schema.marks.em), dom: this.icon("i", "em", "Italic") }, + { command: toggleMark(schema.marks.underline), dom: this.icon("U", "underline", "Underline") }, + { command: toggleMark(schema.marks.strikethrough), dom: this.icon("S", "strikethrough", "Strikethrough") }, + { command: toggleMark(schema.marks.superscript), dom: this.icon("s", "superscript", "Superscript") }, + { command: toggleMark(schema.marks.subscript), dom: this.icon("s", "subscript", "Subscript") }, + { command: deleteSelection, dom: this.icon("C", 'collapse', 'Collapse') } // { 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") }, @@ -86,7 +91,10 @@ export class TooltipTextMenu { dom.addEventListener("pointerdown", e => { e.preventDefault(); view.focus(); - command(view.state, view.dispatch, view); + if (dom.contains(e.target as Node)) { + e.stopPropagation(); + command(view.state, view.dispatch, view); + } }); }); @@ -120,6 +128,13 @@ export class TooltipTextMenu { this.listTypeToIcon.set(schema.nodes.ordered_list, "1)"); this.listTypes = Array.from(this.listTypeToIcon.keys()); + this.link = document.createElement("a"); + this.link.target = "_blank"; + this.link.style.color = "white"; + this.tooltip.appendChild(this.link); + + this.tooltip.appendChild(this.createLink().render(this.view).dom); + this.update(view, undefined); } @@ -131,13 +146,13 @@ export class TooltipTextMenu { //font SIZES let fontSizeBtns: MenuItem[] = []; this.fontSizeToNum.forEach((number, mark) => { - fontSizeBtns.push(this.dropdownMarkBtn(String(number), "width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); + fontSizeBtns.push(this.dropdownMarkBtn(String(number), "color: black; width: 50px;", mark, this.view, this.changeToMarkInGroup, this.fontSizes)); }); if (this.fontSizeDom) { this.tooltip.removeChild(this.fontSizeDom); } this.fontSizeDom = (new Dropdown(cut(fontSizeBtns), { label: label, - css: "color:white; min-width: 60px; padding-left: 5px; margin-right: 0;" + css: "color:black; min-width: 60px; padding-left: 5px; margin-right: 0;" }) as MenuItem).render(this.view).dom; this.tooltip.appendChild(this.fontSizeDom); } @@ -150,13 +165,13 @@ export class TooltipTextMenu { //font STYLES let fontBtns: MenuItem[] = []; this.fontStylesToName.forEach((name, mark) => { - fontBtns.push(this.dropdownMarkBtn(name, "font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); + fontBtns.push(this.dropdownMarkBtn(name, "color: black; font-family: " + name + ", sans-serif; width: 125px;", mark, this.view, this.changeToMarkInGroup, this.fontStyles)); }); if (this.fontStyleDom) { this.tooltip.removeChild(this.fontStyleDom); } this.fontStyleDom = (new Dropdown(cut(fontBtns), { label: label, - css: "color:white; width: 125px; margin-left: -3px; padding-left: 2px;" + css: "color:black; width: 125px; margin-left: -3px; padding-left: 2px;" }) as MenuItem).render(this.view).dom; this.tooltip.appendChild(this.fontStyleDom); @@ -165,7 +180,7 @@ export class TooltipTextMenu { updateLinkMenu() { if (!this.linkEditor || !this.linkText) { this.linkEditor = document.createElement("div"); - this.linkEditor.style.color = "white"; + this.linkEditor.style.color = "black"; this.linkText = document.createElement("div"); this.linkText.style.cssFloat = "left"; this.linkText.style.marginRight = "5px"; @@ -174,13 +189,13 @@ export class TooltipTextMenu { this.linkText.style.whiteSpace = "nowrap"; this.linkText.style.width = "150px"; this.linkText.style.overflow = "hidden"; - this.linkText.style.color = "white"; + this.linkText.style.color = "black"; this.linkText.onpointerdown = (e: PointerEvent) => { e.stopPropagation(); }; let linkBtn = document.createElement("div"); linkBtn.textContent = ">>"; - linkBtn.style.width = "20px"; - linkBtn.style.height = "20px"; - linkBtn.style.color = "white"; + linkBtn.style.width = "10px"; + linkBtn.style.height = "10px"; + linkBtn.style.color = "black"; linkBtn.style.cssFloat = "left"; linkBtn.onpointerdown = (e: PointerEvent) => { let node = this.view.state.selection.$from.nodeAfter; @@ -207,7 +222,7 @@ export class TooltipTextMenu { this.linkDrag.src = "https://seogurusnyc.com/wp-content/uploads/2016/12/link-1.png"; this.linkDrag.style.width = "20px"; this.linkDrag.style.height = "20px"; - this.linkDrag.style.color = "white"; + this.linkDrag.style.color = "black"; this.linkDrag.style.background = "black"; this.linkDrag.style.cssFloat = "left"; this.linkDrag.onpointerdown = (e: PointerEvent) => { @@ -228,6 +243,22 @@ export class TooltipTextMenu { this.linkEditor.appendChild(this.linkText); this.linkEditor.appendChild(linkBtn); this.tooltip.appendChild(this.linkEditor); + + let starButton = document.createElement("span"); + // starButton.style.width = '10px'; + // starButton.style.height = '10px'; + starButton.style.marginLeft = '10px'; + starButton.textContent = "Summarize"; + starButton.style.color = 'black'; + starButton.style.height = '20px'; + starButton.style.backgroundColor = 'white'; + starButton.style.textAlign = 'center'; + starButton.onclick = () => { + let state = this.view.state; + this.insertStar(state, this.view.dispatch); + } + + this.tooltip.appendChild(starButton); } let node = this.view.state.selection.$from.nodeAfter; @@ -253,6 +284,16 @@ export class TooltipTextMenu { link = node && node.marks.find(m => m.type.name === "link"); } + insertStar(state: EditorState<any>, dispatch: any) { + console.log("creating star..."); + let newNode = schema.nodes.star.create({ visibility: false, oldtext: state.selection.content(), oldtextslice: state.selection.content().toJSON(), oldtextlen: state.selection.to - state.selection.from }); + if (dispatch) { + console.log(newNode.attrs.oldtext.toString()); + dispatch(state.tr.replaceSelectionWith(newNode)); + } + return true; + } + //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 @@ -261,14 +302,14 @@ export class TooltipTextMenu { //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)); + toAdd.push(this.dropdownNodeBtn(icon, "color: black; 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)); + toAdd.push(this.dropdownNodeBtn("X", "color: black; width: 40px;", undefined, this.view, this.listTypes, this.changeToNodeType)); listTypeBtn = (new Dropdown(toAdd, { label: label, - css: "color:white; width: 40px;" + css: "color:black; width: 40px;" }) as MenuItem).render(this.view).dom; //add this new button and return it @@ -332,6 +373,43 @@ export class TooltipTextMenu { }); } + createLink() { + let markType = schema.marks.link; + return new MenuItem({ + title: "Add or remove link", + label: "Add or remove link", + execEvent: "", + icon: icons.link, + css: "color:white;", + class: "menuicon", + enable(state) { return !state.selection.empty; }, + run: (state, dispatch, view) => { + // to remove link + if (this.markActive(state, markType)) { + toggleMark(markType)(state, dispatch); + return true; + } + // to create link + openPrompt({ + title: "Create a link", + fields: { + href: new TextField({ + label: "Link target", + required: true + }), + title: new TextField({ label: "Title" }) + }, + callback(attrs: any) { + toggleMark(markType, attrs)(view.state, view.dispatch); + view.focus(); + }, + flyout_top: 0, + flyout_left: 0 + }); + } + }); + } + //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<any> | undefined, view: EditorView, groupNodes: NodeType[]) => any) { @@ -348,13 +426,19 @@ export class TooltipTextMenu { }); } + markActive = function (state: EditorState<any>, type: MarkType<Schema<string, string>>) { + let { from, $from, to, empty } = state.selection; + if (empty) return type.isInSet(state.storedMarks || $from.marks()); + else return state.doc.rangeHasMark(from, to, type); + }; + // Helper function to create menu icons - icon(text: string, name: string) { + icon(text: string, name: string, title: string = name) { let span = document.createElement("span"); span.className = name + " menuicon"; - span.title = name; + span.title = title; span.textContent = text; - span.style.color = "white"; + span.style.color = "black"; return span; } @@ -395,6 +479,20 @@ export class TooltipTextMenu { }; } + getMarksInSelection(state: EditorState<any>, targets: MarkType<any>[]) { + let found: Mark<any>[] = []; + let { from, to } = state.selection as TextSelection; + state.doc.nodesBetween(from, to, (node) => { + let marks = node.marks; + if (marks) { + marks.forEach(m => { + if (targets.includes(m.type)) found.push(m); + }); + } + }); + return found; + } + //updates the tooltip menu when the selection changes update(view: EditorView, lastState: EditorState | undefined) { let state = view.state; @@ -408,6 +506,14 @@ export class TooltipTextMenu { return; } + let linksInSelection = this.activeMarksOnSelection([schema.marks.link]); + if (linksInSelection.length > 0) { + let attributes = this.getMarksInSelection(this.view.state, [schema.marks.link])[0].attrs; + this.link.href = attributes.href; + this.link.textContent = attributes.title; + this.link.style.visibility = "visible"; + } else this.link.style.visibility = "hidden"; + // Otherwise, reposition it and update its content this.tooltip.style.display = ""; let { from, to } = state.selection; @@ -421,8 +527,14 @@ export class TooltipTextMenu { let width = Math.abs(start.left - end.left) / 2 * this.editorProps.ScreenToLocalTransform().Scale; let mid = Math.min(start.left, end.left) + width; - this.tooltip.style.width = 225 + "px"; + //this.tooltip.style.width = 225 + "px"; this.tooltip.style.bottom = (box.bottom - start.top) * this.editorProps.ScreenToLocalTransform().Scale + "px"; + this.tooltip.style.top = "-100px"; + //this.tooltip.style.height = "100px"; + + // let transform = this.editorProps.ScreenToLocalTransform(); + // this.tooltip.style.width = `${225 / transform.Scale}px`; + // Utils //UPDATE LIST ITEM DROPDOWN this.listTypeBtnDom = this.updateListItemDropdown(":", this.listTypeBtnDom!); diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 7260b00cf..ceca940b6 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -24,6 +24,7 @@ import { LinkMenu } from "./nodes/LinkMenu"; import { TemplateMenu } from "./TemplateMenu"; import { Template, Templates } from "./Templates"; import React = require("react"); +import { URLField } from '../../new_fields/URLField'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -41,6 +42,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> private _titleHeight = 20; private _linkButton = React.createRef<HTMLDivElement>(); private _linkerButton = React.createRef<HTMLDivElement>(); + private _embedButton = React.createRef<HTMLDivElement>(); private _downX = 0; private _downY = 0; private _iconDoc?: Doc = undefined; @@ -329,12 +331,27 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.removeEventListener("pointerup", this.onLinkerButtonUp); document.addEventListener("pointerup", this.onLinkerButtonUp); } + + onEmbedButtonDown = (e: React.PointerEvent): void => { + e.stopPropagation(); + document.removeEventListener("pointermove", this.onEmbedButtonMoved); + document.addEventListener("pointermove", this.onEmbedButtonMoved); + document.removeEventListener("pointerup", this.onEmbedButtonUp); + document.addEventListener("pointerup", this.onEmbedButtonUp); + } + onLinkerButtonUp = (e: PointerEvent): void => { document.removeEventListener("pointermove", this.onLinkerButtonMoved); document.removeEventListener("pointerup", this.onLinkerButtonUp); e.stopPropagation(); } + onEmbedButtonUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onEmbedButtonMoved); + document.removeEventListener("pointerup", this.onEmbedButtonUp); + e.stopPropagation(); + } + @action onLinkerButtonMoved = (e: PointerEvent): void => { if (this._linkerButton.current !== null) { @@ -354,6 +371,25 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> e.stopPropagation(); } + @action + onEmbedButtonMoved = (e: PointerEvent): void => { + if (this._embedButton.current !== null) { + document.removeEventListener("pointermove", this.onEmbedButtonMoved); + document.removeEventListener("pointerup", this.onEmbedButtonUp); + + let dragDocView = SelectionManager.SelectedDocuments()[0]; + let dragData = new DragManager.EmbedDragData(dragDocView.props.Document); + + DragManager.StartEmbedDrag(dragDocView.ContentDiv!, dragData, e.x, e.y, { + handlers: { + dragComplete: action(emptyFunction), + }, + hideSource: false + }); + } + e.stopPropagation(); + } + onLinkButtonDown = (e: React.PointerEvent): void => { e.stopPropagation(); document.removeEventListener("pointermove", this.onLinkButtonMoved); @@ -442,11 +478,11 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> let actualdH = Math.max(height + (dH * scale), 20); doc.x = (doc.x || 0) + dX * (actualdW - width); doc.y = (doc.y || 0) + dY * (actualdH - height); - let proto = Doc.GetProto(doc); + let proto = Doc.GetProto(element.props.Document); let fixedAspect = e.ctrlKey || (!BoolCast(proto.ignoreAspect, false) && nwidth && nheight); if (fixedAspect && (!nwidth || !nheight)) { - proto.nativeWidth = doc.width; - proto.nativeHeight = doc.height; + proto.nativeWidth = nwidth = doc.width || 0; + proto.nativeHeight = nheight = doc.height || 0; proto.ignoreAspect = true; } if (nwidth > 0 && nheight > 0) { @@ -469,6 +505,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } else { doc.width = zoomBasis * actualdW; doc.height = zoomBasis * actualdH; + proto.autoHeight = undefined; } } }); @@ -510,6 +547,19 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> // e.stopPropagation(); // } + considerEmbed = () => { + let thisDoc = SelectionManager.SelectedDocuments()[0].props.Document; + let canEmbed = thisDoc.data && thisDoc.data instanceof URLField; + if (!canEmbed) return (null); + return ( + <div className="linkButtonWrapper"> + <div style={{ paddingTop: 3, marginLeft: 30 }} title="Drag Embed" className="linkButton-linker" ref={this._embedButton} onPointerDown={this.onEmbedButtonDown}> + <FontAwesomeIcon className="fa-image" icon="image" size="sm" /> + </div> + </div> + ); + } + render() { var bounds = this.Bounds; let seldoc = SelectionManager.SelectedDocuments().length ? SelectionManager.SelectedDocuments()[0] : undefined; @@ -604,6 +654,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> </div> </div> <TemplateMenu docs={SelectionManager.ViewsSortedVertically()} templates={templates} /> + {this.considerEmbed()} </div> </div > </div> diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss index ba4ec41af..465e14d07 100644 --- a/src/client/views/InkingControl.scss +++ b/src/client/views/InkingControl.scss @@ -1,8 +1,6 @@ @import "globalCssVariables"; .inking-control { - position: absolute; - left: 70px; - bottom: 70px; + bottom: 20px; margin: 0; padding: 0; display: flex; @@ -63,10 +61,9 @@ margin-top: 4px; } .ink-panel { - margin: 6px 12px 6px 0; - height: 30px; + height: 24px; vertical-align: middle; - line-height: 36px; + line-height: 28px; padding: 0 10px; color: $intermediate-color; &:first { @@ -114,7 +111,6 @@ border-radius: 11px; width: 22px; height: 22px; - margin-top: 6px; cursor: pointer; text-align: center; // span { // color: $light-color; diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx index d1a6eb7fd..b98132c23 100644 --- a/src/client/views/InkingControl.tsx +++ b/src/client/views/InkingControl.tsx @@ -4,7 +4,6 @@ import React = require("react"); import { observer } from "mobx-react"; import "./InkingControl.scss"; import { library } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons'; import { SelectionManager } from "../util/SelectionManager"; import { InkTool } from "../../new_fields/InkField"; @@ -19,7 +18,6 @@ export class InkingControl extends React.Component { @observable private _selectedColor: string = "rgb(244, 67, 54)"; @observable private _selectedWidth: string = "25"; @observable private _open: boolean = false; - @observable private _colorPickerDisplay = false; constructor(props: Readonly<{}>) { super(props); @@ -57,45 +55,14 @@ export class InkingControl extends React.Component { return this._selectedWidth; } - selected = (tool: InkTool) => { - if (this._selectedTool === tool) { - return { color: "#61aaa3" }; - } - return {}; - } - @action toggleDisplay = () => { this._open = !this._open; + this.switchTool(this._open ? InkTool.Pen : InkTool.None); } - - - @action - toggleColorPicker = () => { - this._colorPickerDisplay = !this._colorPickerDisplay; - } - render() { return ( <ul className="inking-control" style={this._open ? { display: "flex" } : { display: "none" }}> - <li className="ink-tools ink-panel"> - <div className="ink-tool-buttons"> - <button onClick={() => this.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button> - <button onClick={() => this.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Highlighter" /></button> - <button onClick={() => this.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Eraser" /></button> - <button onClick={() => this.switchTool(InkTool.None)} style={this.selected(InkTool.None)}><FontAwesomeIcon icon="ban" size="lg" title="Pointer" /></button> - </div> - </li> - <li className="ink-color ink-panel"> - <label>COLOR: </label> - <div className="ink-color-display" style={{ backgroundColor: this._selectedColor }} - onClick={() => this.toggleColorPicker()}> - {/* {this._colorPickerDisplay ? <span>▼</span> : <span>▲</span>} */} - </div> - <div className="ink-color-picker" style={this._colorPickerDisplay ? { display: "block" } : { display: "none" }}> - <CirclePicker onChange={this.switchColor} circleSize={22} width={"220"} /> - </div> - </li> <li className="ink-size ink-panel"> <label htmlFor="stroke-width">SIZE: </label> <input type="text" min="1" max="100" value={this._selectedWidth} name="stroke-width" diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 57a53c999..690139341 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -126,6 +126,26 @@ button:hover { margin-bottom: 10px; } } +.toolbar-color-picker { + background-color: $light-color; + border-radius: 5px; + padding: 12px; + position: absolute; + bottom: 36px; + left: -3px; + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; +} +.toolbar-color-button { + border-radius: 11px; + width: 22px; + height: 22px; + cursor: pointer; + text-align: center; // span { + // color: $light-color; + // font-size: 8px; + // user-select: none; + // } +} // add nodes menu. Note that the + button is actually an input label, not an actual button. #add-nodes-menu { @@ -133,7 +153,7 @@ button:hover { bottom: 22px; left: 24px; - label { + > label { background: $dark-color; color: $light-color; display: inline-block; @@ -155,15 +175,15 @@ button:hover { transform: scale(1.15); } - input { + > input { display: none; } - input:not(:checked)~#add-options-content { + > input:not(:checked)~#add-options-content { display: none; } - input:checked~label { + > input:checked~label { transform: rotate(45deg); transition: transform 0.5s; cursor: pointer; @@ -207,7 +227,7 @@ ul#add-options-list { list-style: none; padding: 5 0 0 0; - li { + > li { display: inline-block; padding: 0; } diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx index 6bbd70a25..23e90ece5 100644 --- a/src/client/views/MainOverlayTextBox.tsx +++ b/src/client/views/MainOverlayTextBox.tsx @@ -1,14 +1,15 @@ import { action, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; +import "normalize.css"; import * as React from 'react'; -import { emptyFunction, returnTrue, returnZero } from '../../Utils'; +import { Doc } from '../../new_fields/Doc'; +import { BoolCast } from '../../new_fields/Types'; +import { emptyFunction, returnTrue, returnZero, Utils } from '../../Utils'; import { DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; -import "normalize.css"; +import { CollectionDockingView } from './collections/CollectionDockingView'; import "./MainOverlayTextBox.scss"; import { FormattedTextBox } from './nodes/FormattedTextBox'; -import { CollectionDockingView } from './collections/CollectionDockingView'; -import { Doc } from '../../new_fields/Doc'; interface MainOverlayTextBoxProps { } @@ -17,11 +18,14 @@ interface MainOverlayTextBoxProps { export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> { public static Instance: MainOverlayTextBox; @observable _textXf: () => Transform = () => Transform.Identity(); - private _textFieldKey: string = "data"; + public TextFieldKey: string = "data"; private _textColor: string | null = null; private _textHideOnLeave?: boolean; private _textTargetDiv: HTMLDivElement | undefined; private _textProxyDiv: React.RefObject<HTMLDivElement>; + private _textBottom: boolean | undefined; + private _textAutoHeight: boolean | undefined; + @observable public TextDoc?: Doc; constructor(props: MainOverlayTextBoxProps) { super(props); @@ -29,21 +33,32 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> MainOverlayTextBox.Instance = this; reaction(() => FormattedTextBox.InputBoxOverlay, (box?: FormattedTextBox) => { - if (box) this.setTextDoc(box.props.fieldKey, box.CurrentDiv, box.props.ScreenToLocalTransform); - else this.setTextDoc(); + if (box) { + this.TextDoc = box.props.Document; + let sxf = Utils.GetScreenTransform(box ? box.CurrentDiv : undefined); + let xf = () => { box.props.ScreenToLocalTransform(); return new Transform(-sxf.translateX, -sxf.translateY, 1 / sxf.scale); }; + this.setTextDoc(box.props.fieldKey, box.CurrentDiv, xf, BoolCast(box.props.Document.autoHeight, false) || box.props.height === "min-content") + } + else { + this.TextDoc = undefined; + this.setTextDoc(); + } }); } @action - private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform) { + private setTextDoc(textFieldKey?: string, div?: HTMLDivElement, tx?: () => Transform, autoHeight?: boolean) { if (this._textTargetDiv) { this._textTargetDiv.style.color = this._textColor; } - this._textFieldKey = textFieldKey!; - this._textXf = tx ? tx : () => Transform.Identity(); + this._textAutoHeight = autoHeight; + this.TextFieldKey = textFieldKey!; + let txf = tx ? tx : () => Transform.Identity(); + this._textXf = txf; this._textTargetDiv = div; this._textHideOnLeave = FormattedTextBox.InputBoxOverlay && FormattedTextBox.InputBoxOverlay.props.hideOnLeave; if (div) { + this._textBottom = div.parentElement && div.parentElement.style.bottom ? true : false; this._textColor = (getComputedStyle(div) as any).color; div.style.color = "transparent"; } @@ -90,15 +105,21 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps> } } render() { + this.TextDoc; if (FormattedTextBox.InputBoxOverlay && this._textTargetDiv) { let textRect = this._textTargetDiv.getBoundingClientRect(); let s = this._textXf().Scale; - return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${textRect.top}px) scale(${1 / s},${1 / s})`, width: "auto", height: "auto" }} > + let location = this._textBottom ? textRect.bottom : textRect.top; + let hgt = this._textAutoHeight || this._textBottom ? "auto" : this._textTargetDiv.clientHeight; + return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${textRect.left}px, ${location}px) scale(${1 / s},${1 / s})`, width: "auto", height: "0px" }} > <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} - style={{ width: `${textRect.width * s}px`, height: `${textRect.height * s}px` }}> - <FormattedTextBox color={`${this._textColor}`} fieldKey={this._textFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} isSelected={returnTrue} select={emptyFunction} isTopMost={true} - selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} - ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} /> + style={{ width: `${textRect.width * s}px`, height: "0px" }}> + <div style={{ height: hgt, width: "100%", position: "absolute", bottom: this._textBottom ? "0px" : undefined }}> + <FormattedTextBox color={`${this._textColor}`} fieldKey={this.TextFieldKey} hideOnLeave={this._textHideOnLeave} isOverlay={true} Document={FormattedTextBox.InputBoxOverlay.props.Document} + isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true} + ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} + ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyFunction} addDocTab={this.addDocTab} /> + </div> </div> </ div>; } diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx index 42d5929bf..879c2aca0 100644 --- a/src/client/views/MainView.tsx +++ b/src/client/views/MainView.tsx @@ -3,6 +3,7 @@ import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { action, computed, configure, observable, runInAction, trace } from 'mobx'; import { observer } from 'mobx-react'; +import { CirclePicker } from 'react-color'; import "normalize.css"; import * as React from 'react'; import Measure from 'react-measure'; @@ -32,6 +33,7 @@ import { listSpec } from '../../new_fields/Schema'; import { Id } from '../../new_fields/FieldSymbols'; import { HistoryUtil } from '../util/History'; import { CollectionBaseView } from './collections/CollectionBaseView'; +import { InkTool } from '../../new_fields/InkField'; @observer @@ -217,37 +219,28 @@ export class MainView extends React.Component { </Measure>; } + selected = (tool: InkTool) => { + if (!InkingControl.Instance || InkingControl.Instance.selectedTool === InkTool.None) return { display: "none" }; + if (InkingControl.Instance.selectedTool === tool) { + return { color: "#61aaa3", fontSize: "50%" }; + } + return { fontSize: "50%" }; + } + + @observable private _colorPickerDisplay = false; /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */ nodesMenu() { let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg"; - let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c27211_sample_explain.pdf"; - let weburl = "https://cs.brown.edu/courses/cs166/"; - let audiourl = "http://techslides.com/demos/samples/sample.mp3"; - let videourl = "http://techslides.com/demos/sample-videos/small.mp4"; - let addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" })); let addColNode = action(() => Docs.FreeformDocument([], { width: this.pwidth * .7, height: this.pheight, title: "a freeform collection" })); - let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" })); let addTreeNode = action(() => CurrentUserUtils.UserDocument); - //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" })); - // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" })); - let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" })); - let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" })); let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" })); - let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" })); - let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" })); let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [ - [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode], [React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode], - [React.createRef<HTMLDivElement>(), "file-pdf", "Add PDF", addPDFNode], - [React.createRef<HTMLDivElement>(), "film", "Add Video", addVideoNode], - [React.createRef<HTMLDivElement>(), "music", "Add Audio", addAudioNode], - [React.createRef<HTMLDivElement>(), "globe-asia", "Add Web Clipping", addWebNode], [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode], [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode], - [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode], ]; return < div id="add-nodes-menu" > @@ -256,17 +249,38 @@ export class MainView extends React.Component { <div id="add-options-content"> <ul id="add-options-list"> + <li key="search"><button className="add-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button></li> + <li key="undo"><button className="add-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button></li> + <li key="redo"><button className="add-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button></li> + <li key="color"><button className="add-button round-button" title="Redo" onClick={() => this.toggleColorPicker()}><div className="toolbar-color-button" style={{ backgroundColor: InkingControl.Instance.selectedColor }} > + + <div className="toolbar-color-picker" style={this._colorPickerDisplay ? { display: "block" } : { display: "none" }}> + <CirclePicker onChange={InkingControl.Instance.switchColor} circleSize={22} width={"220"} /> + </div> + </div></button></li> {btns.map(btn => <li key={btn[1]} ><div ref={btn[0]}> <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}> <FontAwesomeIcon icon={btn[1]} size="sm" /> </button> </div></li>)} + <li key="ink" style={{ paddingRight: "6px" }}><button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /> </button></li> + <li key="pen"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Pen)} style={this.selected(InkTool.Pen)}><FontAwesomeIcon icon="pen" size="lg" title="Pen" /></button></li> + <li key="marker"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Highlighter)} style={this.selected(InkTool.Highlighter)}><FontAwesomeIcon icon="highlighter" size="lg" title="Pen" /></button></li> + <li key="eraser"><button onClick={() => InkingControl.Instance.switchTool(InkTool.Eraser)} style={this.selected(InkTool.Eraser)}><FontAwesomeIcon icon="eraser" size="lg" title="Pen" /></button></li> + <li key="inkControls"><InkingControl /></li> </ul> </div> </div >; } + + + @action + toggleColorPicker = () => { + this._colorPickerDisplay = !this._colorPickerDisplay; + } + /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */ @computed get miscButtons() { @@ -276,7 +290,6 @@ export class MainView extends React.Component { let logoutRef = React.createRef<HTMLDivElement>(); return [ - <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.DeleteDatabase()}>Clear Database</button>, <div id="toolbar" key="toolbar"> <div ref={notifsRef}> <button className="toolbar-button round-button" title="Notifs" @@ -287,10 +300,6 @@ export class MainView extends React.Component { {length} </div> </div> - <button className="toolbar-button round-button" title="Search" onClick={this.toggleSearch}><FontAwesomeIcon icon="search" size="sm" /></button> - <button className="toolbar-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button> - <button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button> - <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button> </div >, this.isSearchVisible ? <div className="main-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div> : null, <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}> @@ -314,7 +323,6 @@ export class MainView extends React.Component { <ContextMenu /> {this.nodesMenu()} {this.miscButtons} - <InkingControl /> <MainOverlayTextBox /> </div> ); diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx index b0c93ee26..d2d41a4ba 100644 --- a/src/client/views/PresentationView.tsx +++ b/src/client/views/PresentationView.tsx @@ -18,7 +18,6 @@ interface PresListProps extends PresViewProps { gotoDocument(index: number): void; } - @observer /** * Component that takes in a document prop and a boolean whether it's collapsed or not. diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx index 8943bbcaf..3cd525afa 100644 --- a/src/client/views/Templates.tsx +++ b/src/client/views/Templates.tsx @@ -41,21 +41,13 @@ export namespace Templates { export const Caption = new Template("Caption", TemplatePosition.OutterBottom, `<div> - <div style="height:100%; width:100%;position:absolute;">{layout}</div> + <div style="height:100%; width:100%;">{layout}</div> <div style="bottom: 0; font-size:14px; width:100%; position:absolute"> - <FormattedTextBox {...props} fieldKey={"caption"} hideOnLeave={"true"} /> + <FormattedTextBox {...props} height="min-content" fieldKey={"caption"} hideOnLeave={"true"} /> </div> </div>` ); - export const ImageTitle = new Template("Image Title", TemplatePosition.InnerTop, - `<div style="height:100%"> - <div style="height:100%; width:100%;position:absolute;">{layout}</div> - <div style="height:25px; width:100%; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white; "> - <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span> - </div> - </div>` ); - - export const TextTitle = new Template("Text Title", TemplatePosition.InnerTop, + export const Title = new Template("Title", TemplatePosition.InnerTop, `<div style="height:100%"> <div style="height:25px; width:100%; background-color: rgba(0, 0, 0, .4); color: white; "> <span style="text-align:center;width:100%;font-size:20px;position:absolute;overflow:hidden;white-space:nowrap;text-overflow:ellipsis">{props.Document.title}</span> @@ -92,7 +84,7 @@ export namespace Templates { </div > `); } - export const TemplateList: Template[] = [TextTitle, Header, ImageTitle, Caption, Bullet]; + export const TemplateList: Template[] = [Title, Header, Caption, Bullet]; export function sortTemplates(a: Template, b: Template) { if (a.Position < b.Position) { return -1; } diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index d120c3a0c..5238ad114 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -10,6 +10,7 @@ import { SelectionManager } from '../../util/SelectionManager'; import { ContextMenu } from '../ContextMenu'; import { FieldViewProps } from '../nodes/FieldView'; import './CollectionBaseView.scss'; +import { DocumentManager } from '../../util/DocumentManager'; export enum CollectionViewType { Invalid, @@ -129,7 +130,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { @action.bound removeDocument(doc: Doc): boolean { - SelectionManager.DeselectAll(); + let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView) + docView && SelectionManager.DeselectDoc(docView); const props = this.props; //TODO This won't create the field if it doesn't already exist const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []); @@ -163,7 +165,6 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { return true; } if (this.removeDocument(doc)) { - SelectionManager.DeselectAll(); return addDocument(doc); } return false; diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index fe9e12640..d6b1aa29d 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -166,6 +166,25 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) { e.stopPropagation(); e.preventDefault(); + if (html && FormattedTextBox.IsFragment(html)) { + let href = FormattedTextBox.GetHref(html); + if (href) { + let docid = FormattedTextBox.GetDocFromUrl(href); + if (docid) { // prosemirror text containing link to dash document + DocServer.GetRefField(docid).then(f => { + if (f instanceof Doc) { + if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView + (f instanceof Doc) && this.props.addDocument(f, false); + } + }); + } else { + this.props.addDocument && this.props.addDocument(Docs.WebDocument(href, options)); + } + } else if (text) { + this.props.addDocument && this.props.addDocument(Docs.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }), false); + } + return; + } if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) { let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text }); this.props.addDocument(htmlDoc, false); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 8dc10bcd1..8a6764c58 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -20,6 +20,7 @@ import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); import { Transform } from '../../util/Transform'; +import { SelectionManager } from '../../util/SelectionManager'; export interface TreeViewProps { @@ -32,6 +33,7 @@ export interface TreeViewProps { ScreenToLocalTransform: () => Transform; treeViewId: string; parentKey: string; + active: () => boolean; } export enum BulletType { @@ -69,7 +71,7 @@ class TreeView extends React.Component<TreeViewProps> { @action onMouseLeave = () => { this._isOver = false; } onPointerEnter = (e: React.PointerEvent): void => { - this.props.document.libraryBrush = true; + this.props.active() && (this.props.document.libraryBrush = true); if (e.buttons === 1) { this._header!.current!.className = "treeViewItem-header"; document.addEventListener("pointermove", this.onDragMove, true); @@ -149,7 +151,10 @@ class TreeView extends React.Component<TreeViewProps> { </div>); return ( <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave} - style={{ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }} + style={{ + background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0", + pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none" + }} > {editableView(StrCast(this.props.document.title))} {openRight} @@ -225,18 +230,19 @@ class TreeView extends React.Component<TreeViewProps> { while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1); } keys.map(key => { - let docList = DocListCast(this.props.document[key]); + let docList = Cast(this.props.document[key], listSpec(Doc)); let remDoc = (doc: Doc) => this.remove(doc, key); let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.document, key, doc, addBefore, before); let doc = Cast(this.props.document[key], Doc); - if (doc instanceof Doc || docList.length) { + if (doc instanceof Doc || docList) { if (!this._collapsed) { bulletType = BulletType.Collapsible; contentElement.push(<ul key={key + "more"}> {(key === "data") ? (null) : <span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>} <div style={{ display: "block" }}> - {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, this.props.treeViewId, key, addDoc, remDoc, this.move, this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform)} + {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, key, addDoc, remDoc, this.move, + this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active)} </div> </ul >); } else { @@ -266,11 +272,12 @@ class TreeView extends React.Component<TreeViewProps> { move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => void, - screenToLocalXf: () => Transform + screenToLocalXf: () => Transform, + active: () => boolean ) { return docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized)).map(child => <TreeView document={child} treeViewId={treeViewId} key={child[Id]} deleteDoc={remove} addDocument={add} moveDocument={move} - dropAction={dropAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} parentKey={key} />); + dropAction={dropAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} parentKey={key} active={active} />); } } @@ -309,7 +316,8 @@ export class CollectionTreeView extends CollectionSubView(Document) { } let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before); let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc); - let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove, moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform); + let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove, + moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active); return ( <div id="body" className="collectionTreeView-dropTarget" diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx index ca7c99f28..ba7e6cf9e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx @@ -44,8 +44,9 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / NumCast(b.zoomBasis, 1) / 2); let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / NumCast(b.zoomBasis, 1) / 2); let text = ""; - this.props.LinkDocs.map(l => text += StrCast(l.title) + "(" + StrCast(l.linkDescription) + "), "); - text = text.substr(0, text.length - 2); + let first = this.props.LinkDocs[0]; + if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : ""); + else text = "-multiple-"; return ( <> <line key="linkLine" className="collectionfreeformlinkview-linkLine" diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index e10ba9d7e..5ac2e1f9c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -57,6 +57,7 @@ } >.jsx-parser { + position:absolute; z-index:0; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index f2960541e..0fa6ebc1e 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -6,7 +6,6 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField"; import { createSchema, makeInterface } from "../../../../new_fields/Schema"; import { BoolCast, Cast, FieldValue, NumCast } from "../../../../new_fields/Types"; import { emptyFunction, returnOne } from "../../../../Utils"; -import { DocServer } from "../../../DocServer"; import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; import { HistoryUtil } from "../../../util/History"; @@ -220,6 +219,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action setPan(panX: number, panY: number) { + this.props.Document.panTransformType = "None"; var scale = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index c699b3437..5f2a732b9 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -45,12 +45,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps> _commandExecuted = false; @action - cleanupInteractions = (all: boolean = false) => { + cleanupInteractions = (all: boolean = false, rem_keydown: boolean = true) => { if (all) { document.removeEventListener("pointerup", this.onPointerUp, true); document.removeEventListener("pointermove", this.onPointerMove, true); } - document.removeEventListener("keydown", this.marqueeCommand, true); + if (rem_keydown) { + document.removeEventListener("keydown", this.marqueeCommand, true); + } this._visible = false; } @@ -93,7 +95,8 @@ export class MarqueeView extends React.Component<MarqueeViewProps> } }); } else if (!e.ctrlKey) { - let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" }); + let newBox = Docs.TextDocument({ width: 200, height: 30, x: x, y: y, title: "-typed text-" }); + newBox.proto!.autoHeight = true; this.props.addLiveTextDocument(newBox); } e.stopPropagation(); @@ -145,6 +148,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> this._downY = this._lastY = e.pageY; this._commandExecuted = false; PreviewCursor.Visible = false; + this.cleanupInteractions(true); if (e.button === 2 || (e.button === 0 && e.altKey)) { if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]); document.addEventListener("pointermove", this.onPointerMove, true); @@ -180,14 +184,21 @@ export class MarqueeView extends React.Component<MarqueeViewProps> @action onPointerUp = (e: PointerEvent): void => { + console.log("pointer up!"); if (this._visible) { + console.log("visible"); let mselect = this.marqueeSelect(); if (!e.shiftKey) { SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); } this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); + mselect.length ? this.cleanupInteractions(true, false) : this.cleanupInteractions(true); } - this.cleanupInteractions(true); + else { + console.log("invisible"); + this.cleanupInteractions(true); + } + if (e.altKey) { e.preventDefault(); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 2ac6d12bf..6fe01963a 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -125,10 +125,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu constructor(props: DocumentViewProps) { super(props); - this.selectOnLoad = props.selectOnLoad; } - _reactionDisposer?: IReactionDisposer; @action componentDidMount() { @@ -271,7 +269,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (!linkedFwdDocs.some(l => l instanceof Promise)) { let maxLocation = StrCast(linkedFwdDocs[altKey ? 1 : 0].maximizeLocation, "inTab"); - DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], linkedFwdContextDocs[altKey ? 1 : 0]); + let targetContext = !Doc.AreProtosEqual(linkedFwdContextDocs[altKey ? 1 : 0], this.props.ContainingCollectionView && this.props.ContainingCollectionView.props.Document) ? linkedFwdContextDocs[altKey ? 1 : 0] : undefined; + DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], ctrlKey, document => this.props.addDocTab(document, maxLocation), linkedFwdPage[altKey ? 1 : 0], targetContext); } } } @@ -317,13 +316,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu makeBtnClicked = (): void => { let doc = Doc.GetProto(this.props.Document); doc.isButton = !BoolCast(doc.isButton, false); - if (StrCast(doc.layout).indexOf("Formatted") !== -1) { // only need to freeze the dimensions of text boxes since they don't have a native width and height naturally - if (doc.isButton && !doc.nativeWidth) { + if (doc.isButton) { + if (!doc.nativeWidth) { doc.nativeWidth = this.props.Document[WidthSym](); doc.nativeHeight = this.props.Document[HeightSym](); - } else { - doc.nativeWidth = doc.nativeHeight = undefined; } + } else { + doc.nativeWidth = doc.nativeHeight = undefined; } } fullScreenClicked = (): void => { @@ -349,9 +348,9 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu dst.nativeHeight = src.nativeHeight; } else { - 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); - DocUtils.MakeLink(sourceDoc, destDoc, views.length ? views[0].props.Document : undefined); + // 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); + DocUtils.MakeLink(sourceDoc, destDoc, this.props.ContainingCollectionView ? this.props.ContainingCollectionView.props.Document : undefined); de.data.droppedDocuments.push(destDoc); } } @@ -442,14 +441,13 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu onPointerLeave = (e: React.PointerEvent): void => { this.props.Document.libraryBrush = false; }; isSelected = () => SelectionManager.IsSelected(this); - @action select = (ctrlPressed: boolean) => { this.selectOnLoad = false; SelectionManager.SelectDoc(this, ctrlPressed); } + @action select = (ctrlPressed: boolean) => { SelectionManager.SelectDoc(this, ctrlPressed); } - @observable selectOnLoad: boolean = false; @computed get nativeWidth() { return this.Document.nativeWidth || 0; } @computed get nativeHeight() { return this.Document.nativeHeight || 0; } @computed get contents() { return ( - <DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} selectOnLoad={this.selectOnLoad} layoutKey={"layout"} />); + <DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} selectOnLoad={this.props.selectOnLoad} layoutKey={"layout"} />); } render() { diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 559a9fbfc..e5a43c60a 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -5,49 +5,37 @@ import { observer } from "mobx-react"; import { baseKeymap } from "prosemirror-commands"; import { history } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; +import { NodeType } from 'prosemirror-model'; import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; import { Doc, Opt } from "../../../new_fields/Doc"; +import { Id } from '../../../new_fields/FieldSymbols'; import { RichTextField } from "../../../new_fields/RichTextField"; import { createSchema, makeInterface } from "../../../new_fields/Schema"; -import { Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; import { DocServer } from "../../DocServer"; -import { DocUtils, Docs } from '../../documents/Documents'; -import { DocumentManager } from "../../util/DocumentManager"; +import { Docs } from '../../documents/Documents'; import { DragManager } from "../../util/DragManager"; -import buildKeymap from "../../util/ProsemirrorKeymap"; +import buildKeymap from "../../util/ProsemirrorExampleTransfer"; import { inpRules } from "../../util/RichTextRules"; -import { ImageResizeView, schema } from "../../util/RichTextSchema"; +import { ImageResizeView, schema, SummarizedView } from "../../util/RichTextSchema"; import { SelectionManager } from "../../util/SelectionManager"; import { TooltipLinkingMenu } from "../../util/TooltipLinkingMenu"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { undoBatch, UndoManager } from "../../util/UndoManager"; +import { ContextMenu } from "../../views/ContextMenu"; +import { ContextMenuProps } from '../ContextMenuItem'; import { DocComponent } from "../DocComponent"; import { InkingControl } from "../InkingControl"; import { FieldView, FieldViewProps } from "./FieldView"; import "./FormattedTextBox.scss"; import React = require("react"); -import { Id } from '../../../new_fields/FieldSymbols'; library.add(faEdit); library.add(faSmile); // FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document // -// HTML Markup: <FormattedTextBox Doc={Document's ID} FieldKey={Key's name} -// -// In Code, the node's HTML is specified in the document's parameterized structure as: -// document.SetField(KeyStore.Layout, "<FormattedTextBox doc={doc} fieldKey={<KEYNAME>Key} />"); -// and the node's binding to the specified document KEYNAME as: -// document.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.<KEYNAME>])); -// The Jsx parser at run time will bind: -// 'fieldKey' property to the Key stored in LayoutKeys -// and 'doc' property to the document that is being rendered -// -// When rendered() by React, this extracts the TextController from the Document stored at the -// specified Key and assigns it to an HTML input node. When changes are made to this node, -// this will edit the document and assign the new value to that field. -//] export interface FormattedTextBoxProps { isOverlay?: boolean; @@ -69,16 +57,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe return FieldView.LayoutString(FormattedTextBox, fieldStr); } private _ref: React.RefObject<HTMLDivElement>; - private _proseRef: React.RefObject<HTMLDivElement>; + private _proseRef?: HTMLDivElement; private _editorView: Opt<EditorView>; private _toolTipTextMenu: TooltipTextMenu | undefined = undefined; - private _lastState: any = undefined; private _applyingChange: boolean = false; - private _dropDisposer?: DragManager.DragDropDisposer; private _linkClicked = ""; private _reactionDisposer: Opt<IReactionDisposer>; - private _inputReactionDisposer: Opt<IReactionDisposer>; private _proxyReactionDisposer: Opt<IReactionDisposer>; + private dropDisposer?: DragManager.DragDropDisposer; public get CurrentDiv(): HTMLDivElement { return this._ref.current!; } @observable _entered = false; @@ -110,16 +96,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe super(props); this._ref = React.createRef(); - this._proseRef = React.createRef(); if (this.props.isOverlay) { DragManager.StartDragFunctions.push(() => FormattedTextBox.InputBoxOverlay = undefined); } } - dispatchTransaction = (tx: Transaction) => { if (this._editorView) { - const state = this._lastState = this._editorView.state.apply(tx); + const state = this._editorView.state.apply(tx); this._editorView.updateState(state); this._applyingChange = true; Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new RichTextField(JSON.stringify(state.toJSON()))); @@ -135,25 +119,30 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } } + protected createDropTarget = (ele: HTMLDivElement) => { + this._proseRef = ele; + if (this.dropDisposer) { + this.dropDisposer(); + } + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); + } + } + @undoBatch @action drop = async (e: Event, de: DragManager.DropEvent) => { - if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc = de.data.linkSourceDocument; - let destDoc = this.props.Document; - - DocUtils.MakeLink(sourceDoc, destDoc); - de.data.droppedDocuments.push(destDoc); + // We're dealing with a link to a document + if (de.data instanceof DragManager.EmbedDragData && de.data.urlField) { + // We're dealing with an internal document drop + let url = de.data.urlField.url.href; + let model: NodeType = (url.includes(".mov") || url.includes(".mp4")) ? schema.nodes.video : schema.nodes.image; + this._editorView!.dispatch(this._editorView!.state.tr.insert(0, model.create({ src: url }))); e.stopPropagation(); } } componentDidMount() { - if (this._ref.current) { - this._dropDisposer = DragManager.MakeDropTarget(this._ref.current, { - handlers: { drop: this.drop.bind(this) } - }); - } const config = { schema, inpRules, //these currently don't do anything, but could eventually be helpful @@ -175,16 +164,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe ] }; - if (this.props.isOverlay) { - this._inputReactionDisposer = reaction(() => FormattedTextBox.InputBoxOverlay, - () => { - if (this._editorView) { - this._editorView.destroy(); - } - this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox - } - ); - } else { + if (!this.props.isOverlay) { this._proxyReactionDisposer = reaction(() => this.props.isSelected(), () => { if (this.props.isSelected()) { @@ -194,42 +174,42 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe }); } - this._reactionDisposer = reaction( () => { const field = this.props.Document ? Cast(this.props.Document[this.props.fieldKey], RichTextField) : undefined; - return field ? field.Data : undefined; + return field ? field.Data : `{"doc":{"type":"doc","content":[]},"selection":{"type":"text","anchor":0,"head":0}}`; }, - field => field && this._editorView && !this._applyingChange && + field => this._editorView && !this._applyingChange && this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field))) ); - this.setupEditor(config, this.props.Document); + this.setupEditor(config, this.props.Document, this.props.fieldKey); } - private setupEditor(config: any, doc?: Doc) { - let field = doc ? Cast(doc[this.props.fieldKey], RichTextField) : undefined; - let startup = StrCast(this.props.Document.documentText); + private setupEditor(config: any, doc: Doc, fieldKey: string) { + let field = doc ? Cast(doc[fieldKey], RichTextField) : undefined; + let startup = StrCast(doc.documentText); startup = startup.startsWith("@@@") ? startup.replace("@@@", "") : ""; if (!startup && !field && doc) { - startup = StrCast(doc[this.props.fieldKey]); + startup = StrCast(doc[fieldKey]); } - if (this._proseRef.current) { - this._editorView = new EditorView(this._proseRef.current, { + if (this._proseRef) { + this._editorView = new EditorView(this._proseRef, { state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config), dispatchTransaction: this.dispatchTransaction, nodeViews: { - image(node, view, getPos) { return new ImageResizeView(node, view, getPos); } + image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }, + star(node, view, getPos) { return new SummarizedView(node, view, getPos); } } }); if (startup) { - this.props.Document.proto!.documentText = undefined; + Doc.GetProto(doc).documentText = undefined; this._editorView.dispatch(this._editorView.state.tr.insertText(startup)); } } if (this.props.selectOnLoad) { - this.props.select(false); - this._editorView!.focus(); + if (!this.props.isOverlay) this.props.select(false); + else this._editorView!.focus(); } } @@ -240,15 +220,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._reactionDisposer) { this._reactionDisposer(); } - if (this._inputReactionDisposer) { - this._inputReactionDisposer(); - } if (this._proxyReactionDisposer) { this._proxyReactionDisposer(); } - if (this._dropDisposer) { - this._dropDisposer(); - } } onPointerDown = (e: React.PointerEvent): void => { @@ -258,7 +232,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe this._toolTipTextMenu.tooltip.style.opacity = "0"; } } - this._linkClicked = ""; + 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; for (let parent = (e.target as any).parentNode; !href && parent; parent = parent.parentNode) { @@ -275,6 +249,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe e.stopPropagation(); e.preventDefault(); } + } if (e.button === 2 || (e.button === 0 && e.ctrlKey)) { e.preventDefault(); @@ -284,14 +259,6 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (this._toolTipTextMenu && this._toolTipTextMenu.tooltip) { this._toolTipTextMenu.tooltip.style.opacity = "1"; } - let ctrlKey = e.ctrlKey; - if (this._linkClicked) { - DocServer.GetRefField(this._linkClicked).then(f => { - (f instanceof Doc) && DocumentManager.Instance.jumpToDocument(f, ctrlKey, document => this.props.addDocTab(document, "inTab")); - }); - e.stopPropagation(); - e.preventDefault(); - } if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { e.stopPropagation(); } @@ -314,7 +281,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe } onClick = (e: React.MouseEvent): void => { - this._proseRef.current!.focus(); + this._proseRef!.focus(); if (this._linkClicked) { e.preventDefault(); e.stopPropagation(); @@ -369,6 +336,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe if (!this._undoTyping) { this._undoTyping = UndoManager.StartBatch("undoTyping"); } + if (this.props.isOverlay && this.props.Document.autoHeight) { + let xf = this._ref.current!.getBoundingClientRect(); + let scrBounds = this.props.ScreenToLocalTransform().transformBounds(0, 0, xf.width, xf.height); + let nh = NumCast(this.props.Document.nativeHeight, 0); + let dh = NumCast(this.props.Document.height, 0); + let sh = scrBounds.height; + this.props.Document.height = nh ? dh / nh * sh : sh; + this.props.Document.proto!.nativeHeight = nh ? sh : undefined; + } } @action @@ -379,6 +355,15 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerLeave = (e: React.PointerEvent) => { this._entered = false; } + + specificContextMenu = (e: React.MouseEvent): void => { + let subitems: ContextMenuProps[] = []; + subitems.push({ + description: BoolCast(this.props.Document.autoHeight, false) ? "Manual Height" : "Auto Height", + event: action(() => Doc.GetProto(this.props.Document).autoHeight = !BoolCast(this.props.Document.autoHeight, false)), icon: "expand-arrows-alt" + }); + ContextMenu.Instance.addItem({ description: "Text Funcs...", subitems: subitems }); + } render() { let style = this.props.isOverlay ? "scroll" : "hidden"; let rounded = NumCast(this.props.Document.borderRounding) < 0 ? "-rounded" : ""; @@ -392,10 +377,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe color: this.props.color ? this.props.color : this.props.hideOnLeave ? "white" : "initial", pointerEvents: interactive ? "all" : "none", }} - // onKeyDown={this.onKeyPress} - onKeyPress={this.onKeyPress} + onKeyDown={this.onKeyPress} onFocus={this.onFocused} onClick={this.onClick} + onContextMenu={this.specificContextMenu} onBlur={this.onBlur} onPointerUp={this.onPointerUp} onPointerDown={this.onPointerDown} @@ -405,7 +390,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} > - <div className={`formattedTextBox-inner${rounded}`} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} ref={this._proseRef} /> + <div className={`formattedTextBox-inner${rounded}`} ref={this.createDropTarget} style={{ whiteSpace: "pre-wrap", pointerEvents: this.props.Document.isButton && !this.props.isSelected() ? "none" : "all" }} /> </div> ); } diff --git a/src/debug/Test.tsx b/src/debug/Test.tsx index 57221aa39..0dca4b4b1 100644 --- a/src/debug/Test.tsx +++ b/src/debug/Test.tsx @@ -6,7 +6,6 @@ import { ImageField } from '../new_fields/URLField'; import { Doc } from '../new_fields/Doc'; import { List } from '../new_fields/List'; - const schema1 = createSchema({ hello: "number", test: "string", diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts index 7e02a5bc5..c2cfda079 100644 --- a/src/new_fields/Doc.ts +++ b/src/new_fields/Doc.ts @@ -186,7 +186,8 @@ export namespace Doc { } // compare whether documents or their protos match - export function AreProtosEqual(doc: Doc, other: Doc) { + export function AreProtosEqual(doc?: Doc, other?: Doc) { + if (!doc || !other) return false; let r = (doc === other); let r2 = (doc.proto === other); let r3 = (other.proto === doc); diff --git a/src/new_fields/util.ts b/src/new_fields/util.ts index fe0da2e7a..8cb1db953 100644 --- a/src/new_fields/util.ts +++ b/src/new_fields/util.ts @@ -6,7 +6,7 @@ import { FieldValue } from "./Types"; import { RefField } from "./RefField"; import { ObjectField } from "./ObjectField"; import { action } from "mobx"; -import { Parent, OnUpdate, Update, Id } from "./FieldSymbols"; +import { Parent, OnUpdate, Update, Id, SelfProxy } from "./FieldSymbols"; import { ComputedField } from "../fields/ScriptField"; export const setter = action(function (target: any, prop: string | symbol | number, value: any, receiver: any): boolean { @@ -86,7 +86,7 @@ export function deleteProperty(target: any, prop: string | number | symbol) { delete target[prop]; return true; } - target[prop] = undefined; + target[SelfProxy][prop] = undefined; return true; } diff --git a/src/server/index.ts b/src/server/index.ts index fd66c90b4..eda1ab422 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -9,7 +9,6 @@ import * as fs from 'fs'; import * as sharp from 'sharp'; const imageDataUri = require('image-data-uri'); import * as mobileDetect from 'mobile-detect'; -import { ObservableMap } from 'mobx'; import * as passport from 'passport'; import * as path from 'path'; import * as request from 'request'; diff --git a/tsconfig.json b/tsconfig.json index 0d4d77002..68f4b058a 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -5,6 +5,7 @@ "experimentalDecorators": true, "strict": true, "jsx": "react", + "allowJs": true, "sourceMap": true, "outDir": "dist", "lib": [ |