diff options
23 files changed, 585 insertions, 414 deletions
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts index 340983e11..46c27e672 100644 --- a/src/client/documents/Documents.ts +++ b/src/client/documents/Documents.ts @@ -43,6 +43,7 @@ export interface DocumentOptions { layoutKeys?: Key[]; viewType?: number; backgroundColor?: string; + copyDraggedItems?: boolean; } export namespace Documents { @@ -86,6 +87,7 @@ export namespace Documents { if (options.ink !== undefined) { doc.Set(KeyStore.Ink, new InkField(options.ink)); } if (options.layout !== undefined) { doc.SetText(KeyStore.Layout, options.layout); } if (options.layoutKeys !== undefined) { doc.Set(KeyStore.LayoutKeys, new ListField(options.layoutKeys)); } + if (options.copyDraggedItems !== undefined) { doc.SetBoolean(KeyStore.CopyDraggedItems, options.copyDraggedItems); } return doc; } diff --git a/src/client/northstar/dash-fields/HistogramField.ts b/src/client/northstar/dash-fields/HistogramField.ts index 90be70b80..48731f52d 100644 --- a/src/client/northstar/dash-fields/HistogramField.ts +++ b/src/client/northstar/dash-fields/HistogramField.ts @@ -27,7 +27,7 @@ export class HistogramField extends BasicField<HistogramOperation> { } Copy(): Field { - return new HistogramField(this.Data); + return new HistogramField(this.Data.Copy()); } ToScriptString(): string { diff --git a/src/client/northstar/operations/HistogramOperation.ts b/src/client/northstar/operations/HistogramOperation.ts index e63de1632..840520235 100644 --- a/src/client/northstar/operations/HistogramOperation.ts +++ b/src/client/northstar/operations/HistogramOperation.ts @@ -42,6 +42,10 @@ export class HistogramOperation extends BaseOperation implements IBaseFilterCons this.SchemaName = schemaName; } + Copy(): HistogramOperation { + return new HistogramOperation(this.SchemaName, this.X, this.Y, this.V, this.Normalization); + } + Equals(other: Object): boolean { throw new Error("Method not implemented."); } 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 043932de5..0ee7ed2b3 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -1,7 +1,5 @@ import { action } from "mobx"; import { Document } from "../../fields/Document"; -import { ImageField } from "../../fields/ImageField"; -import { KeyStore } from "../../fields/KeyStore"; import { CollectionDockingView } from "../views/collections/CollectionDockingView"; import { CollectionView } from "../views/collections/CollectionView"; import { DocumentDecorations } from "../views/DocumentDecorations"; @@ -10,16 +8,19 @@ import { DocumentView } from "../views/nodes/DocumentView"; export function setupDrag( _reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, - removeFunc: (containingCollection: CollectionView) => void = () => { } + 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); } @@ -125,6 +126,7 @@ export namespace DragManager { xOffset?: number; yOffset?: number; aliasOnDrop?: boolean; + copyOnDrop?: boolean; removeDocument?: (collectionDrop: CollectionView) => void; [id: string]: any; } @@ -136,15 +138,12 @@ export namespace DragManager { downY: number, options?: DragOptions ) { - StartDrag( - eles, - dragData, - downX, downY, - options, + StartDrag(eles, dragData, downX, downY, options, (dropData: { [id: string]: any }) => (dropData.droppedDocuments = dragData.aliasOnDrop ? dragData.draggedDocuments.map(d => d.CreateAlias()) - : dragData.draggedDocuments) + : dragData.copyOnDrop ? dragData.draggedDocuments.map(d => d.Copy(true) as Document) : + dragData.draggedDocuments) ); } 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 958c14491..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 { Main } from "../views/Main"; export namespace SelectionManager { class Manager { @@ -40,6 +41,7 @@ export namespace SelectionManager { } manager.SelectedDocuments.length = 0; if (found) manager.SelectedDocuments.push(found); + Main.Instance.SetTextDoc(undefined, undefined); } export function SelectedDocuments(): Array<DocumentView> { 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/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss index c5a2a50cb..42ae38c73 100644 --- a/src/client/views/InkingCanvas.scss +++ b/src/client/views/InkingCanvas.scss @@ -1,5 +1,8 @@ @import "global_variables"; +.inkingCanvas { + opacity:0.99; +} .inkingCanvas-paths-ink, .inkingCanvas-paths-markers, .inkingCanvas-noSelect, .inkingCanvas-canSelect { position: absolute; top: 0; diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 594803aab..fe7f007b0 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -4,7 +4,7 @@ html, body { width: 100%; height: 100%; - overflow: hidden; + overflow: auto; font-family: $sans-serif; margin: 0; position:absolute; @@ -157,12 +157,33 @@ button:hover { cursor: pointer; } } +#root { + overflow: visible; +} #main-div { width:100%; height:100%; position:absolute; top: 0; left:0; + overflow: scroll; +} +.mainDiv-textInput { + background:pink; + width: 200px; + height: 200px; + position:absolute; + overflow: visible; + top: 0; + left: 0; + .formattedTextBox-cont { + background:pink; + width: 100%; + height: 100%; + position:absolute; + top: 0; + left: 0; + } } #mainContent-div { width:100%; diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx index 237eb3b6e..c9467e130 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -1,57 +1,42 @@ -import { action, configure, observable, runInAction, trace, computed, reaction } from 'mobx'; +import { IconName, library } from '@fortawesome/fontawesome-svg-core'; +import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, computed, configure, observable, runInAction, trace } from 'mobx'; +import { observer } from 'mobx-react'; import "normalize.css"; import * as React from 'react'; import * as ReactDOM from 'react-dom'; +import Measure from 'react-measure'; +import * as request from 'request'; import { Document } from '../../fields/Document'; +import { Field, FieldWaiting, Opt } from '../../fields/Field'; import { KeyStore } from '../../fields/KeyStore'; -import "./Main.scss"; +import { ListField } from '../../fields/ListField'; +import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu'; +import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; import { MessageStore } from '../../server/Message'; +import { RouteStore } from '../../server/RouteStore'; +import { ServerUtils } from '../../server/ServerUtil'; import { Utils } from '../../Utils'; -import * as request from 'request' -import * as rp from 'request-promise' import { Documents } from '../documents/Documents'; +import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel'; +import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel'; +import { Gateway, Settings } from '../northstar/manager/Gateway'; +import { AggregateFunction, Catalog } from '../northstar/model/idea/idea'; +import '../northstar/model/ModelExtensions'; +import { HistogramOperation } from '../northstar/operations/HistogramOperation'; +import '../northstar/utils/Extensions'; import { Server } from '../Server'; import { setupDrag } from '../util/DragManager'; import { Transform } from '../util/Transform'; -import { UndoManager, undoBatch } from '../util/UndoManager'; -import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu'; +import { UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; import { ContextMenu } from './ContextMenu'; import { DocumentDecorations } from './DocumentDecorations'; -import { DocumentView } from './nodes/DocumentView'; -import "./Main.scss"; -import { observer } from 'mobx-react'; import { InkingControl } from './InkingControl'; -import { RouteStore } from '../../server/RouteStore'; -import { library, IconName } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faFont } from '@fortawesome/free-solid-svg-icons'; -import { faImage } from '@fortawesome/free-solid-svg-icons'; -import { faFilePdf } from '@fortawesome/free-solid-svg-icons'; -import { faObjectGroup } from '@fortawesome/free-solid-svg-icons'; -import { faTable } from '@fortawesome/free-solid-svg-icons'; -import { faGlobeAsia } from '@fortawesome/free-solid-svg-icons'; -import { faUndoAlt } from '@fortawesome/free-solid-svg-icons'; -import { faRedoAlt } from '@fortawesome/free-solid-svg-icons'; -import { faPenNib } from '@fortawesome/free-solid-svg-icons'; -import { faFilm } from '@fortawesome/free-solid-svg-icons'; -import { faMusic } from '@fortawesome/free-solid-svg-icons'; -import { faTree } from '@fortawesome/free-solid-svg-icons'; -import Measure from 'react-measure'; -import { DashUserModel } from '../../server/authentication/models/user_model'; -import { ServerUtils } from '../../server/ServerUtil'; -import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils'; -import { Field, Opt, FieldWaiting } from '../../fields/Field'; -import { ListField } from '../../fields/ListField'; -import { Gateway, Settings } from '../northstar/manager/Gateway'; -import { Catalog, Schema, Attribute, AttributeGroup, AggregateFunction } from '../northstar/model/idea/idea'; -import { ArrayUtil } from '../northstar/utils/ArrayUtil'; -import '../northstar/model/ModelExtensions' -import '../northstar/utils/Extensions' -import { HistogramOperation } from '../northstar/operations/HistogramOperation'; -import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel'; -import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel'; -import { CollectionView } from './collections/CollectionView'; +import "./Main.scss"; +import { DocumentView } from './nodes/DocumentView'; +import { FormattedTextBox } from './nodes/FormattedTextBox'; @observer export class Main extends React.Component { @@ -219,6 +204,30 @@ export class Main extends React.Component { focusDocument = (doc: Document) => { } noScaling = () => 1; + @observable _textDoc?: Document = undefined; + _textRect: any; + @action + SetTextDoc(textDoc?: Document, div?: HTMLDivElement) { + this._textDoc = undefined; + this._textDoc = textDoc; + if (div) + this._textRect = div.getBoundingClientRect(); + } + + @computed + get activeTextBox() { + if (this._textDoc) { + let x: number = this._textRect.x; + let y: number = this._textRect.y; + let w: number = this._textRect.width; + let h: number = this._textRect.height; + return <div className="mainDiv-textInput" style={{ transform: `translate(${x}px, ${y}px)`, width: `${w}px`, height: `${h}px` }} > + <FormattedTextBox fieldKey={KeyStore.Archives} doc={this._textDoc} isSelected={() => true} select={() => { }} isTopMost={true} selectOnLoad={true} bindings={undefined} /> + </ div> + } + else return (null); + } + @computed get mainContent() { return !this.mainContainer ? (null) : @@ -247,7 +256,7 @@ export class Main extends React.Component { let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" })) let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" })); let addSchemaNode = action(() => Documents.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" })); - let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas" })); + let addTreeNode = action(() => Documents.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", copyDraggedItems: true })); let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" })); let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" })); let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" })); @@ -312,24 +321,27 @@ export class Main extends React.Component { isShown={this.areWorkspacesShown} toggle={this.toggleWorkspaces} /> } return ( - <div id="main-div"> - <DocumentDecorations /> - <Measure onResize={(r: any) => runInAction(() => { - this.pwidth = r.entry.width; - this.pheight = r.entry.height; - })}> - {({ measureRef }) => - <div ref={measureRef} id="mainContent-div"> - {this.mainContent} - </div> - } - </Measure> - <ContextMenu /> - {this.nodesMenu} - {this.miscButtons} - {workspaceMenu} - <InkingControl /> - </div> + [ + <div id="main-div"> + <DocumentDecorations /> + <Measure onResize={(r: any) => runInAction(() => { + this.pwidth = r.entry.width; + this.pheight = r.entry.height; + })}> + {({ measureRef }) => + <div ref={measureRef} id="mainContent-div"> + {this.mainContent} + </div> + } + </Measure> + <ContextMenu /> + {this.nodesMenu} + {this.miscButtons} + {workspaceMenu} + <InkingControl /> + </div>, + this.activeTextBox + ] ); } diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 0ff6c3b40..b10aaba98 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -86,12 +86,10 @@ export class CollectionSchemaView extends CollectionViewBase { ) let reference = React.createRef<HTMLDivElement>(); let onItemDown = setupDrag(reference, () => props.doc, (containingCollection: CollectionView) => this.props.removeDocument(props.doc)); - let applyToDoc = (doc: Document, value: string) => { - let script = CompileScript(value, { this: doc }, true); - if (!script.compiled) { - return false; - } - let field = script(); + let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => { + const res = run({ this: doc }); + if (!res.success) return false; + const field = res.result; if (field instanceof Field) { doc.Set(props.fieldKey, field); return true; @@ -118,12 +116,22 @@ export class CollectionSchemaView extends CollectionViewBase { return field || ""; }} SetValue={(value: string) => { - return applyToDoc(props.doc, value); + let script = CompileScript(value, { addReturn: true, params: { this: "Document" } }); + if (!script.compiled) { + return false; + } + return applyToDoc(props.doc, script.run); }} OnFillDown={(value: string) => { + let script = CompileScript(value, { addReturn: true, params: { this: "Document" } }); + if (!script.compiled) { + return; + } + const run = script.run; + //TODO This should be able to be refactored to compile the script once this.props.Document.GetTAsync<ListField<Document>>(this.props.fieldKey, ListField).then((val) => { if (val) { - val.Data.forEach(doc => applyToDoc(doc, value)); + val.Data.forEach(doc => applyToDoc(doc, run)); } }) }}> diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 5a14aa54d..f2affbf55 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,64 +1,62 @@ @import "../global_variables"; -#body { + +.collectionTreeView-dropTarget { + border: 0px solid transparent; + border-radius: $border-radius; + box-sizing: border-box; + height: 100%; padding: 20px; padding-left: 10px; padding-right: 0px; background: $light-color-secondary; font-size: 13px; overflow: scroll; -} -ul { - list-style: none; - padding-left: 20px; -} + ul { + list-style: none; + padding-left: 20px; + } -li { - margin: 5px 0; -} + li { + margin: 5px 0; + } -.collection-child { - margin-top: 10px; - margin-bottom: 10px; -} + .collection-child { + margin-top: 10px; + margin-bottom: 10px; + } -.no-indent { - padding-left: 0; -} + .no-indent { + padding-left: 0; + } -.bullet { - width: 1.5em; - display: inline-block; - color: $intermediate-color; -} + .bullet { + width: 1.5em; + display: inline-block; + color: $intermediate-color; + } -.coll-title { - font-size: 24px; - margin-bottom: 20px; -} + .coll-title { + font-size: 24px; + margin-bottom: 20px; + } -.collectionTreeView-dropTarget { - border: 0px solid transparent; - border-radius: $border-radius; - box-sizing: border-box; - height: 100%; -} + .docContainer { + display: inline-table; + } -.docContainer { - display: inline-table; -} + .docContainer:hover { + .delete-button { + display: inline; + width: auto; + } + } -.docContainer:hover { .delete-button { + color: $intermediate-color; + float: right; + margin-left: 15px; + margin-top: 3px; display: inline; - width: auto; } -} - -.delete-button { - color: $intermediate-color; - float: right; - margin-left: 15px; - margin-top: 3px; - display: inline; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 70790af18..0b12f11fd 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -18,6 +18,7 @@ import React = require("react") export interface TreeViewProps { document: Document; deleteDoc: (doc: Document) => void; + copyOnDrag: boolean; } export enum BulletType { @@ -63,7 +64,7 @@ class TreeView extends React.Component<TreeViewProps> { */ renderTitle() { let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document)); + let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document), this.props.copyOnDrag); let editableView = (titleString: string) => (<EditableView display={"inline"} @@ -85,13 +86,12 @@ class TreeView extends React.Component<TreeViewProps> { render() { let bulletType = BulletType.List; let childElements: JSX.Element | undefined = undefined; - var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField); if (children && children !== FieldWaiting) { // add children for a collection if (!this._collapsed) { bulletType = BulletType.Collapsible; childElements = <ul> - {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} />)} + {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} copyOnDrag={this.props.copyOnDrag} />)} </ul> } else bulletType = BulletType.Collapsed; @@ -118,10 +118,11 @@ export class CollectionTreeView extends CollectionViewBase { } render() { - var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField); + let children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField); + let copyOnDrag = this.props.Document.GetBoolean(KeyStore.CopyDraggedItems, false); let childrenElement = !children || children === FieldWaiting ? (null) : (children.Data.map(value => - <TreeView document={value} key={value.Id} deleteDoc={this.remove} />) + <TreeView document={value} key={value.Id} deleteDoc={this.remove} copyOnDrag={copyOnDrag} />) ) return ( diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx index d8f4a0be5..5a24f8cde 100644 --- a/src/client/views/collections/CollectionViewBase.tsx +++ b/src/client/views/collections/CollectionViewBase.tsx @@ -83,13 +83,13 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps> @action protected drop(e: Event, de: DragManager.DropEvent): boolean { if (de.data instanceof DragManager.DocumentDragData) { - if (de.data.aliasOnDrop) { + if (de.data.aliasOnDrop || de.data.copyOnDrop) { [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key => de.data.draggedDocuments.map((draggedDocument: Document, i: number) => draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocuments[i].SetNumber(key, f.Data) : null))); } let added = de.data.droppedDocuments.reduce((added, d) => this.props.addDocument(d, false), true); - if (added && de.data.removeDocument && !de.data.aliasOnDrop) { + if (added && de.data.removeDocument && !de.data.aliasOnDrop && !de.data.copyOnDrop) { de.data.removeDocument(this.props.CollectionView); } e.stopPropagation(); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index d4809ac1c..1189dd4e8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed, reaction, runInAction, trace } from "mobx"; +import { computed, reaction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -11,59 +11,60 @@ import { CollectionViewProps } from "../CollectionViewBase"; import "./CollectionFreeFormLinksView.scss"; import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView"; import React = require("react"); -import v5 = require("uuid/v5"); -import { find } from "async"; @observer export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> { + HackToAvoidReactionFiringUnnecessarily?: Document = undefined; componentDidMount() { - reaction(() => { - return DocumentManager.Instance.getAllDocumentViews(this.props.Document).map(dv => dv.props.Document.GetNumber(KeyStore.X, 0)) - }, () => { - let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document); - for (let i = 0; i < views.length; i++) { - for (let j = 0; j < views.length; j++) { - let srcDoc = views[j].props.Document; - let dstDoc = views[i].props.Document; - let x1 = srcDoc.GetNumber(KeyStore.X, 0); - let x1w = srcDoc.GetNumber(KeyStore.Width, -1); - let x2 = dstDoc.GetNumber(KeyStore.X, 0); - let x2w = dstDoc.GetNumber(KeyStore.Width, -1); - if (x1w < 0 || x2w < 0 || i == j) - continue; - let dstTarg = dstDoc; - let srcTarg = srcDoc; - let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => { - let bdocs = brush ? brush.GetList(KeyStore.BrushingDocs, [] as Document[]) : []; - return (bdocs.length && ((bdocs[0] == dstTarg && bdocs[1] == srcTarg)) ? true : false) - }); - let brushAction = (field: ListField<Document>) => { - let found = findBrush(field); - if (found != -1) { - console.log("REMOVE BRUSH " + srcTarg.Title + " " + dstTarg.Title); - field.Data.splice(found, 1); - } - }; - if (Math.abs(x1 + x1w - x2) < 20) { - let linkDoc: Document = new Document(); - linkDoc.SetText(KeyStore.Title, "Histogram Brush"); - linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title); - linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField); - - brushAction = brushAction = (field: ListField<Document>) => { - if (findBrush(field) == -1) { - console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title); - (findBrush(field) == -1) && field.Data.push(linkDoc); + this.HackToAvoidReactionFiringUnnecessarily = this.props.Document + reaction(() => + DocumentManager.Instance.getAllDocumentViews(this.HackToAvoidReactionFiringUnnecessarily!). + map(dv => dv.props.Document.GetNumber(KeyStore.X, 0)), + () => { + let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document); + for (let i = 0; i < views.length; i++) { + for (let j = 0; j < views.length; j++) { + let srcDoc = views[j].props.Document; + let dstDoc = views[i].props.Document; + let x1 = srcDoc.GetNumber(KeyStore.X, 0); + let x1w = srcDoc.GetNumber(KeyStore.Width, -1); + let x2 = dstDoc.GetNumber(KeyStore.X, 0); + let x2w = dstDoc.GetNumber(KeyStore.Width, -1); + if (x1w < 0 || x2w < 0 || i == j) + continue; + let dstTarg = dstDoc; + let srcTarg = srcDoc; + let findBrush = (field: ListField<Document>) => field.Data.findIndex(brush => { + let bdocs = brush ? brush.GetList(KeyStore.BrushingDocs, [] as Document[]) : []; + return (bdocs.length && ((bdocs[0] == dstTarg && bdocs[1] == srcTarg)) ? true : false) + }); + let brushAction = (field: ListField<Document>) => { + let found = findBrush(field); + if (found != -1) { + console.log("REMOVE BRUSH " + srcTarg.Title + " " + dstTarg.Title); + field.Data.splice(found, 1); } }; - } - dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); - srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); + if (Math.abs(x1 + x1w - x2) < 20) { + let linkDoc: Document = new Document(); + linkDoc.SetText(KeyStore.Title, "Histogram Brush"); + linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title); + linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField); + brushAction = brushAction = (field: ListField<Document>) => { + if (findBrush(field) == -1) { + console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title); + (findBrush(field) == -1) && field.Data.push(linkDoc); + } + }; + } + dstTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); + srcTarg.GetOrCreateAsync(KeyStore.BrushingDocs, ListField, brushAction); + + } } - } - }) + }) } documentAnchors(view: DocumentView) { let equalViews = [view]; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 37bb78151..f99ba111c 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -21,6 +21,7 @@ import v5 = require("uuid/v5"); import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import { PreviewCursor } from "./PreviewCursor"; import { NumberField } from "../../../../fields/NumberField"; +import { Main } from "../../Main"; @observer export class CollectionFreeFormView extends CollectionViewBase { @@ -185,6 +186,7 @@ export class CollectionFreeFormView extends CollectionViewBase { @action private SetPan(panX: number, panY: number) { + Main.Instance.SetTextDoc(undefined, undefined); var x1 = this.getLocalTransform().inverse().Scale; const newPanX = Math.min((1 - 1 / x1) * this.nativeWidth, Math.max(0, panX)); const newPanY = Math.min((1 - 1 / x1) * this.nativeHeight, Math.max(0, panY)); diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 0c0efc437..e59179874 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -2,19 +2,19 @@ import { action, IReactionDisposer, reaction } from "mobx"; import { baseKeymap } from "prosemirror-commands"; import { history, redo, undo } from "prosemirror-history"; import { keymap } from "prosemirror-keymap"; -import { schema } from "../../util/RichTextSchema"; -import { EditorState, Transaction } from "prosemirror-state"; +import { EditorState, Plugin, Transaction } from "prosemirror-state"; import { EditorView } from "prosemirror-view"; -import { Opt, FieldWaiting } from "../../../fields/Field"; -import "./FormattedTextBox.scss"; -import React = require("react"); +import { FieldWaiting, Opt } from "../../../fields/Field"; +import { KeyStore } from "../../../fields/KeyStore"; import { RichTextField } from "../../../fields/RichTextField"; -import { FieldViewProps, FieldView } from "./FieldView"; -import { Plugin } from "prosemirror-state"; -import { Decoration, DecorationSet } from "prosemirror-view"; +import { inpRules } from "../../util/RichTextRules"; +import { schema } from "../../util/RichTextSchema"; import { TooltipTextMenu } from "../../util/TooltipTextMenu"; import { ContextMenu } from "../../views/ContextMenu"; -import { inpRules } from "../../util/RichTextRules"; +import { Main } from "../Main"; +import { FieldView, FieldViewProps } from "./FieldView"; +import "./FormattedTextBox.scss"; +import React = require("react"); const { buildMenuItems } = require("prosemirror-example-setup"); const { menuBar } = require("prosemirror-menu"); @@ -35,156 +35,196 @@ const { menuBar } = require("prosemirror-menu"); // this will edit the document and assign the new value to that field. //] export class FormattedTextBox extends React.Component<FieldViewProps> { - public static LayoutString(fieldStr: string = "DataKey") { - return FieldView.LayoutString(FormattedTextBox, fieldStr); - } - private _ref: React.RefObject<HTMLDivElement>; - private _editorView: Opt<EditorView>; - private _reactionDisposer: Opt<IReactionDisposer>; - - constructor(props: FieldViewProps) { - super(props); - - this._ref = React.createRef(); - this.onChange = this.onChange.bind(this); - } - - dispatchTransaction = (tx: Transaction) => { - if (this._editorView) { - const state = this._editorView.state.apply(tx); - this._editorView.updateState(state); - const { doc, fieldKey } = this.props; - doc.SetDataOnPrototype( - fieldKey, - JSON.stringify(state.toJSON()), - RichTextField - ); - // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); + public static LayoutString(fieldStr: string = "DataKey") { + return FieldView.LayoutString(FormattedTextBox, fieldStr); + } + private _ref: React.RefObject<HTMLDivElement>; + private _editorView: Opt<EditorView>; + private _reactionDisposer: Opt<IReactionDisposer>; + private _inputReactionDisposer: Opt<IReactionDisposer>; + + constructor(props: FieldViewProps) { + super(props); + + this._ref = React.createRef(); + this.onChange = this.onChange.bind(this); } - }; - - componentDidMount() { - let state: EditorState; - const config = { - schema, - inpRules, //these currently don't do anything, but could eventually be helpful - plugins: [ - history(), - keymap({ "Mod-z": undo, "Mod-y": redo }), - keymap(baseKeymap), - this.tooltipMenuPlugin() - ] + + dispatchTransaction = (tx: Transaction) => { + if (this._editorView) { + const state = this._editorView.state.apply(tx); + this._editorView.updateState(state); + this.FieldDoc.SetDataOnPrototype( + this.FieldKey, + JSON.stringify(state.toJSON()), + RichTextField + ); + // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); + } }; - let field = this.props.doc.GetT(this.props.fieldKey, RichTextField); - if (field && field != FieldWaiting && field.Data) { - state = EditorState.fromJSON(config, JSON.parse(field.Data)); - } else { - state = EditorState.create(config); + get FieldDoc() { return this.props.fieldKey === KeyStore.Archives ? Main.Instance._textDoc! : this.props.doc; } + get FieldKey() { return this.props.fieldKey === KeyStore.Archives ? KeyStore.Data : this.props.fieldKey; } + + componentDidMount() { + const config = { + schema, + inpRules, //these currently don't do anything, but could eventually be helpful + plugins: [ + history(), + keymap({ "Mod-z": undo, "Mod-y": redo }), + keymap(baseKeymap), + this.tooltipMenuPlugin() + ] + }; + + if (this.props.fieldKey === KeyStore.Archives) { + this._inputReactionDisposer = reaction(() => Main.Instance._textDoc && Main.Instance._textDoc.Id, + () => { + if (this._editorView) + this._editorView!.destroy(); + + this.setupEditor(config); + } + ) + } + + this._reactionDisposer = reaction( + () => { + const field = this.FieldDoc.GetT(this.FieldKey, RichTextField); + return field && field != FieldWaiting ? field.Data : undefined; + }, + field => { + if (field && this._editorView) { + this._editorView.updateState( + EditorState.fromJSON(config, JSON.parse(field)) + ); + } + } + ); + this.setupEditor(config); + } + + private setupEditor(config: any) { + + let state: EditorState; + let field = this.FieldDoc.GetT(this.FieldKey, RichTextField); + if (field && field != FieldWaiting && field.Data) { + state = EditorState.fromJSON(config, JSON.parse(field.Data)); + } else { + state = EditorState.create(config); + } + if (this._ref.current) { + this._editorView = new EditorView(this._ref.current, { + state, + dispatchTransaction: this.dispatchTransaction + }); + } + + if (this.props.selectOnLoad) { + this.props.select(); + this._editorView!.focus(); + } } - if (this._ref.current) { - this._editorView = new EditorView(this._ref.current, { - state, - dispatchTransaction: this.dispatchTransaction - }); + + componentWillUnmount() { + if (this._editorView) { + this._editorView.destroy(); + } + if (this._reactionDisposer) { + this._reactionDisposer(); + } + if (this._inputReactionDisposer) { + this._inputReactionDisposer(); + } + } + + shouldComponentUpdate() { + return false; + } + + @action + onChange(e: React.ChangeEvent<HTMLInputElement>) { + const { fieldKey, doc } = this.props; + doc.SetOnPrototype(fieldKey, new RichTextField(e.target.value)); + // doc.SetData(fieldKey, e.target.value, RichTextField); } + onPointerDown = (e: React.PointerEvent): void => { + if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { + e.stopPropagation(); + } + if (e.buttons === 1 && this.props.fieldKey !== KeyStore.Archives) + e.preventDefault(); + }; + onPointerUp = (e: React.PointerEvent): void => { + if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { + e.stopPropagation(); + } + if (this.props.fieldKey !== KeyStore.Archives) { + e.preventDefault(); + Main.Instance.SetTextDoc(this.props.doc, this._ref.current!); + } + }; - this._reactionDisposer = reaction( - () => { - const field = this.props.doc.GetT(this.props.fieldKey, RichTextField); - return field && field != FieldWaiting ? field.Data : undefined; - }, - field => { - if (field && this._editorView) { - this._editorView.updateState( - EditorState.fromJSON(config, JSON.parse(field)) - ); + onFocused = (e: React.FocusEvent): void => { + if (this.props.fieldKey !== KeyStore.Archives) { + Main.Instance.SetTextDoc(this.props.doc, this._ref.current!); } - } - ); - if (this.props.selectOnLoad) { - this.props.select(); - this._editorView!.focus(); } - } - componentWillUnmount() { - if (this._editorView) { - this._editorView.destroy(); + //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE + textCapability = (e: React.MouseEvent): void => { }; + + specificContextMenu = (e: React.MouseEvent): void => { + ContextMenu.Instance.addItem({ + description: "Text Capability", + event: this.textCapability + }); + + // ContextMenu.Instance.addItem({ + // description: "Submenu", + // items: [ + // { + // description: "item 1", event: + // }, + // { + // description: "item 2", event: + // } + // ] + // }) + // e.stopPropagation() + }; + + onPointerWheel = (e: React.WheelEvent): void => { + e.stopPropagation(); + }; + + tooltipMenuPlugin() { + return new Plugin({ + view(_editorView) { + return new TooltipTextMenu(_editorView); + } + }); } - if (this._reactionDisposer) { - this._reactionDisposer(); + onKeyPress(e: React.KeyboardEvent) { + e.stopPropagation(); + // stop propagation doesn't seem to stop propagation of native keyboard events. + // so we set a flag on the native event that marks that the event's been handled. + // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; } - } - - shouldComponentUpdate() { - return false; - } - - @action - onChange(e: React.ChangeEvent<HTMLInputElement>) { - const { fieldKey, doc } = this.props; - doc.SetOnPrototype(fieldKey, new RichTextField(e.target.value)); - // doc.SetData(fieldKey, e.target.value, RichTextField); - } - onPointerDown = (e: React.PointerEvent): void => { - if (e.buttons === 1 && this.props.isSelected() && !e.altKey) { - e.stopPropagation(); + render() { + return ( + <div + className="formattedTextBox-cont" + onKeyDown={this.onKeyPress} + onKeyPress={this.onKeyPress} + onPointerUp={this.onPointerUp} + onPointerDown={this.onPointerDown} + onContextMenu={this.specificContextMenu} + // tfs: do we need this event handler + onWheel={this.onPointerWheel} + ref={this._ref} + /> + ); } - }; - - //REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE - textCapability = (e: React.MouseEvent): void => {}; - - specificContextMenu = (e: React.MouseEvent): void => { - ContextMenu.Instance.addItem({ - description: "Text Capability", - event: this.textCapability - }); - - // ContextMenu.Instance.addItem({ - // description: "Submenu", - // items: [ - // { - // description: "item 1", event: - // }, - // { - // description: "item 2", event: - // } - // ] - // }) - // e.stopPropagation() - }; - - onPointerWheel = (e: React.WheelEvent): void => { - e.stopPropagation(); - }; - - tooltipMenuPlugin() { - return new Plugin({ - view(_editorView) { - return new TooltipTextMenu(_editorView); - } - }); - } - onKeyPress(e: React.KeyboardEvent) { - e.stopPropagation(); - // stop propagation doesn't seem to stop propagation of native keyboard events. - // so we set a flag on the native event that marks that the event's been handled. - // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true; - } - render() { - return ( - <div - className="formattedTextBox-cont" - onKeyDown={this.onKeyPress} - onKeyPress={this.onKeyPress} - onPointerDown={this.onPointerDown} - onContextMenu={this.specificContextMenu} - // tfs: do we need this event handler - onWheel={this.onPointerWheel} - ref={this._ref} - /> - ); - } } diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index 283c1f732..9bd6c1052 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -40,11 +40,13 @@ export class KeyValueBox extends React.Component<FieldViewProps> { } let realDoc = doc; - let script = CompileScript(this._valueInput, undefined, true); + let script = CompileScript(this._valueInput, { addReturn: true }); if (!script.compiled) { return; } - let field = script(); + let res = script.run(); + if (!res.success) return; + const field = res.result; if (field instanceof Field) { realDoc.Set(new Key(this._keyInput), field); } else { diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index 7ed5ee272..5647f45bf 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -76,11 +76,13 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { return field || ""; }} SetValue={(value: string) => { - let script = CompileScript(value, undefined, true); + let script = CompileScript(value, { addReturn: true }); if (!script.compiled) { return false; } - let field = script(); + let res = script.run(); + if (!res.success) return false; + const field = res.result; if (field instanceof Field) { props.doc.Set(props.fieldKey, field); return true; diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 538d4ada9..02004678d 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -10,6 +10,9 @@ import { Types } from "../server/Message"; import { UndoManager } from "../client/util/UndoManager"; import { HtmlField } from "./HtmlField"; import { BooleanField } from "./BooleanField"; +import { allLimit } from "async"; +import { prototype } from "nodemailer/lib/smtp-pool"; +import { HistogramField } from "../client/northstar/dash-fields/HistogramField"; export class Document extends Field { //TODO tfs: We should probably store FieldWaiting in fields when we request it from the server so that we don't set up multiple server gets for the same document and field @@ -343,7 +346,10 @@ export class Document extends Field { SetText(key: Key, value: string, replaceWrongType = true) { this.SetData(key, value, TextField, replaceWrongType); } - + @action + SetBoolean(key: Key, value: boolean, replaceWrongType = true) { + this.SetData(key, value, BooleanField, replaceWrongType); + } @action SetNumber(key: Key, value: number, replaceWrongType = true) { this.SetData(key, value, NumberField, replaceWrongType); @@ -393,8 +399,26 @@ export class Document extends Field { return title; //throw new Error("Method not implemented."); } - Copy(): Field { - throw new Error("Method not implemented."); + Copy(copyProto?: boolean, id?: string): Field { + let copy = new Document(); + this._proxies.forEach((fieldid, keyid) => { // copy each prototype field + let key = KeyStore.KeyLookup(keyid); + if (key) { + this.GetAsync(key, (field: Opt<Field>) => { + if (key === KeyStore.Prototype && copyProto) { // handle prototype field specially + if (field instanceof Document) { + copy.Set(key, field.Copy(false)); // only copying one level of prototypes for now... + } + } + else + if (field instanceof Document) // ... TODO bcz: should we copy documents or reference them + copy.Set(key!, field) + else if (field) + copy.Set(key!, field.Copy()) + }) + } + }); + return copy; } ToJson(): { type: Types; data: [string, string][]; _id: string } { diff --git a/src/fields/KeyStore.ts b/src/fields/KeyStore.ts index 6ed3b1604..42dc34c51 100644 --- a/src/fields/KeyStore.ts +++ b/src/fields/KeyStore.ts @@ -2,49 +2,64 @@ import { Key } from "./Key"; import { KeyTransfer } from "../server/Message"; export namespace KeyStore { - export const Prototype = new Key("Prototype"); - export const X = new Key("X"); - export const Y = new Key("Y"); - export const Page = new Key("Page"); - export const Title = new Key("Title"); - export const Author = new Key("Author"); - export const PanX = new Key("PanX"); - export const PanY = new Key("PanY"); - export const Scale = new Key("Scale"); - export const NativeWidth = new Key("NativeWidth"); - export const NativeHeight = new Key("NativeHeight"); - export const Width = new Key("Width"); - export const Height = new Key("Height"); - export const ZIndex = new Key("ZIndex"); - export const Data = new Key("Data"); - export const Annotations = new Key("Annotations"); - export const ViewType = new Key("ViewType"); - export const Layout = new Key("Layout"); - export const BackgroundColor = new Key("BackgroundColor"); - export const BackgroundLayout = new Key("BackgroundLayout"); - export const OverlayLayout = new Key("OverlayLayout"); - export const LayoutKeys = new Key("LayoutKeys"); - export const LayoutFields = new Key("LayoutFields"); - export const ColumnsKey = new Key("SchemaColumns"); - export const SchemaSplitPercentage = new Key("SchemaSplitPercentage"); - export const Caption = new Key("Caption"); - export const ActiveWorkspace = new Key("ActiveWorkspace"); - export const DocumentText = new Key("DocumentText"); - export const BrushingDocs = new Key("BrushingDocs"); - export const LinkedToDocs = new Key("LinkedToDocs"); - export const LinkedFromDocs = new Key("LinkedFromDocs"); - export const LinkDescription = new Key("LinkDescription"); - export const LinkTags = new Key("LinkTag"); - export const Thumbnail = new Key("Thumbnail"); - export const ThumbnailPage = new Key("ThumbnailPage"); - export const CurPage = new Key("CurPage"); - export const AnnotationOn = new Key("AnnotationOn"); - export const NumPages = new Key("NumPages"); - export const Ink = new Key("Ink"); - export const Cursors = new Key("Cursors"); - export const OptionalRightCollection = new Key("OptionalRightCollection"); - export const Archives = new Key("Archives"); - export const Updated = new Key("Updated"); - export const Workspaces = new Key("Workspaces"); - export const Minimized = new Key("Minimized"); + export const Prototype = new Key("Prototype"); + export const X = new Key("X"); + export const Y = new Key("Y"); + export const Page = new Key("Page"); + export const Title = new Key("Title"); + export const Author = new Key("Author"); + export const PanX = new Key("PanX"); + export const PanY = new Key("PanY"); + export const Scale = new Key("Scale"); + export const NativeWidth = new Key("NativeWidth"); + export const NativeHeight = new Key("NativeHeight"); + export const Width = new Key("Width"); + export const Height = new Key("Height"); + export const ZIndex = new Key("ZIndex"); + export const Data = new Key("Data"); + export const Annotations = new Key("Annotations"); + export const ViewType = new Key("ViewType"); + export const Layout = new Key("Layout"); + export const BackgroundColor = new Key("BackgroundColor"); + export const BackgroundLayout = new Key("BackgroundLayout"); + export const OverlayLayout = new Key("OverlayLayout"); + export const LayoutKeys = new Key("LayoutKeys"); + export const LayoutFields = new Key("LayoutFields"); + export const ColumnsKey = new Key("SchemaColumns"); + export const SchemaSplitPercentage = new Key("SchemaSplitPercentage"); + export const Caption = new Key("Caption"); + export const ActiveWorkspace = new Key("ActiveWorkspace"); + export const DocumentText = new Key("DocumentText"); + export const BrushingDocs = new Key("BrushingDocs"); + export const LinkedToDocs = new Key("LinkedToDocs"); + export const LinkedFromDocs = new Key("LinkedFromDocs"); + export const LinkDescription = new Key("LinkDescription"); + export const LinkTags = new Key("LinkTag"); + export const Thumbnail = new Key("Thumbnail"); + export const ThumbnailPage = new Key("ThumbnailPage"); + export const CurPage = new Key("CurPage"); + export const AnnotationOn = new Key("AnnotationOn"); + export const NumPages = new Key("NumPages"); + export const Ink = new Key("Ink"); + export const Cursors = new Key("Cursors"); + export const OptionalRightCollection = new Key("OptionalRightCollection"); + export const Archives = new Key("Archives"); + export const Workspaces = new Key("Workspaces"); + export const Minimized = new Key("Minimized"); + export const CopyDraggedItems = new Key("CopyDraggedItems"); + + export const KeyList: Key[] = [Prototype, X, Y, Page, Title, Author, PanX, PanY, Scale, NativeWidth, NativeHeight, + Width, Height, ZIndex, Data, Annotations, ViewType, Layout, BackgroundColor, BackgroundLayout, OverlayLayout, LayoutKeys, + LayoutFields, ColumnsKey, SchemaSplitPercentage, Caption, ActiveWorkspace, DocumentText, BrushingDocs, LinkedToDocs, LinkedFromDocs, + LinkDescription, LinkTags, Thumbnail, ThumbnailPage, CurPage, AnnotationOn, NumPages, Ink, Cursors, OptionalRightCollection, + Archives, Workspaces, Minimized, CopyDraggedItems + ]; + export function KeyLookup(keyid: string) { + for (let i = 0; i < KeyList.length; i++) { + let keylistid = KeyList[i].Id; + if (keylistid === keyid) + return KeyList[i]; + } + return null; + } } diff --git a/src/fields/NumberField.ts b/src/fields/NumberField.ts index 47dfc74cb..e0c8648de 100644 --- a/src/fields/NumberField.ts +++ b/src/fields/NumberField.ts @@ -8,7 +8,7 @@ export class NumberField extends BasicField<number> { } ToScriptString(): string { - return "new NumberField(this.Data)"; + return `new NumberField(${this.Data})`; } Copy() { |