diff options
Diffstat (limited to 'src/client/util')
-rw-r--r-- | src/client/util/DocumentManager.ts | 2 | ||||
-rw-r--r-- | src/client/util/DragManager.ts | 229 | ||||
-rw-r--r-- | src/client/util/Scripting.ts | 97 | ||||
-rw-r--r-- | src/client/util/SelectionManager.ts | 18 | ||||
-rw-r--r-- | src/client/util/TooltipTextMenu.tsx | 288 | ||||
-rw-r--r-- | src/client/util/Transform.ts | 14 | ||||
-rw-r--r-- | src/client/util/UndoManager.ts | 4 |
7 files changed, 374 insertions, 278 deletions
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index bf59fbb43..341959936 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -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.Id === collection.Id); } public getDocumentView(toFind: Document): DocumentView | null { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index 9ffe964ef..0ee7ed2b3 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,27 +1,36 @@ -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"; -export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, removeFunc: (containingCollection: CollectionView) => void = () => { }) { - let onRowMove = action((e: PointerEvent): void => { - e.stopPropagation(); - e.preventDefault(); - - document.removeEventListener("pointermove", onRowMove); - document.removeEventListener('pointerup', onRowUp); - var dragData = new DragManager.DocumentDragData([docFunc()]); - dragData.removeDocument = removeFunc; - DragManager.StartDocumentDrag([_reference.current!], dragData); - }); - let onRowUp = action((e: PointerEvent): void => { - document.removeEventListener("pointermove", onRowMove); - document.removeEventListener('pointerup', onRowUp); - }); +export function setupDrag( + _reference: React.RefObject<HTMLDivElement>, + docFunc: () => Document, + removeFunc: (containingCollection: CollectionView) => void = () => { }, + copyOnDrop: boolean = false +) { + let onRowMove = action( + (e: PointerEvent): void => { + e.stopPropagation(); + e.preventDefault(); + + // TODO: bcz -- this needs to have a drag threshold so that it doesn't trigger when just selecting. + document.removeEventListener("pointermove", onRowMove); + document.removeEventListener("pointerup", onRowUp); + var dragData = new DragManager.DocumentDragData([docFunc()]); + dragData.copyOnDrop = copyOnDrop; + dragData.removeDocument = removeFunc; + DragManager.StartDocumentDrag([_reference.current!], dragData, e.x, e.y); + } + ); + let onRowUp = action( + (e: PointerEvent): void => { + document.removeEventListener("pointermove", onRowMove); + document.removeEventListener("pointerup", onRowUp); + } + ); let onItemDown = (e: React.PointerEvent) => { // if (this.props.isSelected() || this.props.isTopMost) { if (e.button == 0) { @@ -30,11 +39,11 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: CollectionDockingView.Instance.StartOtherDrag([docFunc()], e); } else { document.addEventListener("pointermove", onRowMove); - document.addEventListener('pointerup', onRowUp); + document.addEventListener("pointerup", onRowUp); } } //} - } + }; return onItemDown; } @@ -50,7 +59,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 +74,7 @@ export namespace DragManager { (): void; } - export class DragCompleteEvent { - } + export class DragCompleteEvent { } export interface DragHandlers { dragComplete: (e: DragCompleteEvent) => void; @@ -74,19 +84,25 @@ 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"; const handler = (e: Event) => { @@ -96,7 +112,7 @@ export namespace DragManager { element.addEventListener("dashOnDrop", handler); return () => { element.removeEventListener("dashOnDrop", handler); - delete element.dataset["canDrop"] + delete element.dataset["canDrop"]; }; } @@ -110,28 +126,53 @@ export namespace DragManager { xOffset?: number; yOffset?: number; aliasOnDrop?: boolean; + copyOnDrop?: boolean; removeDocument?: (collectionDrop: CollectionView) => void; [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 +181,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,8 +237,10 @@ 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(); @@ -196,17 +248,29 @@ export namespace DragManager { 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]})`); + 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); @@ -215,32 +279,37 @@ export namespace DragManager { document.addEventListener("pointerup", upHandler); } - function FinishDrag(dragEles: HTMLElement[], e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) { + 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); + if (parent) 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 +317,4 @@ export namespace DragManager { } DocumentDecorations.Instance.Hidden = false; } -}
\ No newline at end of file +} diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 4e97b9401..7c0649a6a 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -18,41 +18,57 @@ 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 CompileSuccess { + compiled: true; + 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 CompiledScript = CompileSuccess | CompileError; + +function Run(script: string | undefined, customParams: string[], diagnostics: any[]): CompiledScript { + 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 = args["this"]; + try { + const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray); + return { success: true, result }; + } catch (error) { + return { success: false, error }; } - 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 - }); + return { compiled: true, run }; } interface File { @@ -108,20 +124,39 @@ 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 = {}): CompiledScript { 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 => `${key}: ${params[key]}`).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); +} + +export function OrLiteralType(returnType: string): string { + return `${returnType} | string | number`; } export function ToField(data: any): Opt<Field> { diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts index 1354e32e1..79d4ceb25 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 { @@ -15,15 +16,15 @@ export namespace SelectionManager { } if (manager.SelectedDocuments.indexOf(doc) === -1) { - manager.SelectedDocuments.push(doc) + manager.SelectedDocuments.push(doc); } } } - 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 { @@ -35,16 +36,15 @@ export namespace SelectionManager { if (except) { for (let i = 0; i < manager.SelectedDocuments.length; i++) { let view = manager.SelectedDocuments[i]; - if (view.props.Document == except) - found = view; + if (view.props.Document == except) found = view; } } manager.SelectedDocuments.length = 0; - if (found) - manager.SelectedDocuments.push(found); + if (found) manager.SelectedDocuments.push(found); + 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 219e7c94d..82e9d1bac 100644 --- a/src/client/util/TooltipTextMenu.tsx +++ b/src/client/util/TooltipTextMenu.tsx @@ -13,7 +13,7 @@ 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, + faListUl, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; @@ -22,159 +22,147 @@ 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: lift, dom: this.icon("<", "lift") }, - ] - //add menu items - items.forEach(({ dom, command }) => { - this.tooltip.appendChild(dom); - }); - - //add dropdowns - let cut = arr => arr.filter(x => x); - let config = { + 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: lift, 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"; + } + }) + }) + + 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; } - let tnr = new MenuItem({ - title: "tnr", - label: "Times New Roman", - css: "font-family: Times New Roman, Times, serif; ", - enable(state) { return canInsert(state, hr) }, - run(state, dispatch) { dispatch(state.tr.replaceSelectionWith(hr.create())) } - }) - new Dropdown(cut([schema.marks.timesNewRoman, r.insertHorizontalRule]), { label: "Insert" }) - - //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"; + + //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"; + //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") + } } - } - - //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 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"; } - // 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() } -}
\ No newline at end of file + destroy() { this.tooltip.remove() } +} diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts index 3e1039166..060c5da82 100644 --- a/src/client/util/Transform.ts +++ b/src/client/util/Transform.ts @@ -17,45 +17,45 @@ 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; diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 6d1b2f1b8..eb13ff1ee 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 { Without } from "../../Utils"; +import { string } from "prop-types"; function getBatchName(target: any, key: string | symbol): string { let keyName = key.toString(); @@ -84,6 +85,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; |