diff options
Diffstat (limited to 'src/client/util')
| -rw-r--r-- | src/client/util/DocumentManager.ts | 26 | ||||
| -rw-r--r-- | src/client/util/DragManager.ts | 183 | ||||
| -rw-r--r-- | src/client/util/RichTextSchema.tsx | 434 | ||||
| -rw-r--r-- | src/client/util/Scripting.ts | 114 | ||||
| -rw-r--r-- | src/client/util/ScrollBox.tsx | 4 | ||||
| -rw-r--r-- | src/client/util/SelectionManager.ts | 33 | ||||
| -rw-r--r-- | src/client/util/TooltipTextMenu.tsx | 260 | ||||
| -rw-r--r-- | src/client/util/Transform.ts | 56 | ||||
| -rw-r--r-- | src/client/util/TypedEvent.ts | 4 | ||||
| -rw-r--r-- | src/client/util/UndoManager.ts | 50 | ||||
| -rw-r--r-- | src/client/util/type_decls.d | 2 |
11 files changed, 610 insertions, 556 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index bf59fbb43..f38b8ca75 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -1,7 +1,7 @@ -import React = require('react') +import React = require('react'); import { observer } from 'mobx-react'; import { observable, action, computed } from 'mobx'; -import { Document } from "../../fields/Document" +import { Document } from "../../fields/Document"; import { DocumentView } from '../views/nodes/DocumentView'; import { KeyStore } from '../../fields/KeyStore'; import { FieldWaiting } from '../../fields/Field'; @@ -29,7 +29,7 @@ export class DocumentManager { public getAllDocumentViews(collection: Document) { return this.DocumentViews.filter(dv => - dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document == collection); + dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === collection); } public getDocumentView(toFind: Document): DocumentView | null { @@ -42,15 +42,15 @@ export class DocumentManager { let doc = view.props.Document; // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) { - if (Object.is(doc, toFind)) { + if (doc === toFind) { toReturn = view; return; } let docSrc = doc.GetT(KeyStore.Prototype, Document); - if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) { + if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) { toReturn = view; } - }) + }); return (toReturn); } @@ -63,15 +63,15 @@ export class DocumentManager { let doc = view.props.Document; // if (view.props.ContainingCollectionView instanceof CollectionFreeFormView) { - if (Object.is(doc, toFind)) { + if (doc === toFind) { toReturn.push(view); } else { let docSrc = doc.GetT(KeyStore.Prototype, Document); - if (docSrc && docSrc != FieldWaiting && Object.is(docSrc, toFind)) { + if (docSrc && docSrc !== FieldWaiting && Object.is(docSrc, toFind)) { toReturn.push(view); } } - }) + }); return (toReturn); } @@ -80,14 +80,14 @@ export class DocumentManager { public get LinkedDocumentViews() { return DocumentManager.Instance.DocumentViews.reduce((pairs, dv) => { let linksList = dv.props.Document.GetT(KeyStore.LinkedToDocs, ListField); - if (linksList && linksList != FieldWaiting && linksList.Data.length) { + if (linksList && linksList !== FieldWaiting && linksList.Data.length) { pairs.push(...linksList.Data.reduce((pairs, link) => { if (link instanceof Document) { let linkToDoc = link.GetT(KeyStore.LinkedToDocs, Document); - if (linkToDoc && linkToDoc != FieldWaiting) { + if (linkToDoc && linkToDoc !== FieldWaiting) { DocumentManager.Instance.getDocumentViews(linkToDoc).map(docView1 => { - pairs.push({ a: dv, b: docView1, l: link }) - }) + pairs.push({ a: dv, b: docView1, l: link }); + }); } } return pairs; diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 9ffe964ef..4849ae9f7 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,13 +1,12 @@ -import { DocumentDecorations } from "../views/DocumentDecorations"; -import { CollectionDockingView } from "../views/collections/CollectionDockingView"; -import { Document } from "../../fields/Document" import { action } from "mobx"; -import { ImageField } from "../../fields/ImageField"; -import { KeyStore } from "../../fields/KeyStore"; +import { Document } from "../../fields/Document"; +import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { CollectionView } from "../views/collections/CollectionView"; +import { DocumentDecorations } from "../views/DocumentDecorations"; import { DocumentView } from "../views/nodes/DocumentView"; +import { returnFalse } from "../../Utils"; -export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, removeFunc: (containingCollection: CollectionView) => void = () => { }) { +export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) { let onRowMove = action((e: PointerEvent): void => { e.stopPropagation(); e.preventDefault(); @@ -15,8 +14,9 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: document.removeEventListener("pointermove", onRowMove); document.removeEventListener('pointerup', onRowUp); var dragData = new DragManager.DocumentDragData([docFunc()]); - dragData.removeDocument = removeFunc; - DragManager.StartDocumentDrag([_reference.current!], dragData); + dragData.copyOnDrop = copyOnDrop; + dragData.moveDocument = moveFunc; + DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y); }); let onRowUp = action((e: PointerEvent): void => { document.removeEventListener("pointermove", onRowMove); @@ -24,17 +24,17 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: }); let onItemDown = (e: React.PointerEvent) => { // if (this.props.isSelected() || this.props.isTopMost) { - if (e.button == 0) { + if (e.button === 0) { e.stopPropagation(); if (e.shiftKey) { CollectionDockingView.Instance.StartOtherDrag([docFunc()], e); } else { document.addEventListener("pointermove", onRowMove); - document.addEventListener('pointerup', onRowUp); + document.addEventListener("pointerup", onRowUp); } } //} - } + }; return onItemDown; } @@ -50,7 +50,9 @@ export namespace DragManager { let dragDiv: HTMLDivElement; export enum DragButtons { - Left = 1, Right = 2, Both = Left | Right + Left = 1, + Right = 2, + Both = Left | Right } interface DragOptions { @@ -63,8 +65,7 @@ export namespace DragManager { (): void; } - export class DragCompleteEvent { - } + export class DragCompleteEvent { } export interface DragHandlers { dragComplete: (e: DragCompleteEvent) => void; @@ -74,21 +75,27 @@ export namespace DragManager { handlers: DropHandlers; } export class DropEvent { - constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { } + constructor( + readonly x: number, + readonly y: number, + readonly data: { [id: string]: any } + ) { } } - - export interface DropHandlers { drop: (e: Event, de: DropEvent) => void; } - - export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer { + export function MakeDropTarget( + element: HTMLElement, + options: DropOptions + ): DragDropDisposer { if ("canDrop" in element.dataset) { - throw new Error("Element is already droppable, can't make it droppable again"); + throw new Error( + "Element is already droppable, can't make it droppable again" + ); } - element.dataset["canDrop"] = "true"; + element.dataset.canDrop = "true"; const handler = (e: Event) => { const ce = e as CustomEvent<DropEvent>; options.handlers.drop(e, ce.detail); @@ -96,10 +103,11 @@ export namespace DragManager { element.addEventListener("dashOnDrop", handler); return () => { element.removeEventListener("dashOnDrop", handler); - delete element.dataset["canDrop"] + delete element.dataset.canDrop; }; } + export type MoveFunction = (document: Document, targetCollection: Document, addDocument: (document: Document) => boolean) => boolean; export class DocumentDragData { constructor(dragDoc: Document[]) { this.draggedDocuments = dragDoc; @@ -110,28 +118,33 @@ export namespace DragManager { xOffset?: number; yOffset?: number; aliasOnDrop?: boolean; - removeDocument?: (collectionDrop: CollectionView) => void; + copyOnDrop?: boolean; + moveDocument?: MoveFunction; [id: string]: any; } - export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, options?: DragOptions) { - StartDrag(eles, dragData, options, (dropData: { [id: string]: any }) => dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) : dragData.draggedDocuments); + export function StartDocumentDrag(eles: HTMLElement[], dragData: DocumentDragData, downX: number, downY: number, options?: DragOptions) { + StartDrag(eles, dragData, downX, downY, options, + (dropData: { [id: string]: any }) => (dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) : dragData.copyOnDrop ? dragData.draggedDocuments.map(d => d.Copy(true) as Document) : dragData.draggedDocuments)); } export class LinkDragData { constructor(linkSourceDoc: DocumentView) { this.linkSourceDocumentView = linkSourceDoc; } + droppedDocuments: Document[] = []; linkSourceDocumentView: DocumentView; [id: string]: any; } - export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, options?: DragOptions) { - StartDrag([ele], dragData, options); + + export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, downX: number, downY: number, options?: DragOptions) { + StartDrag([ele], dragData, downX, downY, options); } - function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { + + function StartDrag(eles: HTMLElement[], dragData: { [id: string]: any }, downX: number, downY: number, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) { if (!dragDiv) { dragDiv = document.createElement("div"); - dragDiv.className = "dragManager-dragDiv" + dragDiv.className = "dragManager-dragDiv"; DragManager.Root().appendChild(dragDiv); } @@ -140,40 +153,49 @@ export namespace DragManager { let xs: number[] = []; let ys: number[] = []; - const docs: Document[] = dragData instanceof DocumentDragData ? dragData.draggedDocuments : []; + const docs: Document[] = + dragData instanceof DocumentDragData ? dragData.draggedDocuments : []; let dragElements = eles.map(ele => { - const w = ele.offsetWidth, h = ele.offsetHeight; + const w = ele.offsetWidth, + h = ele.offsetHeight; const rect = ele.getBoundingClientRect(); - const scaleX = rect.width / w, scaleY = rect.height / h; - let x = rect.left, y = rect.top; - xs.push(x); ys.push(y); - scaleXs.push(scaleX); scaleYs.push(scaleY); + const scaleX = rect.width / w, + scaleY = rect.height / h; + let x = rect.left, + y = rect.top; + xs.push(x); + ys.push(y); + scaleXs.push(scaleX); + scaleYs.push(scaleY); let dragElement = ele.cloneNode(true) as HTMLElement; dragElement.style.opacity = "0.7"; dragElement.style.position = "absolute"; + dragElement.style.margin = "0"; + dragElement.style.top = "0"; dragElement.style.bottom = ""; - dragElement.style.left = ""; + dragElement.style.left = "0"; dragElement.style.transformOrigin = "0 0"; dragElement.style.zIndex = "1000"; dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; dragElement.style.width = `${rect.width / scaleX}px`; dragElement.style.height = `${rect.height / scaleY}px`; - // bcz: PDFs don't show up if you clone them because they contain a canvas. + // bcz: if PDFs are rendered with svg's, then this code isn't needed + // bcz: PDFs don't show up if you clone them when rendered using a canvas. // however, PDF's have a thumbnail field that contains an image of their canvas. // So we replace the pdf's canvas with the image thumbnail - if (docs.length) { - var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement; - let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField); - if (pdfBox && pdfBox.childElementCount && thumbnail) { - let img = new Image(); - img!.src = thumbnail.toString(); - img!.style.position = "absolute"; - img!.style.width = `${rect.width / scaleX}px`; - img!.style.height = `${rect.height / scaleY}px`; - pdfBox.replaceChild(img!, pdfBox.children[0]) - } - } + // if (docs.length) { + // var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement; + // let thumbnail = docs[0].GetT(KeyStore.Thumbnail, ImageField); + // if (pdfBox && pdfBox.childElementCount && thumbnail) { + // let img = new Image(); + // img.src = thumbnail.toString(); + // img.style.position = "absolute"; + // img.style.width = `${rect.width / scaleX}px`; + // img.style.height = `${rect.height / scaleY}px`; + // pdfBox.replaceChild(img, pdfBox.children[0]) + // } + // } dragDiv.appendChild(dragElement); return dragElement; @@ -187,26 +209,42 @@ export namespace DragManager { hideSource = options.hideSource(); } } - eles.map(ele => ele.hidden = hideSource); + eles.map(ele => (ele.hidden = hideSource)); + let lastX = downX; + let lastY = downY; const moveHandler = (e: PointerEvent) => { e.stopPropagation(); e.preventDefault(); - if (dragData instanceof DocumentDragData) + if (dragData instanceof DocumentDragData) { dragData.aliasOnDrop = e.ctrlKey || e.altKey; + } if (e.shiftKey) { abortDrag(); - CollectionDockingView.Instance.StartOtherDrag(docs, { pageX: e.pageX, pageY: e.pageY, preventDefault: () => { }, button: 0 }); + CollectionDockingView.Instance.StartOtherDrag(docs, { + pageX: e.pageX, + pageY: e.pageY, + preventDefault: () => { }, + button: 0 + }); } - dragElements.map((dragElement, i) => dragElement.style.transform = `translate(${xs[i] += e.movementX}px, ${ys[i] += e.movementY}px) scale(${scaleXs[i]}, ${scaleYs[i]})`); + //TODO: Why can't we use e.movementX and e.movementY? + let moveX = e.pageX - lastX; + let moveY = e.pageY - lastY; + lastX = e.pageX; + lastY = e.pageY; + dragElements.map((dragElement, i) => (dragElement.style.transform = + `translate(${(xs[i] += moveX)}px, ${(ys[i] += moveY)}px) + scale(${scaleXs[i]}, ${scaleYs[i]})`) + ); }; const abortDrag = () => { document.removeEventListener("pointermove", moveHandler, true); document.removeEventListener("pointerup", upHandler); dragElements.map(dragElement => dragDiv.removeChild(dragElement)); - eles.map(ele => ele.hidden = false); - } + eles.map(ele => (ele.hidden = false)); + }; const upHandler = (e: PointerEvent) => { abortDrag(); FinishDrag(eles, e, dragData, options, finishDrag); @@ -218,29 +256,28 @@ export namespace DragManager { function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { let removed = dragEles.map(dragEle => { let parent = dragEle.parentElement; - if (parent) - parent.removeChild(dragEle); + if (parent) parent.removeChild(dragEle); return [dragEle, parent]; }); const target = document.elementFromPoint(e.x, e.y); removed.map(r => { - let dragEle: HTMLElement = r[0]!; - let parent: HTMLElement | null = r[1]; - if (parent) - parent.appendChild(dragEle); + let dragEle = r[0]; + let parent = r[1]; + if (parent && dragEle) parent.appendChild(dragEle); }); if (target) { - if (finishDrag) - finishDrag(dragData); - - target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", { - bubbles: true, - detail: { - x: e.x, - y: e.y, - data: dragData - } - })); + if (finishDrag) finishDrag(dragData); + + target.dispatchEvent( + new CustomEvent<DropEvent>("dashOnDrop", { + bubbles: true, + detail: { + x: e.x, + y: e.y, + data: dragData + } + }) + ); if (options) { options.handlers.dragComplete({}); @@ -248,4 +285,4 @@ export namespace DragManager { } DocumentDecorations.Instance.Hidden = false; } -}
\ No newline at end of file +} diff --git a/src/client/util/RichTextSchema.tsx b/src/client/util/RichTextSchema.tsx index 489c22a57..290e26dc3 100644 --- a/src/client/util/RichTextSchema.tsx +++ b/src/client/util/RichTextSchema.tsx @@ -1,141 +1,141 @@ 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 { redo, undo } from 'prosemirror-history' -import { orderedList, bulletList, listItem, } from 'prosemirror-schema-list' +import { Schema, NodeSpec, MarkSpec, DOMOutputSpecArray, NodeType } from "prosemirror-model"; +import { joinUp, lift, setBlockType, toggleMark, wrapIn } 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 { EditorView, } from "prosemirror-view"; const pDOM: DOMOutputSpecArray = ["p", 0], blockquoteDOM: DOMOutputSpecArray = ["blockquote", 0], hrDOM: DOMOutputSpecArray = ["hr"], - preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0] + preDOM: DOMOutputSpecArray = ["pre", ["code", 0]], brDOM: DOMOutputSpecArray = ["br"], ulDOM: DOMOutputSpecArray = ["ul", 0]; // :: Object // [Specs](#model.NodeSpec) for the nodes defined in this schema. export const nodes: { [index: string]: NodeSpec } = { - // :: NodeSpec The top level document node. - doc: { - content: "block+" - }, - - // :: NodeSpec A plain paragraph textblock. Represented in the DOM - // as a `<p>` element. - paragraph: { - content: "inline*", - group: "block", - parseDOM: [{ tag: "p" }], - toDOM() { return pDOM } - }, - - // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks. - blockquote: { - content: "block+", - group: "block", - defining: true, - parseDOM: [{ tag: "blockquote" }], - toDOM() { return blockquoteDOM } - }, - - // :: NodeSpec A horizontal rule (`<hr>`). - horizontal_rule: { - group: "block", - parseDOM: [{ tag: "hr" }], - toDOM() { return hrDOM } - }, - - // :: NodeSpec A heading textblock, with a `level` attribute that - // should hold the number 1 to 6. Parsed and serialized as `<h1>` to - // `<h6>` elements. - heading: { - attrs: { level: { default: 1 } }, - content: "inline*", - group: "block", - defining: true, - parseDOM: [{ tag: "h1", attrs: { level: 1 } }, - { tag: "h2", attrs: { level: 2 } }, - { tag: "h3", attrs: { level: 3 } }, - { tag: "h4", attrs: { level: 4 } }, - { tag: "h5", attrs: { level: 5 } }, - { tag: "h6", attrs: { level: 6 } }], - toDOM(node: any) { return ["h" + node.attrs.level, 0] } - }, - - // :: NodeSpec A code listing. Disallows marks or non-text inline - // nodes by default. Represented as a `<pre>` element with a - // `<code>` element inside of it. - code_block: { - content: "text*", - marks: "", - group: "block", - code: true, - defining: true, - parseDOM: [{ tag: "pre", preserveWhitespace: "full" }], - toDOM() { return preDOM } - }, - - // :: NodeSpec The text node. - text: { - group: "inline" - }, - - // :: NodeSpec An inline image (`<img>`) node. Supports `src`, - // `alt`, and `href` attributes. The latter two default to the empty - // string. - image: { - inline: true, - attrs: { - src: {}, - alt: { default: null }, - title: { default: null } - }, - group: "inline", - draggable: true, - parseDOM: [{ - tag: "img[src]", getAttrs(dom: any) { - return { - src: dom.getAttribute("src"), - title: dom.getAttribute("title"), - alt: dom.getAttribute("alt") - } - } - }], - toDOM(node: any) { return ["img", node.attrs] } - }, - - // :: NodeSpec A hard line break, represented in the DOM as `<br>`. - hard_break: { - inline: true, - group: "inline", - selectable: false, - parseDOM: [{ tag: "br" }], - toDOM() { return brDOM } - }, - - ordered_list: { - ...orderedList, - content: 'list_item+', - group: 'block' - }, - //this doesn't currently work for some reason - bullet_list: { - ...bulletList, - content: 'list_item+', - group: 'block', - // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], - // toDOM() { return ulDOM } - }, - //bullet_list: { - // content: 'list_item+', - // group: 'block', - //active: blockActive(schema.nodes.bullet_list), - //enable: wrapInList(schema.nodes.bullet_list), - //run: wrapInList(schema.nodes.bullet_list), - //select: state => true, - // }, - list_item: { - ...listItem, - content: 'paragraph block*' - } -} + // :: NodeSpec The top level document node. + doc: { + content: "block+" + }, + + // :: NodeSpec A plain paragraph textblock. Represented in the DOM + // as a `<p>` element. + paragraph: { + content: "inline*", + group: "block", + parseDOM: [{ tag: "p" }], + toDOM() { return pDOM; } + }, + + // :: NodeSpec A blockquote (`<blockquote>`) wrapping one or more blocks. + blockquote: { + content: "block+", + group: "block", + defining: true, + parseDOM: [{ tag: "blockquote" }], + toDOM() { return blockquoteDOM; } + }, + + // :: NodeSpec A horizontal rule (`<hr>`). + horizontal_rule: { + group: "block", + parseDOM: [{ tag: "hr" }], + toDOM() { return hrDOM; } + }, + + // :: NodeSpec A heading textblock, with a `level` attribute that + // should hold the number 1 to 6. Parsed and serialized as `<h1>` to + // `<h6>` elements. + heading: { + attrs: { level: { default: 1 } }, + content: "inline*", + group: "block", + defining: true, + parseDOM: [{ tag: "h1", attrs: { level: 1 } }, + { tag: "h2", attrs: { level: 2 } }, + { tag: "h3", attrs: { level: 3 } }, + { tag: "h4", attrs: { level: 4 } }, + { tag: "h5", attrs: { level: 5 } }, + { tag: "h6", attrs: { level: 6 } }], + toDOM(node: any) { return ["h" + node.attrs.level, 0]; } + }, + + // :: NodeSpec A code listing. Disallows marks or non-text inline + // nodes by default. Represented as a `<pre>` element with a + // `<code>` element inside of it. + code_block: { + content: "text*", + marks: "", + group: "block", + code: true, + defining: true, + parseDOM: [{ tag: "pre", preserveWhitespace: "full" }], + toDOM() { return preDOM; } + }, + + // :: NodeSpec The text node. + text: { + group: "inline" + }, + + // :: NodeSpec An inline image (`<img>`) node. Supports `src`, + // `alt`, and `href` attributes. The latter two default to the empty + // string. + image: { + inline: true, + attrs: { + src: {}, + alt: { default: null }, + title: { default: null } + }, + group: "inline", + draggable: true, + parseDOM: [{ + tag: "img[src]", getAttrs(dom: any) { + return { + src: dom.getAttribute("src"), + title: dom.getAttribute("title"), + alt: dom.getAttribute("alt") + }; + } + }], + toDOM(node: any) { return ["img", node.attrs]; } + }, + + // :: NodeSpec A hard line break, represented in the DOM as `<br>`. + hard_break: { + inline: true, + group: "inline", + selectable: false, + parseDOM: [{ tag: "br" }], + toDOM() { return brDOM; } + }, + + ordered_list: { + ...orderedList, + content: 'list_item+', + group: 'block' + }, + //this doesn't currently work for some reason + bullet_list: { + ...bulletList, + content: 'list_item+', + group: 'block', + // parseDOM: [{ tag: "ul" }, { style: 'list-style-type=disc' }], + // toDOM() { return ulDOM } + }, + //bullet_list: { + // content: 'list_item+', + // group: 'block', + //active: blockActive(schema.nodes.bullet_list), + //enable: wrapInList(schema.nodes.bullet_list), + //run: wrapInList(schema.nodes.bullet_list), + //select: state => true, + // }, + list_item: { + ...listItem, + content: 'paragraph block*' + } +}; const emDOM: DOMOutputSpecArray = ["em", 0]; const strongDOM: DOMOutputSpecArray = ["strong", 0]; @@ -144,93 +144,93 @@ const underlineDOM: DOMOutputSpecArray = ["underline", 0]; // :: Object [Specs](#model.MarkSpec) for the marks in the schema. export const marks: { [index: string]: MarkSpec } = { - // :: MarkSpec A link. Has `href` and `title` attributes. `title` - // defaults to the empty string. Rendered and parsed as an `<a>` - // element. - link: { - attrs: { - href: {}, - title: { default: null } - }, - inclusive: false, - parseDOM: [{ - tag: "a[href]", getAttrs(dom: any) { - return { href: dom.getAttribute("href"), title: dom.getAttribute("title") } - } - }], - toDOM(node: any) { return ["a", node.attrs, 0] } - }, - - // :: MarkSpec An emphasis mark. Rendered as an `<em>` element. - // Has parse rules that also match `<i>` and `font-style: italic`. - em: { - parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }], - toDOM() { return emDOM } - }, - - // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules - // also match `<b>` and `font-weight: bold`. - strong: { - parseDOM: [{ tag: "strong" }, - { tag: "b" }, - { style: "font-weight" }], - toDOM() { return strongDOM } - }, - - underline: { - parseDOM: [ - { tag: 'u' }, - { style: 'text-decoration=underline' } - ], - toDOM: () => ['span', { - style: 'text-decoration:underline' - }] - }, - - strikethrough: { - parseDOM: [ - { tag: 'strike' }, - { style: 'text-decoration=line-through' }, - { style: 'text-decoration-line=line-through' } - ], - toDOM: () => ['span', { - style: 'text-decoration-line:line-through' - }] - }, - - subscript: { - excludes: 'superscript', - parseDOM: [ - { tag: 'sub' }, - { style: 'vertical-align=sub' } - ], - toDOM: () => ['sub'] - }, - - superscript: { - excludes: 'subscript', - parseDOM: [ - { tag: 'sup' }, - { style: 'vertical-align=super' } - ], - toDOM: () => ['sup'] - }, - - - // :: MarkSpec Code font mark. Represented as a `<code>` element. - code: { - parseDOM: [{ tag: "code" }], - toDOM() { return codeDOM } - }, - - - timesNewRoman: { - parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }], - toDOM: () => ['span', { - style: 'font-family: "Times New Roman", Times, serif;' - }] - }, -} + // :: MarkSpec A link. Has `href` and `title` attributes. `title` + // defaults to the empty string. Rendered and parsed as an `<a>` + // element. + link: { + attrs: { + href: {}, + title: { default: null } + }, + inclusive: false, + parseDOM: [{ + tag: "a[href]", getAttrs(dom: any) { + return { href: dom.getAttribute("href"), title: dom.getAttribute("title") }; + } + }], + toDOM(node: any) { return ["a", node.attrs, 0]; } + }, + + // :: MarkSpec An emphasis mark. Rendered as an `<em>` element. + // Has parse rules that also match `<i>` and `font-style: italic`. + em: { + parseDOM: [{ tag: "i" }, { tag: "em" }, { style: "font-style=italic" }], + toDOM() { return emDOM; } + }, + + // :: MarkSpec A strong mark. Rendered as `<strong>`, parse rules + // also match `<b>` and `font-weight: bold`. + strong: { + parseDOM: [{ tag: "strong" }, + { tag: "b" }, + { style: "font-weight" }], + toDOM() { return strongDOM; } + }, + + underline: { + parseDOM: [ + { tag: 'u' }, + { style: 'text-decoration=underline' } + ], + toDOM: () => ['span', { + style: 'text-decoration:underline' + }] + }, + + strikethrough: { + parseDOM: [ + { tag: 'strike' }, + { style: 'text-decoration=line-through' }, + { style: 'text-decoration-line=line-through' } + ], + toDOM: () => ['span', { + style: 'text-decoration-line:line-through' + }] + }, + + subscript: { + excludes: 'superscript', + parseDOM: [ + { tag: 'sub' }, + { style: 'vertical-align=sub' } + ], + toDOM: () => ['sub'] + }, + + superscript: { + excludes: 'subscript', + parseDOM: [ + { tag: 'sup' }, + { style: 'vertical-align=super' } + ], + toDOM: () => ['sup'] + }, + + + // :: MarkSpec Code font mark. Represented as a `<code>` element. + code: { + parseDOM: [{ tag: "code" }], + toDOM() { return codeDOM; } + }, + + + timesNewRoman: { + parseDOM: [{ style: 'font-family: "Times New Roman", Times, serif;' }], + toDOM: () => ['span', { + style: 'font-family: "Times New Roman", Times, serif;' + }] + }, +}; // :: Schema // This schema rougly corresponds to the document schema used by @@ -240,4 +240,4 @@ export const marks: { [index: string]: MarkSpec } = { // // 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 });
\ No newline at end of file diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 4e97b9401..9015f21cf 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -14,45 +14,63 @@ import { ListField } from "../../fields/ListField"; // import * as typescriptes5 from '!!raw-loader!../../../node_modules/typescript/lib/lib.es5.d.ts' // @ts-ignore -import * as typescriptlib from '!!raw-loader!./type_decls.d' +import * as typescriptlib from '!!raw-loader!./type_decls.d'; import { Documents } from "../documents/Documents"; import { Key } from "../../fields/Key"; +export interface ScriptSucccess { + success: true; + result: any; +} + +export interface ScriptError { + success: false; + error: any; +} + +export type ScriptResult = ScriptSucccess | ScriptError; -export interface ExecutableScript { - (): any; +export interface CompiledScript { + readonly compiled: true; + readonly originalScript: string; + readonly options: Readonly<ScriptOptions>; + run(args?: { [name: string]: any }): ScriptResult; +} - compiled: boolean; +export interface CompileError { + compiled: false; + errors: any[]; } -function Compile(script: string | undefined, diagnostics: Opt<any[]>, scope: { [name: string]: any }): ExecutableScript { - const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error)); +export type CompileResult = CompiledScript | CompileError; + +function Run(script: string | undefined, customParams: string[], diagnostics: any[], originalScript: string, options: ScriptOptions): CompileResult { + const errors = diagnostics.some(diag => diag.category === ts.DiagnosticCategory.Error); + if (errors || !script) { + return { compiled: false, errors: diagnostics }; + } - let func: () => Opt<Field>; - if (compiled && script) { - let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key]; - let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)]; - let params: any[] = [KeyStore, Documents, ...fieldTypes] - for (let prop in scope) { - if (prop === "this") { + let fieldTypes = [Document, NumberField, TextField, ImageField, RichTextField, ListField, Key]; + let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)]; + let params: any[] = [KeyStore, Documents, ...fieldTypes]; + let compiledFunction = new Function(...paramNames, `return ${script}`); + let run = (args: { [name: string]: any } = {}): ScriptResult => { + let argsArray: any[] = []; + for (let name of customParams) { + if (name === "this") { continue; } - paramNames.push(prop); - params.push(scope[prop]); + argsArray.push(args[name]); } - let thisParam = scope["this"]; - let compiledFunction = new Function(...paramNames, script); - func = function (): Opt<Field> { - return compiledFunction.apply(thisParam, params) - }; - } else { - func = () => undefined; - } - - return Object.assign(func, - { - compiled - }); + let thisParam = args.this; + try { + const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray); + return { success: true, result }; + } catch (error) { + return { success: false, error }; + } + }; + return { compiled: true, run, originalScript, options }; } interface File { @@ -74,14 +92,14 @@ class ScriptingCompilerHost { } // getDefaultLibFileName(options: ts.CompilerOptions): string { getDefaultLibFileName(options: any): string { - return 'node_modules/typescript/lib/lib.d.ts' // No idea what this means... + return 'node_modules/typescript/lib/lib.d.ts'; // No idea what this means... } writeFile(fileName: string, content: string) { const file = this.files.find(file => file.fileName === fileName); if (file) { file.content = content; } else { - this.files.push({ fileName, content }) + this.files.push({ fileName, content }); } } getCurrentDirectory(): string { @@ -108,26 +126,48 @@ class ScriptingCompilerHost { } } -export function CompileScript(script: string, scope?: { [name: string]: any }, addReturn: boolean = false): ExecutableScript { +export interface ScriptOptions { + requiredType?: string; + addReturn?: boolean; + params?: { [name: string]: string }; +} + +export function CompileScript(script: string, { requiredType = "", addReturn = false, params = {} }: ScriptOptions = {}): CompileResult { let host = new ScriptingCompilerHost; - let funcScript = `(function() { + let paramArray: string[] = []; + if ("this" in params) { + paramArray.push("this"); + } + for (const key in params) { + if (key === "this") continue; + paramArray.push(key); + } + let paramString = paramArray.map(key => { + const val = params[key]; + return `${key}: ${val}`; + }).join(", "); + let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} { ${addReturn ? `return ${script};` : script} - }).apply(this)` + })`; host.writeFile("file.ts", funcScript); host.writeFile('node_modules/typescript/lib/lib.d.ts', typescriptlib); let program = ts.createProgram(["file.ts"], {}, host); let testResult = program.emit(); - let outputText = "return " + host.readFile("file.js"); + let outputText = host.readFile("file.js"); let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics); - return Compile(outputText, diagnostics, scope || {}); + return Run(outputText, paramArray, diagnostics, script, { requiredType, addReturn, params }); +} + +export function OrLiteralType(returnType: string): string { + return `${returnType} | string | number`; } export function ToField(data: any): Opt<Field> { - if (typeof data == "string") { + if (typeof data === "string") { return new TextField(data); - } else if (typeof data == "number") { + } else if (typeof data === "number") { return new NumberField(data); } return undefined; diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx index b6b088170..a209874a3 100644 --- a/src/client/util/ScrollBox.tsx +++ b/src/client/util/ScrollBox.tsx @@ -1,4 +1,4 @@ -import React = require("react") +import React = require("react"); export class ScrollBox extends React.Component { onWheel = (e: React.WheelEvent) => { @@ -16,6 +16,6 @@ export class ScrollBox extends React.Component { }} onWheel={this.onWheel}> {this.props.children} </div> - ) + ); } }
\ No newline at end of file diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 1354e32e1..5ddaafc72 100644 --- a/src/client/util/SelectionManager.ts +++ b/src/client/util/SelectionManager.ts @@ -1,6 +1,7 @@ import { observable, action } from "mobx"; import { DocumentView } from "../views/nodes/DocumentView"; -import { Document } from "../../fields/Document" +import { Document } from "../../fields/Document"; +import { Main } from "../views/Main"; export namespace SelectionManager { class Manager { @@ -11,19 +12,26 @@ export namespace SelectionManager { SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { // if doc is not in SelectedDocuments, add it if (!ctrlPressed) { - manager.SelectedDocuments = []; + this.DeselectAll(); } if (manager.SelectedDocuments.indexOf(doc) === -1) { - manager.SelectedDocuments.push(doc) + manager.SelectedDocuments.push(doc); + doc.props.onActiveChanged(true); } } + + @action + DeselectAll(): void { + manager.SelectedDocuments.map(dv => dv.props.onActiveChanged(false)); + manager.SelectedDocuments = []; + } } - const manager = new Manager; + const manager = new Manager(); export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void { - manager.SelectDoc(doc, ctrlPressed) + manager.SelectDoc(doc, ctrlPressed); } export function IsSelected(doc: DocumentView): boolean { @@ -33,18 +41,17 @@ export namespace SelectionManager { export function DeselectAll(except?: Document): void { let found: DocumentView | undefined = undefined; if (except) { - for (let i = 0; i < manager.SelectedDocuments.length; i++) { - let view = manager.SelectedDocuments[i]; - if (view.props.Document == except) - found = view; + for (const view of manager.SelectedDocuments) { + if (view.props.Document === except) found = view; } } - manager.SelectedDocuments.length = 0; - if (found) - manager.SelectedDocuments.push(found); + + manager.DeselectAll(); + if (found) manager.SelectDoc(found, false); + Main.Instance.SetTextDoc(undefined, undefined); } export function SelectedDocuments(): Array<DocumentView> { return manager.SelectedDocuments; } -}
\ No newline at end of file +} diff --git a/src/client/util/TooltipTextMenu.tsx b/src/client/util/TooltipTextMenu.tsx index e84d48ce3..19affdf97 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -10,159 +10,131 @@ import { Schema, NodeType } from "prosemirror-model"; import React = require("react"); import "./TooltipTextMenu.scss"; const { toggleMark, setBlockType, wrapIn } = require("prosemirror-commands"); -import { library } from '@fortawesome/fontawesome-svg-core' -import { wrapInList, bulletList, liftListItem, listItem } from 'prosemirror-schema-list' -import { - faListUl, -} from '@fortawesome/free-solid-svg-icons'; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { wrapInList, bulletList, liftListItem, listItem } from 'prosemirror-schema-list'; +import { faListUl } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -const SVG = "http://www.w3.org/2000/svg" +const SVG = "http://www.w3.org/2000/svg"; //appears above a selection of text in a RichTextBox to give user options such as Bold, Italics, etc. export class TooltipTextMenu { - private tooltip: HTMLElement; - private num_icons = 0; - - constructor(view: EditorView) { - this.tooltip = document.createElement("div"); - this.tooltip.className = "tooltipMenu"; - - //add the div which is the tooltip - view.dom.parentNode!.appendChild(this.tooltip); - - //add additional icons - 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: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }, - { command: toggleMark(schema.marks.timesNewRoman), dom: this.icon("x", "TNR") }, - { command: undo, dom: this.icon("<", "lift") }, - ] - //add menu items - items.forEach(({ dom, command }) => { - this.tooltip.appendChild(dom); - }); - - //add dropdowns - - //pointer down handler to activate button effects - this.tooltip.addEventListener("pointerdown", e => { - e.preventDefault(); - view.focus(); - //update view of icons - this.num_icons = 0; - items.forEach(({ command, dom }) => { - if (e.srcElement && dom.contains(e.srcElement as Node)) { - //let active = command(view.state, view.dispatch, view); - let active = command(view.state, view.dispatch, view); - //uncomment this if we want the bullet button to disappear if current selection is bulleted - //dom.style.display = active ? "" : "none"; + private tooltip: HTMLElement; + + constructor(view: EditorView) { + this.tooltip = document.createElement("div"); + this.tooltip.className = "tooltipMenu"; + + //add the div which is the tooltip + view.dom.parentNode!.appendChild(this.tooltip); + + //add additional icons + 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") }, + //this doesn't work currently - look into notion of active block + { command: wrapInList(schema.nodes.bullet_list), dom: this.icon(":", "bullets") }, + ]; + items.forEach(({ dom }) => this.tooltip.appendChild(dom)); + + //pointer down handler to activate button effects + this.tooltip.addEventListener("pointerdown", e => { + e.preventDefault(); + view.focus(); + items.forEach(({ command, dom }) => { + if (dom.contains(e.target as Node)) { + let active = command(view.state, view.dispatch, view); + //uncomment this if we want the bullet button to disappear if current selection is bulleted + // dom.style.display = active ? "" : "none" + } + }); + }); + + this.update(view, undefined); + } + + // Helper function to create menu icons + icon(text: string, name: string) { + let span = document.createElement("span"); + span.className = "menuicon " + name; + span.title = name; + span.textContent = text; + return span; + } + + //adapted this method - use it to check if block has a tag (ie bulleting) + blockActive(type: NodeType<Schema<string, string>>, state: EditorState) { + let attrs = {}; + + if (state.selection instanceof NodeSelection) { + const sel: NodeSelection = state.selection; + let $from = sel.$from; + let to = sel.to; + let node = sel.node; + + if (node) { + return node.hasMarkup(type, attrs); + } + + return to <= $from.end() && $from.parent.hasMarkup(type, attrs); } - }) - }) - - this.update(view, undefined); - } - - // Helper function to create menu icons - icon(text: string, name: string) { - let span = document.createElement("span"); - span.className = "menuicon " + name; - span.title = name; - span.textContent = text; - return span; - } - - //adapted this method - use it to check if block has a tag (ie bulleting) - blockActive(type: NodeType<Schema<string, string>>, state: EditorState) { - let attrs = {}; - - if (state.selection instanceof NodeSelection) { - const sel: NodeSelection = state.selection; - let $from = sel.$from; - let to = sel.to; - let node = sel.node; - - if (node) { - return node.hasMarkup(type, attrs); - } - - return to <= $from.end() && $from.parent.hasMarkup(type, attrs); } - } - - //this doesn't currently work but could be used to use icons for buttons - unorderedListIcon(): HTMLSpanElement { - let span = document.createElement("span"); - //let icon = document.createElement("FontAwesomeIcon"); - //icon.className = "menuicon"; - //icon.style.color = "white"; - //span.appendChild(<i style={{ color: "white" }} icon="list-ul" size="lg" />); - let i = document.createElement("i"); - i.className = "fa falist-ul"; - span.appendChild(i); - //span.appendChild(icon); - //return liftItem.spec.icon.sty - - //let sym = document.createElementNS(SVG, "symbol") - // sym.id = name - //sym.style.color = "white"; - //width then height - //sym.setAttribute("viewBox", "0 0 " + 1024 + " " + 1024); - //let path = sym.appendChild(document.createElementNS(SVG, "path")); - //path.setAttribute("d", "M219 310v329q0 7-5 12t-12 5q-8 0-13-5l-164-164q-5-5-5-13t5-13l164-164q5-5 13-5 7 0 12 5t5 12zM1024 749v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12zM1024 530v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 310v109q0 7-5 12t-12 5h-621q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h621q7 0 12 5t5 12zM1024 91v109q0 7-5 12t-12 5h-987q-7 0-12-5t-5-12v-109q0-7 5-12t12-5h987q7 0 12 5t5 12z"); - //span.appendChild(sym); - return span; - } - - // Create an icon for a heading at the given level - heading(level: number) { - return { - command: setBlockType(schema.nodes.heading, { level }), - dom: this.icon("H" + level, "heading") + + //this doesn't currently work but could be used to use icons for buttons + unorderedListIcon(): HTMLSpanElement { + let span = document.createElement("span"); + let icon = document.createElement("FontAwesomeIcon"); + icon.className = "menuicon fa fa-smile-o"; + span.appendChild(icon); + return span; + } + + // Create an icon for a heading at the given level + heading(level: number) { + return { + command: setBlockType(schema.nodes.heading, { level }), + dom: this.icon("H" + level, "heading") + }; } - } - - //updates the tooltip menu when the selection changes - update(view: EditorView, lastState: EditorState | undefined) { - let state = view.state - // Don't do anything if the document/selection didn't change - if (lastState && lastState.doc.eq(state.doc) && - lastState.selection.eq(state.selection)) return - - // Hide the tooltip if the selection is empty - if (state.selection.empty) { - this.tooltip.style.display = "none" - return + + //updates the tooltip menu when the selection changes + update(view: EditorView, lastState: EditorState | undefined) { + let state = view.state; + // Don't do anything if the document/selection didn't change + if (lastState && lastState.doc.eq(state.doc) && + lastState.selection.eq(state.selection)) return; + + // Hide the tooltip if the selection is empty + if (state.selection.empty) { + this.tooltip.style.display = "none"; + return; + } + + // Otherwise, reposition it and update its content + this.tooltip.style.display = ""; + let { from, to } = state.selection; + let start = view.coordsAtPos(from), end = view.coordsAtPos(to); + // The box in which the tooltip is positioned, to use as base + let box = this.tooltip.offsetParent!.getBoundingClientRect(); + // Find a center-ish x position from the selection endpoints (when + // crossing lines, end may be more to the left) + let left = Math.max((start.left + end.left) / 2, start.left + 3); + this.tooltip.style.left = (left - box.left) + "px"; + let width = Math.abs(start.left - end.left) / 2; + let mid = Math.min(start.left, end.left) + width; + + //THIS WIDTH IS 15 * NUMBER OF ICONS + 15 + this.tooltip.style.width = 122 + "px"; + this.tooltip.style.bottom = (box.bottom - start.top) + "px"; } - // Otherwise, reposition it and update its content - this.tooltip.style.display = "" - let { from, to } = state.selection - let start = view.coordsAtPos(from), end = view.coordsAtPos(to) - // The box in which the tooltip is positioned, to use as base - let box = this.tooltip.offsetParent!.getBoundingClientRect() - // Find a center-ish x position from the selection endpoints (when - // crossing lines, end may be more to the left) - let left = Math.max((start.left + end.left) / 2, start.left + 3) - this.tooltip.style.left = (left - box.left) + "px" - //let width = Math.abs(start.left - end.left) / 2; - let width = 8 * 16 + 15; - let mid = Math.min(start.left, end.left) + width; - - //THIS WIDTH IS 15 * NUMBER OF ICONS + 15 - this.tooltip.style.width = width + "px"; - this.tooltip.style.bottom = (box.bottom - start.top) + "px"; - } - - destroy() { this.tooltip.remove() } + destroy() { this.tooltip.remove(); } }
\ No newline at end of file diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts index 3e1039166..e9170ec36 100644 --- a/src/client/util/Transform.ts +++ b/src/client/util/Transform.ts @@ -3,7 +3,7 @@ export class Transform { private _translateY: number = 0; private _scale: number = 1; - static get Identity(): Transform { + static Identity(): Transform { return new Transform(0, 0, 1); } @@ -17,78 +17,64 @@ export class Transform { this._scale = scale; } - translate = (x: number, y: number): Transform => { + translate = (x: number, y: number): this => { this._translateX += x; this._translateY += y; return this; } - scale = (scale: number): Transform => { + scale = (scale: number): this => { this._scale *= scale; this._translateX *= scale; this._translateY *= scale; return this; } - scaleAbout = (scale: number, x: number, y: number): Transform => { + scaleAbout = (scale: number, x: number, y: number): this => { this._translateX += x * this._scale - x * this._scale * scale; this._translateY += y * this._scale - y * this._scale * scale; this._scale *= scale; return this; } - transform = (transform: Transform): Transform => { + transform = (transform: Transform): this => { this._translateX = transform._translateX + transform._scale * this._translateX; this._translateY = transform._translateY + transform._scale * this._translateY; this._scale *= transform._scale; return this; } - preTranslate = (x: number, y: number): Transform => { + preTranslate = (x: number, y: number): this => { this._translateX += this._scale * x; this._translateY += this._scale * y; return this; } - preScale = (scale: number): Transform => { + preScale = (scale: number): this => { this._scale *= scale; return this; } - preTransform = (transform: Transform): Transform => { + preTransform = (transform: Transform): this => { this._translateX += transform._translateX * this._scale; this._translateY += transform._translateY * this._scale; this._scale *= transform._scale; return this; } - translated = (x: number, y: number): Transform => { - return this.copy().translate(x, y); - } + translated = (x: number, y: number): Transform => this.copy().translate(x, y); - preTranslated = (x: number, y: number): Transform => { - return this.copy().preTranslate(x, y); - } + preTranslated = (x: number, y: number): Transform => this.copy().preTranslate(x, y); - scaled = (scale: number): Transform => { - return this.copy().scale(scale); - } + scaled = (scale: number): Transform => this.copy().scale(scale); - scaledAbout = (scale: number, x: number, y: number): Transform => { - return this.copy().scaleAbout(scale, x, y); - } + scaledAbout = (scale: number, x: number, y: number): Transform => this.copy().scaleAbout(scale, x, y); - preScaled = (scale: number): Transform => { - return this.copy().preScale(scale); - } + preScaled = (scale: number): Transform => this.copy().preScale(scale); - transformed = (transform: Transform): Transform => { - return this.copy().transform(transform); - } + transformed = (transform: Transform): Transform => this.copy().transform(transform); - preTransformed = (transform: Transform): Transform => { - return this.copy().preTransform(transform); - } + preTransformed = (transform: Transform): Transform => this.copy().preTransform(transform); transformPoint = (x: number, y: number): [number, number] => { x *= this._scale; @@ -98,9 +84,7 @@ export class Transform { return [x, y]; } - transformDirection = (x: number, y: number): [number, number] => { - return [x * this._scale, y * this._scale]; - } + transformDirection = (x: number, y: number): [number, number] => [x * this._scale, y * this._scale]; transformBounds(x: number, y: number, width: number, height: number): { x: number, y: number, width: number, height: number } { [x, y] = this.transformPoint(x, y); @@ -108,12 +92,8 @@ export class Transform { return { x, y, width, height }; } - inverse = () => { - return new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale) - } + inverse = () => new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale); - copy = () => { - return new Transform(this._translateX, this._translateY, this._scale); - } + copy = () => new Transform(this._translateX, this._translateY, this._scale); }
\ No newline at end of file diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts index 0714a7f5c..532ba78eb 100644 --- a/src/client/util/TypedEvent.ts +++ b/src/client/util/TypedEvent.ts @@ -36,7 +36,5 @@ export class TypedEvent<T> { this.listenersOncer = []; } - pipe = (te: TypedEvent<T>): Disposable => { - return this.on((e) => te.emit(e)); - } + pipe = (te: TypedEvent<T>): Disposable => this.on((e) => te.emit(e)); }
\ No newline at end of file diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 6d1b2f1b8..27aed4bac 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -1,6 +1,7 @@ import { observable, action } from "mobx"; -import 'source-map-support/register' +import 'source-map-support/register'; import { Without } from "../../Utils"; +import { string } from "prop-types"; function getBatchName(target: any, key: string | symbol): string { let keyName = key.toString(); @@ -30,11 +31,24 @@ function propertyDecorator(target: any, key: string | symbol) { batch.end(); } } - }) + }); } - }) + }); } -export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any { + +export function undoBatch(target: any, key: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any; +export function undoBatch(fn: (...args: any[]) => any): (...args: any[]) => any; +export function undoBatch(target: any, key?: string | symbol, descriptor?: TypedPropertyDescriptor<any>): any { + if (!key) { + return function () { + let batch = UndoManager.StartBatch(""); + try { + return target.apply(undefined, arguments); + } finally { + batch.end(); + } + }; + } if (!descriptor) { propertyDecorator(target, key); return; @@ -44,11 +58,11 @@ export function undoBatch(target: any, key: string | symbol, descriptor?: TypedP descriptor.value = function (...args: any[]) { let batch = UndoManager.StartBatch(getBatchName(target, key)); try { - return oldFunction.apply(this, args) + return oldFunction.apply(this, args); } finally { batch.end(); } - } + }; return descriptor; } @@ -84,6 +98,9 @@ export namespace UndoManager { export function GetOpenBatches(): Without<Batch, 'end'>[] { return openBatches; } + export function TraceOpenBatches() { + console.log(`Open batches:\n\t${openBatches.map(batch => batch.batchName).join("\n\t")}\n`); + } export class Batch { private disposed: boolean = false; @@ -100,8 +117,8 @@ export namespace UndoManager { EndBatch(cancel); } - end = () => { this.dispose(false); } - cancel = () => { this.dispose(true); } + end = () => { this.dispose(false); }; + cancel = () => { this.dispose(true); }; } export function StartBatch(batchName: string): Batch { @@ -121,12 +138,15 @@ export namespace UndoManager { redoStack.length = 0; currentBatch = undefined; } - }) + }); export function RunInBatch(fn: () => void, batchName: string) { let batch = StartBatch(batchName); - fn(); - batch.end(); + try { + fn(); + } finally { + batch.end(); + } } export const Undo = action(() => { @@ -146,7 +166,7 @@ export namespace UndoManager { undoing = false; redoStack.push(commands); - }) + }); export const Redo = action(() => { if (redoStack.length === 0) { @@ -159,12 +179,12 @@ export namespace UndoManager { } undoing = true; - for (let i = 0; i < commands.length; i++) { - commands[i].redo(); + for (const command of commands) { + command.redo(); } undoing = false; undoStack.push(commands); - }) + }); }
\ No newline at end of file diff --git a/src/client/util/type_decls.d b/src/client/util/type_decls.d index 4f69053b1..47c3481b2 100644 --- a/src/client/util/type_decls.d +++ b/src/client/util/type_decls.d @@ -181,7 +181,7 @@ declare class Key extends Field { Copy(): Field; ToScriptString(): string; } -declare type FIELD_WAITING = "<Waiting>"; +declare type FIELD_WAITING = null; declare type Opt<T> = T | undefined; declare type FieldValue<T> = Opt<T> | FIELD_WAITING; // @ts-ignore |
