From 11134bc5ce01d0a025d311a4f83e67ff6e63ce1c Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Sat, 9 Feb 2019 19:13:24 -0500 Subject: Moved client code to client folder --- src/client/util/DragManager.ts | 131 ++++++++++++++++++++++++++++++++++++ src/client/util/Scripting.ts | 58 ++++++++++++++++ src/client/util/ScrollBox.tsx | 21 ++++++ src/client/util/SelectionManager.ts | 39 +++++++++++ src/client/util/TypedEvent.ts | 42 ++++++++++++ 5 files changed, 291 insertions(+) create mode 100644 src/client/util/DragManager.ts create mode 100644 src/client/util/Scripting.ts create mode 100644 src/client/util/ScrollBox.tsx create mode 100644 src/client/util/SelectionManager.ts create mode 100644 src/client/util/TypedEvent.ts (limited to 'src/client/util') diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts new file mode 100644 index 000000000..f4dcce7c8 --- /dev/null +++ b/src/client/util/DragManager.ts @@ -0,0 +1,131 @@ + +export namespace DragManager { + export function Root() { + const root = document.getElementById("root"); + if (!root) { + throw new Error("No root element found"); + } + return root; + } + + let dragDiv: HTMLDivElement; + + export enum DragButtons { + Left = 1, Right = 2, Both = Left | Right + } + + interface DragOptions { + handlers: DragHandlers; + + hideSource: boolean | (() => boolean); + } + + export interface DragDropDisposer { + (): void; + } + + export class DragCompleteEvent { + } + + export interface DragHandlers { + dragComplete: (e: DragCompleteEvent) => void; + } + + export interface DropOptions { + handlers: DropHandlers; + } + + export class DropEvent { + 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 { + if ("canDrop" in element.dataset) { + throw new Error("Element is already droppable, can't make it droppable again"); + } + element.dataset["canDrop"] = "true"; + const handler = (e: Event) => { + const ce = e as CustomEvent; + options.handlers.drop(e, ce.detail); + }; + element.addEventListener("dashOnDrop", handler); + return () => { + element.removeEventListener("dashOnDrop", handler); + delete element.dataset["canDrop"] + }; + } + + + let _lastPointerX: number = 0; + let _lastPointerY: number = 0; + export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options: DragOptions) { + if (!dragDiv) { + dragDiv = document.createElement("div"); + DragManager.Root().appendChild(dragDiv); + } + 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; + // const offsetX = e.x - rect.left, offsetY = e.y - rect.top; + let dragElement = ele.cloneNode(true) as HTMLElement; + dragElement.style.opacity = "0.7"; + dragElement.style.position = "absolute"; + dragElement.style.transformOrigin = "0 0"; + dragElement.style.zIndex = "1000"; + dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + dragDiv.appendChild(dragElement); + _lastPointerX = dragData["xOffset"] + rect.left; + _lastPointerY = dragData["yOffset"] + rect.top; + + let hideSource = false; + if (typeof options.hideSource === "boolean") { + hideSource = options.hideSource; + } else { + hideSource = options.hideSource(); + } + const wasHidden = ele.hidden; + if (hideSource) { + ele.hidden = true; + } + + const moveHandler = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + x += e.clientX - _lastPointerX; _lastPointerX = e.clientX; + y += e.clientY - _lastPointerY; _lastPointerY = e.clientY; + dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`; + }; + const upHandler = (e: PointerEvent) => { + document.removeEventListener("pointermove", moveHandler, true); + document.removeEventListener("pointerup", upHandler); + FinishDrag(dragElement, e, options, dragData); + if (hideSource && !wasHidden) { + ele.hidden = false; + } + }; + document.addEventListener("pointermove", moveHandler, true); + document.addEventListener("pointerup", upHandler); + } + + function FinishDrag(dragEle: HTMLElement, e: PointerEvent, options: DragOptions, dragData: { [index: string]: any }) { + dragDiv.removeChild(dragEle); + const target = document.elementFromPoint(e.x, e.y); + if (!target) { + return; + } + target.dispatchEvent(new CustomEvent("dashOnDrop", { + bubbles: true, + detail: { + x: e.x, + y: e.y, + data: dragData + } + })); + options.handlers.dragComplete({}); + } +} \ No newline at end of file diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts new file mode 100644 index 000000000..6bc5fa412 --- /dev/null +++ b/src/client/util/Scripting.ts @@ -0,0 +1,58 @@ +// import * as ts from "typescript" +let ts = (window as any).ts; +import { Opt, Field } from "../../fields/Field"; +import { Document as DocumentImport } from "../../fields/Document"; +import { NumberField as NumberFieldImport, NumberField } from "../../fields/NumberField"; +import { ImageField as ImageFieldImport } from "../../fields/ImageField"; +import { TextField as TextFieldImport, TextField } from "../../fields/TextField"; +import { RichTextField as RichTextFieldImport } from "../../fields/RichTextField"; +import { KeyStore as KeyStoreImport } from "../../fields/Key"; + +export interface ExecutableScript { + (): any; + + compiled: boolean; +} + +function ExecScript(script: string, diagnostics: Opt): ExecutableScript { + const compiled = !(diagnostics && diagnostics.some(diag => diag.category == ts.DiagnosticCategory.Error)); + + let func: () => Opt; + if (compiled) { + func = function (): Opt { + let KeyStore = KeyStoreImport; + let Document = DocumentImport; + let NumberField = NumberFieldImport; + let TextField = TextFieldImport; + let ImageField = ImageFieldImport; + let RichTextField = RichTextFieldImport; + let window = undefined; + let document = undefined; + let retVal = eval(script); + + return retVal; + }; + } else { + func = () => undefined; + } + + return Object.assign(func, + { + compiled + }); +} + +export function CompileScript(script: string): ExecutableScript { + let result = (window as any).ts.transpileModule(script, {}) + + return ExecScript(result.outputText, result.diagnostics); +} + +export function ToField(data: any): Opt { + if (typeof data == "string") { + return new TextField(data); + } else if (typeof data == "number") { + return new NumberField(data); + } + return undefined; +} \ No newline at end of file diff --git a/src/client/util/ScrollBox.tsx b/src/client/util/ScrollBox.tsx new file mode 100644 index 000000000..b6b088170 --- /dev/null +++ b/src/client/util/ScrollBox.tsx @@ -0,0 +1,21 @@ +import React = require("react") + +export class ScrollBox extends React.Component { + onWheel = (e: React.WheelEvent) => { + if (e.currentTarget.scrollHeight > e.currentTarget.clientHeight) { // If the element has a scroll bar, then we don't want the containing collection to zoom + e.stopPropagation(); + } + } + + render() { + return ( +
+ {this.props.children} +
+ ) + } +} \ No newline at end of file diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts new file mode 100644 index 000000000..0759ae110 --- /dev/null +++ b/src/client/util/SelectionManager.ts @@ -0,0 +1,39 @@ +import { CollectionFreeFormDocumentView } from "../views/nodes/CollectionFreeFormDocumentView"; +import { observable, action } from "mobx"; + +export namespace SelectionManager { + class Manager { + @observable + SelectedDocuments: Array = []; + + @action + SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void { + // if doc is not in SelectedDocuments, add it + if (!ctrlPressed) { + manager.SelectedDocuments = []; + } + + if (manager.SelectedDocuments.indexOf(doc) === -1) { + manager.SelectedDocuments.push(doc) + } + } + } + + const manager = new Manager; + + export function SelectDoc(doc: CollectionFreeFormDocumentView, ctrlPressed: boolean): void { + manager.SelectDoc(doc, ctrlPressed) + } + + export function IsSelected(doc: CollectionFreeFormDocumentView): boolean { + return manager.SelectedDocuments.indexOf(doc) !== -1; + } + + export function DeselectAll(): void { + manager.SelectedDocuments = [] + } + + export function SelectedDocuments(): Array { + return manager.SelectedDocuments; + } +} \ No newline at end of file diff --git a/src/client/util/TypedEvent.ts b/src/client/util/TypedEvent.ts new file mode 100644 index 000000000..0714a7f5c --- /dev/null +++ b/src/client/util/TypedEvent.ts @@ -0,0 +1,42 @@ +export interface Listener { + (event: T): any; +} + +export interface Disposable { + dispose(): void; +} + +/** passes through events as they happen. You will not get events from before you start listening */ +export class TypedEvent { + private listeners: Listener[] = []; + private listenersOncer: Listener[] = []; + + on = (listener: Listener): Disposable => { + this.listeners.push(listener); + return { + dispose: () => this.off(listener) + }; + } + + once = (listener: Listener): void => { + this.listenersOncer.push(listener); + } + + off = (listener: Listener) => { + var callbackIndex = this.listeners.indexOf(listener); + if (callbackIndex > -1) this.listeners.splice(callbackIndex, 1); + } + + emit = (event: T) => { + /** Update any general listeners */ + this.listeners.forEach((listener) => listener(event)); + + /** Clear the `once` queue */ + this.listenersOncer.forEach((listener) => listener(event)); + this.listenersOncer = []; + } + + pipe = (te: TypedEvent): Disposable => { + return this.on((e) => te.emit(e)); + } +} \ No newline at end of file -- cgit v1.2.3-70-g09d2 From e2ca8fa7ec95768ef37914b909ee47fbca6b1251 Mon Sep 17 00:00:00 2001 From: Tyler Schicke Date: Tue, 12 Feb 2019 00:44:01 -0500 Subject: Added transforms to everything, not being used yet --- package-lock.json | 199 ++++++++++++++++++++- package.json | 1 + src/client/util/Transform.ts | 94 ++++++++++ src/client/views/Main.tsx | 5 +- .../views/collections/CollectionDockingView.tsx | 11 +- .../views/collections/CollectionFreeFormView.tsx | 24 ++- .../views/collections/CollectionSchemaView.tsx | 6 +- .../views/collections/CollectionViewBase.tsx | 11 +- .../views/nodes/CollectionFreeFormDocumentView.tsx | 7 +- src/client/views/nodes/DocumentView.tsx | 8 +- 10 files changed, 354 insertions(+), 12 deletions(-) create mode 100644 src/client/util/Transform.ts (limited to 'src/client/util') diff --git a/package-lock.json b/package-lock.json index 535c348d5..a4939f1cb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -684,6 +684,11 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, + "asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha1-5QNHYR1+aQlDIIu9r+vLwvuGbUY=" + }, "asn1": { "version": "0.2.4", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz", @@ -745,6 +750,11 @@ "resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz", "integrity": "sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c=" }, + "ast-types": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.9.6.tgz", + "integrity": "sha1-ECyenpAF0+fjgpvwxPok7oYu6bk=" + }, "async": { "version": "1.5.2", "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", @@ -883,6 +893,11 @@ } } }, + "base62": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/base62/-/base62-1.2.8.tgz", + "integrity": "sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA==" + }, "base64-js": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.0.tgz", @@ -1445,8 +1460,7 @@ "commander": { "version": "2.15.1", "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true + "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==" }, "commondir": { "version": "1.0.1", @@ -1454,6 +1468,36 @@ "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", "dev": true }, + "commoner": { + "version": "0.10.8", + "resolved": "https://registry.npmjs.org/commoner/-/commoner-0.10.8.tgz", + "integrity": "sha1-NPw2cs0kOT6LtH5wyqApOBH08sU=", + "requires": { + "commander": "^2.5.0", + "detective": "^4.3.1", + "glob": "^5.0.15", + "graceful-fs": "^4.1.2", + "iconv-lite": "^0.4.5", + "mkdirp": "^0.5.0", + "private": "^0.1.6", + "q": "^1.1.2", + "recast": "^0.11.17" + }, + "dependencies": { + "glob": { + "version": "5.0.15", + "resolved": "https://registry.npmjs.org/glob/-/glob-5.0.15.tgz", + "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", + "requires": { + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "2 || 3", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, "component-emitter": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.2.1.tgz", @@ -1594,6 +1638,11 @@ "serialize-javascript": "^1.4.0" } }, + "core-js": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-1.2.7.tgz", + "integrity": "sha1-ZSKUwUZR2yj6k70tX/KYOk8IxjY=" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -1884,6 +1933,11 @@ } } }, + "defined": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", + "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=" + }, "del": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/del/-/del-3.0.0.tgz", @@ -1969,6 +2023,15 @@ "integrity": "sha512-ZIzRpLJrOj7jjP2miAtgqIfmzbxa4ZOr5jJc601zklsfEx9oTzmmj2nVpIPRpNlRTIh8lc1kyViIY7BWSGNmKw==", "dev": true }, + "detective": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/detective/-/detective-4.7.1.tgz", + "integrity": "sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig==", + "requires": { + "acorn": "^5.2.1", + "defined": "^1.0.0" + } + }, "diff": { "version": "3.5.0", "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", @@ -2127,6 +2190,15 @@ "tapable": "^1.0.0" } }, + "envify": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/envify/-/envify-3.4.1.tgz", + "integrity": "sha1-1xIjKejfFoi6dxsSUBkXyc5cvOg=", + "requires": { + "jstransform": "^11.0.3", + "through": "~2.3.4" + } + }, "errno": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz", @@ -2196,6 +2268,11 @@ "estraverse": "^4.1.1" } }, + "esprima-fb": { + "version": "15001.1.0-dev-harmony-fb", + "resolved": "https://registry.npmjs.org/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz", + "integrity": "sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE=" + }, "esrecurse": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz", @@ -2479,6 +2556,18 @@ "websocket-driver": ">=0.5.1" } }, + "fbjs": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/fbjs/-/fbjs-0.6.1.tgz", + "integrity": "sha1-lja3cF9bqWhNRLcveDISVK/IYPc=", + "requires": { + "core-js": "^1.0.0", + "loose-envify": "^1.0.0", + "promise": "^7.0.3", + "ua-parser-js": "^0.7.9", + "whatwg-fetch": "^0.9.0" + } + }, "figgy-pudding": { "version": "3.5.1", "resolved": "https://registry.npmjs.org/figgy-pudding/-/figgy-pudding-3.5.1.tgz", @@ -3649,6 +3738,11 @@ "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "integrity": "sha1-SMptcvbGo68Aqa1K5odr44ieKwk=" }, + "immutable": { + "version": "4.0.0-rc.12", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.0.0-rc.12.tgz", + "integrity": "sha512-0M2XxkZLx/mi3t8NVwIm1g8nHoEmM9p9UBl/G9k4+hm0kBgOVdMV/B3CY5dQ8qG8qc80NN4gDV4HQv6FTJ5q7A==" + }, "import-lazy": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/import-lazy/-/import-lazy-2.1.0.tgz", @@ -4092,6 +4186,11 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, + "json-stringify-pretty-compact": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/json-stringify-pretty-compact/-/json-stringify-pretty-compact-1.2.0.tgz", + "integrity": "sha512-/11Pj1OyX814QMKO7K8l85SHPTr/KsFxHp8GE2zVa0BtJgGimDjXHfM3FhC7keQdWDea7+nXf+f1de7ATZcZkQ==" + }, "json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", @@ -4122,6 +4221,46 @@ "verror": "1.10.0" } }, + "jstransform": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/jstransform/-/jstransform-11.0.3.tgz", + "integrity": "sha1-CaeJk+CuTU70SH9hVakfYZDLQiM=", + "requires": { + "base62": "^1.1.0", + "commoner": "^0.10.1", + "esprima-fb": "^15001.1.0-dev-harmony-fb", + "object-assign": "^2.0.0", + "source-map": "^0.4.2" + }, + "dependencies": { + "object-assign": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-2.1.1.tgz", + "integrity": "sha1-Q8NuXVaf+OSBbE76i+AtJpZ8GKo=" + } + } + }, + "jsx-to-string": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/jsx-to-string/-/jsx-to-string-1.4.0.tgz", + "integrity": "sha1-Ztw013PaufQP6ZPP+ZQOXaZVtwU=", + "requires": { + "immutable": "^4.0.0-rc.9", + "json-stringify-pretty-compact": "^1.0.1", + "react": "^0.14.0" + }, + "dependencies": { + "react": { + "version": "0.14.9", + "resolved": "https://registry.npmjs.org/react/-/react-0.14.9.tgz", + "integrity": "sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE=", + "requires": { + "envify": "^3.0.0", + "fbjs": "^0.6.1" + } + } + } + }, "killable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/killable/-/killable-1.0.1.tgz", @@ -8491,6 +8630,11 @@ "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=" }, + "private": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/private/-/private-0.1.8.tgz", + "integrity": "sha512-VvivMrbvd2nKkiG38qjULzlc+4Vx4wm/whI9pQD35YrARNnhxeiRktSOhSukRLFNlzg6Br/cJPet5J/u19r/mg==" + }, "process": { "version": "0.11.10", "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", @@ -8502,6 +8646,14 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, + "promise": { + "version": "7.3.1", + "resolved": "https://registry.npmjs.org/promise/-/promise-7.3.1.tgz", + "integrity": "sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==", + "requires": { + "asap": "~2.0.3" + } + }, "promise-inflight": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", @@ -8659,6 +8811,11 @@ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" }, + "q": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz", + "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc=" + }, "qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", @@ -8879,6 +9036,29 @@ "readable-stream": "^2.0.2" } }, + "recast": { + "version": "0.11.23", + "resolved": "https://registry.npmjs.org/recast/-/recast-0.11.23.tgz", + "integrity": "sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM=", + "requires": { + "ast-types": "0.9.6", + "esprima": "~3.1.0", + "private": "~0.1.5", + "source-map": "~0.5.0" + }, + "dependencies": { + "esprima": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-3.1.3.tgz", + "integrity": "sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM=" + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=" + } + } + }, "redent": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/redent/-/redent-1.0.0.tgz", @@ -10075,6 +10255,11 @@ "native-promise-only": "^0.8.1" } }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=" + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -10263,6 +10448,11 @@ "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.3.1.tgz", "integrity": "sha512-cTmIDFW7O0IHbn1DPYjkiebHxwtCMU+eTy30ZtJNBPF9j2O1ITu5XH2YnBeVRKWHqF+3JQwWJv0Q0aUgX8W7IA==" }, + "ua-parser-js": { + "version": "0.7.19", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.19.tgz", + "integrity": "sha512-T3PVJ6uz8i0HzPxOF9SWzWAlfN/DavlpQqepn22xgve/5QecC+XMCAtmUNnY7C9StehaV6exjUCI801lOI7QlQ==" + }, "undefsafe": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.2.tgz", @@ -11238,6 +11428,11 @@ "integrity": "sha512-nqHUnMXmBzT0w570r2JpJxfiSD1IzoI+HGVdd3aZ0yNi3ngvQ4jv1dtHt5VGxfI2yj5yqImPhOK4vmIh2xMbGg==", "dev": true }, + "whatwg-fetch": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz", + "integrity": "sha1-DjaExsuZlbQ+/J3wPkw2XZX9nMA=" + }, "which": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", diff --git a/package.json b/package.json index 6697b5f47..657bb875d 100644 --- a/package.json +++ b/package.json @@ -50,6 +50,7 @@ "express": "^4.16.4", "flexlayout-react": "^0.3.3", "golden-layout": "^1.5.9", + "jsx-to-string": "^1.4.0", "mobx": "^5.9.0", "mobx-react": "^5.3.5", "mobx-react-devtools": "^6.0.3", diff --git a/src/client/util/Transform.ts b/src/client/util/Transform.ts new file mode 100644 index 000000000..7861ed308 --- /dev/null +++ b/src/client/util/Transform.ts @@ -0,0 +1,94 @@ +export class Transform { + private _translateX: number = 0; + private _translateY: number = 0; + private _scale: number = 1; + + static get Identity(): Transform { + return new Transform(0, 0, 1); + } + + constructor(x: number, y: number, scale: number) { + this._translateX = x; + this._translateY = y; + this._scale = scale; + } + + translate = (x: number, y: number): Transform => { + this._translateX += x; + this._translateY += y; + return this; + } + + translated = (x: number, y: number): Transform => { + return this.copy().translate(x, y); + } + + preTranslate = (x: number, y: number): Transform => { + this._translateX += x * this._scale; + this._translateY += y * this._scale; + return this; + } + + preTranslated = (x: number, y: number): Transform => { + return this.copy().preTranslate(x, y); + } + + scale = (scale: number): Transform => { + this._scale *= scale; + return this; + } + + scaled = (scale: number): Transform => { + return this.copy().scale(scale); + } + + preScale = (scale: number): Transform => { + this._scale *= scale; + this._translateX *= scale; + this._translateY *= scale; + return this; + } + + preScaled = (scale: number): Transform => { + return this.copy().preScale(scale); + } + + transform = (transform: Transform): Transform => { + this._translateX += transform._translateX * this._scale; + this._translateY += transform._translateY * this._scale; + this._scale *= transform._scale; + return this; + } + + transformed = (transform: Transform): Transform => { + return this.copy().transform(transform); + } + + preTransform = (transform: Transform): Transform => { + this._translateX = transform._translateX + this._translateX * transform._scale; + this._translateY = transform._translateY + this._translateY * transform._scale; + this._scale *= transform._scale; + return this; + } + + preTransformed = (transform: Transform): Transform => { + return this.copy().preTransform(transform); + } + + transformPoint = (x: number, y: number): [number, number] => { + x *= this._scale; + x += this._translateX; + y *= this._scale; + y += this._translateY; + return [x, y]; + } + + inverse = () => { + return new Transform(-this._translateX / this._scale, -this._translateY / this._scale, 1 / this._scale) + } + + copy = () => { + return new Transform(this._translateX, this._translateY, this._scale); + } + +} \ No newline at end of file diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 9a359e868..ac6f1de00 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -13,6 +13,7 @@ import "./Main.scss"; import { ContextMenu } from './ContextMenu'; import { DocumentView } from './nodes/DocumentView'; import { ImageField } from '../../fields/ImageField'; +import { Transform } from '../util/Transform'; configure({ @@ -84,7 +85,9 @@ document.addEventListener("pointerdown", action(function (e: PointerEvent) { ReactDOM.render((
- + Transform.Identity} + ContainingCollectionView={undefined} DocumentView={undefined} />
), diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 9aee9c10f..32d00d41a 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -14,6 +14,7 @@ import * as GoldenLayout from "golden-layout"; import * as ReactDOM from 'react-dom'; import { DragManager } from "../../util/DragManager"; import { CollectionViewBase, CollectionViewProps, COLLECTION_BORDER_WIDTH } from "./CollectionViewBase"; +import { Transform } from "../../util/Transform"; @observer export class CollectionDockingView extends CollectionViewBase { @@ -95,7 +96,10 @@ export class CollectionDockingView extends CollectionViewBase { const value: Document[] = Document.GetData(fieldKey, ListField, []); for (var i: number = 0; i < value.length; i++) { if (value[i].Id === component) { - return (); + return ( Transform.Identity} + ContainingCollectionView={this} DocumentView={undefined} />); } } if (component === "text") { @@ -236,7 +240,10 @@ export class CollectionDockingView extends CollectionViewBase { container.getElement().html("
"); setTimeout(function () { ReactDOM.render(( - + Transform.Identity} + ContainingCollectionView={me} DocumentView={undefined} /> ), document.getElementById(containingDiv) ); diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 9cf29d000..50f4f1892 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -13,6 +13,7 @@ import { ListField } from "../../../fields/ListField"; import { NumberField } from "../../../fields/NumberField"; import { Documents } from "../../documents/Documents"; import { FieldWaiting } from "../../../fields/Field"; +import { Transform } from "../../util/Transform"; @observer export class CollectionFreeFormView extends CollectionViewBase { @@ -172,6 +173,23 @@ export class CollectionFreeFormView extends CollectionViewBase { } } + @computed + get translate(): [number, number] { + const x = this.props.DocumentForCollection.GetNumber(KeyStore.PanX, 0); + const y = this.props.DocumentForCollection.GetNumber(KeyStore.PanY, 0); + return [x, y]; + } + + @computed + get scale(): number { + return this.props.DocumentForCollection.GetNumber(KeyStore.Scale, 1); + } + + getTransform = (): Transform => { + const [x, y] = this.translate; + return this.props.GetTransform().scaled(this.scale).translate(x, y); + } + render() { const { CollectionFieldKey: fieldKey, DocumentForCollection: Document } = this.props; const value: Document[] = Document.GetList(fieldKey, []); @@ -194,7 +212,11 @@ export class CollectionFreeFormView extends CollectionViewBase {
{value.map(doc => { - return (); + return (); })}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 2d5bd6c99..b897fd481 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -14,6 +14,7 @@ import { CompileScript, ToField } from "../../util/Scripting"; import { KeyStore as KS, Key } from "../../../fields/Key"; import { Document } from "../../../fields/Document"; import { Field } from "../../../fields/Field"; +import { Transform } from "../../util/Transform"; @observer export class CollectionSchemaView extends CollectionViewBase { @@ -104,7 +105,10 @@ export class CollectionSchemaView extends CollectionViewBase { let content; if (this.selectedIndex != -1) { content = ( - + Transform.Identity}//TODO This should probably be an actual transform + DocumentView={undefined} ContainingCollectionView={this} /> ) } else { content =
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index 09e8ec729..ff54d88d7 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -10,12 +10,14 @@ import React = require("react"); import { DocumentView } from "../nodes/DocumentView"; import { CollectionDockingView } from "./CollectionDockingView"; import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView"; +import { Transform } from "../../util/Transform"; export interface CollectionViewProps { CollectionFieldKey: Key; DocumentForCollection: Document; ContainingDocumentView: Opt; + GetTransform: () => Transform; } export const COLLECTION_BORDER_WIDTH = 2; @@ -43,15 +45,18 @@ export class CollectionViewBase extends React.Component { } @action - removeDocument = (doc: Document): void => { + removeDocument = (doc: Document): boolean => { //TODO This won't create the field if it doesn't already exist const value = this.props.DocumentForCollection.GetData(this.props.CollectionFieldKey, ListField, new Array()) - if (value.indexOf(doc) !== -1) { - value.splice(value.indexOf(doc), 1) + let index = value.indexOf(doc); + if (index !== -1) { + value.splice(index, 1) SelectionManager.DeselectAll() ContextMenu.Instance.clearItems() + return true; } + return false } } \ No newline at end of file diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx index a111a9936..0defc8f1d 100644 --- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx +++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx @@ -10,6 +10,7 @@ import { ContextMenu } from "../ContextMenu"; import "./NodeView.scss"; import React = require("react"); import { DocumentView, DocumentViewProps } from "./DocumentView"; +import { Transform } from "../../util/Transform"; @observer @@ -204,6 +205,10 @@ export class CollectionFreeFormDocumentView extends DocumentView { } } + getTransform = (): Transform => { + return this.props.GetTransform().translated(this.x, this.y); + } + render() { var freestyling = this.props.ContainingCollectionView instanceof CollectionFreeFormView; return ( @@ -217,7 +222,7 @@ export class CollectionFreeFormDocumentView extends DocumentView { onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}> - +
); } diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 730ce62f2..ce23a70a6 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -15,13 +15,19 @@ import { FormattedTextBox } from "../nodes/FormattedTextBox"; import { ImageBox } from "../nodes/ImageBox"; import "./NodeView.scss"; import React = require("react"); +import { Transform } from "../../util/Transform"; const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this? export interface DocumentViewProps { - Document: Document; DocumentView: Opt // needed only to set ContainingDocumentView on CollectionViewProps when invoked from JsxParser -- is there a better way? ContainingCollectionView: Opt; + + Document: Document; + AddDocument?: (doc: Document) => void; + RemoveDocument?: (doc: Document) => boolean; + GetTransform: () => Transform; } + @observer export class DocumentView extends React.Component { -- cgit v1.2.3-70-g09d2