diff options
Diffstat (limited to 'src')
53 files changed, 751 insertions, 659 deletions
diff --git a/src/Utils.ts b/src/Utils.ts index b0e66787e..c629bc263 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -2,6 +2,7 @@ import v4 = require('uuid/v4'); import v5 = require("uuid/v5"); import { Socket } from 'socket.io'; import { Message, Types } from './server/Message'; +import { Document } from './fields/Document'; export class Utils { @@ -86,14 +87,12 @@ export class Utils { } } -export function returnTrue() { - return true; -} +export function returnTrue() { return true; } -export function returnFalse() { - return false; -} +export function returnFalse() { return false; } export function emptyFunction() { } +export function emptyDocFunction(doc: Document) { } + export type Without<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>;
\ No newline at end of file diff --git a/src/client/SocketStub.ts b/src/client/SocketStub.ts index 257973e3d..5e2ca6a98 100644 --- a/src/client/SocketStub.ts +++ b/src/client/SocketStub.ts @@ -62,10 +62,13 @@ export class SocketStub { public static SEND_FIELDS_REQUEST(fieldIds: FieldId[], callback: (fields: FieldMap) => any) { Utils.EmitCallback(Server.Socket, MessageStore.GetFields, fieldIds, (fields: any[]) => { let fieldMap: any = {}; + let proms: Promise<any>[] = []; for (let field of fields) { - fieldMap[field._id] = ServerUtils.FromJson(field); + let f = ServerUtils.FromJson(field); + fieldMap[field._id] = f; + proms.push(new Promise(res => f.init(res))); } - callback(fieldMap); + Promise.all(proms).then(() => callback(fieldMap)); }); } diff --git a/src/client/northstar/dash-nodes/HistogramBox.tsx b/src/client/northstar/dash-nodes/HistogramBox.tsx index 7df59ef07..2084fc346 100644 --- a/src/client/northstar/dash-nodes/HistogramBox.tsx +++ b/src/client/northstar/dash-nodes/HistogramBox.tsx @@ -47,10 +47,6 @@ export class HistogramBox extends React.Component<FieldViewProps> { this.BinRanges[1] instanceof AggregateBinRange ? ChartType.VerticalBar : ChartType.HeatMap; } - constructor(props: FieldViewProps) { - super(props); - } - @action dropX = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.DocumentDragData) { diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts index f38b8ca75..3e093c8dc 100644 --- a/src/client/util/DocumentManager.ts +++ b/src/client/util/DocumentManager.ts @@ -28,8 +28,7 @@ export class DocumentManager { } public getAllDocumentViews(collection: Document) { - return this.DocumentViews.filter(dv => - dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === collection); + return this.DocumentViews.filter(dv => dv.props.ContainingCollectionView && dv.props.ContainingCollectionView.props.Document === collection); } public getDocumentView(toFind: Document): DocumentView | null { diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts index d66c6e90f..6a7047725 100644 --- a/src/client/util/DragManager.ts +++ b/src/client/util/DragManager.ts @@ -5,10 +5,11 @@ import { CollectionDockingView } from "../views/collections/CollectionDockingVie import { DocumentDecorations } from "../views/DocumentDecorations"; import { Main } from "../views/Main"; import { DocumentView } from "../views/nodes/DocumentView"; -import globalStyles from "../views/_global_variables"; -// import globalStyleVariables from "../views/_global_variables.scss"; // bcz: why doesn't this work? +import * as globalCssVariables from "../views/globalCssVariables.scss"; +import { KeyStore } from "../../fields/KeyStore"; +import { FieldWaiting } from "../../fields/Field"; -export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) { +export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, moveFunc?: DragManager.MoveFunction, copyOnDrop: boolean = false) { let onRowMove = action((e: PointerEvent): void => { e.stopPropagation(); e.preventDefault(); @@ -40,6 +41,31 @@ export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: return onItemDown; } +export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Document) { + let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document); + let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ? + srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc => + (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : []; + let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ? + srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc => + (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : []; + draggedDocs.push(...draggedFromDocs); + if (draggedDocs.length) { + let moddrag = [] as Document[]; + for (const draggedDoc of draggedDocs) { + let doc = await draggedDoc.GetTAsync(KeyStore.AnnotationOn, Document); + if (doc) moddrag.push(doc); + } + let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); + DragManager.StartDocumentDrag([dragEle], dragData, x, y, { + handlers: { + dragComplete: action(emptyFunction), + }, + hideSource: false + }); + } +} + export namespace DragManager { export function Root() { const root = document.getElementById("root"); @@ -131,11 +157,11 @@ export namespace DragManager { } export class LinkDragData { - constructor(linkSourceDoc: DocumentView) { - this.linkSourceDocumentView = linkSourceDoc; + constructor(linkSourceDoc: Document) { + this.linkSourceDocument = linkSourceDoc; } droppedDocuments: Document[] = []; - linkSourceDocumentView: DocumentView; + linkSourceDocument: Document; [id: string]: any; } @@ -178,7 +204,7 @@ export namespace DragManager { dragElement.style.bottom = ""; dragElement.style.left = "0"; dragElement.style.transformOrigin = "0 0"; - dragElement.style.zIndex = globalStyles.contextMenuZindex;// "1000"; + dragElement.style.zIndex = globalCssVariables.contextMenuZindex;// "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`; diff --git a/src/client/util/Scripting.ts b/src/client/util/Scripting.ts index 9015f21cf..c67cc067a 100644 --- a/src/client/util/Scripting.ts +++ b/src/client/util/Scripting.ts @@ -54,15 +54,20 @@ function Run(script: string | undefined, customParams: string[], diagnostics: an let paramNames = ["KeyStore", "Documents", ...fieldTypes.map(fn => fn.name)]; let params: any[] = [KeyStore, Documents, ...fieldTypes]; let compiledFunction = new Function(...paramNames, `return ${script}`); + let { capturedVariables = {} } = options; let run = (args: { [name: string]: any } = {}): ScriptResult => { let argsArray: any[] = []; for (let name of customParams) { if (name === "this") { continue; } - argsArray.push(args[name]); + if (name in args) { + argsArray.push(args[name]); + } else { + argsArray.push(capturedVariables[name]); + } } - let thisParam = args.this; + let thisParam = args.this || capturedVariables.this; try { const result = compiledFunction.apply(thisParam, params).apply(thisParam, argsArray); return { success: true, result }; @@ -130,22 +135,30 @@ export interface ScriptOptions { requiredType?: string; addReturn?: boolean; params?: { [name: string]: string }; + capturedVariables?: { [name: string]: Field }; } -export function CompileScript(script: string, { requiredType = "", addReturn = false, params = {} }: ScriptOptions = {}): CompileResult { +export function CompileScript(script: string, options: ScriptOptions = {}): CompileResult { + const { requiredType = "", addReturn = false, params = {}, capturedVariables = {} } = options; let host = new ScriptingCompilerHost; - let paramArray: string[] = []; - if ("this" in params) { - paramArray.push("this"); + let paramNames: string[] = []; + if ("this" in params || "this" in capturedVariables) { + paramNames.push("this"); } for (const key in params) { if (key === "this") continue; - paramArray.push(key); + paramNames.push(key); } - let paramString = paramArray.map(key => { + let paramList = paramNames.map(key => { const val = params[key]; return `${key}: ${val}`; - }).join(", "); + }); + for (const key in capturedVariables) { + if (key === "this") continue; + paramNames.push(key); + paramList.push(`${key}: ${capturedVariables[key].constructor.name}`); + } + let paramString = paramList.join(", "); let funcScript = `(function(${paramString})${requiredType ? `: ${requiredType}` : ''} { ${addReturn ? `return ${script};` : script} })`; @@ -157,7 +170,7 @@ export function CompileScript(script: string, { requiredType = "", addReturn = f let diagnostics = ts.getPreEmitDiagnostics(program).concat(testResult.diagnostics); - return Run(outputText, paramArray, diagnostics, script, { requiredType, addReturn, params }); + return Run(outputText, paramNames, diagnostics, script, options); } export function OrLiteralType(returnType: string): string { diff --git a/src/client/util/TooltipTextMenu.scss b/src/client/util/TooltipTextMenu.scss index 9111d60f1..8fddbd3cf 100644 --- a/src/client/util/TooltipTextMenu.scss +++ b/src/client/util/TooltipTextMenu.scss @@ -1,4 +1,4 @@ -@import "../views/global_variables"; +@import "../views/globalCssVariables"; .ProseMirror-textblock-dropdown { min-width: 3em; diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss index 5acf598cf..fe884ca85 100644 --- a/src/client/views/ContextMenu.scss +++ b/src/client/views/ContextMenu.scss @@ -1,4 +1,4 @@ -@import "global_variables"; +@import "globalCssVariables"; .contextMenu-cont { position: absolute; display: flex; diff --git a/src/client/views/DocumentDecorations.scss b/src/client/views/DocumentDecorations.scss index 321bda384..c1a949639 100644 --- a/src/client/views/DocumentDecorations.scss +++ b/src/client/views/DocumentDecorations.scss @@ -1,4 +1,4 @@ -@import "global_variables"; +@import "globalCssVariables"; .documentDecorations { position: absolute; @@ -78,6 +78,7 @@ grid-column-end: 6; pointer-events: all; text-align: center; + cursor: pointer; } .documentDecorations-minimizeButton { background:$alt-accent; @@ -86,6 +87,7 @@ grid-column-end: 3; pointer-events: all; text-align: center; + cursor: pointer; } .documentDecorations-background { background: lightblue; diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx index 29cca286d..2dc496bc1 100644 --- a/src/client/views/DocumentDecorations.tsx +++ b/src/client/views/DocumentDecorations.tsx @@ -7,7 +7,7 @@ import { ListField } from "../../fields/ListField"; import { NumberField } from "../../fields/NumberField"; import { Document } from "../../fields/Document"; import { TextField } from "../../fields/TextField"; -import { DragManager } from "../util/DragManager"; +import { DragManager, DragLinksAsDocuments } from "../util/DragManager"; import { SelectionManager } from "../util/SelectionManager"; import { CollectionView } from "./collections/CollectionView"; import './DocumentDecorations.scss'; @@ -17,6 +17,8 @@ import React = require("react"); import { FieldWaiting } from "../../fields/Field"; import { emptyFunction } from "../../Utils"; import { Main } from "./Main"; +import { undo } from "prosemirror-history"; +import { undoBatch } from "../util/UndoManager"; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -108,7 +110,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> document.addEventListener("pointerup", this.onBackgroundUp); this._lastDrag = [e.clientX, e.clientY]; e.stopPropagation(); - e.preventDefault(); + if (e.currentTarget.localName !== "input") { + e.preventDefault(); + } } @action @@ -154,6 +158,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (e.button === 0) { } } + @undoBatch @action onCloseUp = (e: PointerEvent): void => { e.stopPropagation(); @@ -216,7 +221,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> if (this._linkerButton.current !== null) { document.removeEventListener("pointermove", this.onLinkerButtonMoved); document.removeEventListener("pointerup", this.onLinkerButtonUp); - let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]); + let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0].props.Document); DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, { handlers: { dragComplete: action(emptyFunction), @@ -242,38 +247,15 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> } onLinkButtonMoved = async (e: PointerEvent) => { - if (this._linkButton.current !== null) { + if (this._linkButton.current !== null && (e.movementX > 1 || e.movementY > 1)) { document.removeEventListener("pointermove", this.onLinkButtonMoved); document.removeEventListener("pointerup", this.onLinkButtonUp); - let sourceDoc = SelectionManager.SelectedDocuments()[0].props.Document; - let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document); - let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ? - srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc => - (linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : []; - let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ? - srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc => - (linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : []; - draggedDocs.push(...draggedFromDocs); - if (draggedDocs.length) { - let moddrag = [] as Document[]; - for (const draggedDoc of draggedDocs) { - let doc = await draggedDoc.GetTAsync(KeyStore.AnnotationOn, Document); - if (doc) moddrag.push(doc); - } - let dragData = new DragManager.DocumentDragData(moddrag.length ? moddrag : draggedDocs); - DragManager.StartDocumentDrag([this._linkButton.current], dragData, e.x, e.y, { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } + DragLinksAsDocuments(this._linkButton.current, e.x, e.y, SelectionManager.SelectedDocuments()[0].props.Document); } e.stopPropagation(); } - onPointerMove = (e: PointerEvent): void => { e.stopPropagation(); e.preventDefault(); @@ -427,7 +409,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }> opacity: this._opacity }}> <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>...</div> - <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onPointerDown} onKeyPress={this.enterPressed} /> + <input ref={this.keyinput} className="title" type="text" name="dynbox" value={this.getValue()} onChange={this.handleChange} onPointerDown={this.onBackgroundDown} onKeyPress={this.enterPressed} /> <div className="documentDecorations-closeButton" onPointerDown={this.onCloseDown}>X</div> <div id="documentDecorations-topLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> <div id="documentDecorations-topResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div> diff --git a/src/client/views/InkingCanvas.scss b/src/client/views/InkingCanvas.scss index 42ae38c73..2c550051c 100644 --- a/src/client/views/InkingCanvas.scss +++ b/src/client/views/InkingCanvas.scss @@ -1,4 +1,4 @@ -@import "global_variables"; +@import "globalCssVariables"; .inkingCanvas { opacity:0.99; diff --git a/src/client/views/InkingControl.scss b/src/client/views/InkingControl.scss index 0d8fd8784..ba4ec41af 100644 --- a/src/client/views/InkingControl.scss +++ b/src/client/views/InkingControl.scss @@ -1,4 +1,4 @@ -@import "global_variables"; +@import "globalCssVariables"; .inking-control { position: absolute; left: 70px; diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss index 7329b8eb6..13cadb10d 100644 --- a/src/client/views/Main.scss +++ b/src/client/views/Main.scss @@ -1,4 +1,4 @@ -@import "global_variables"; +@import "globalCssVariables"; @import "nodeModuleOverrides"; html, body { @@ -184,7 +184,7 @@ button:hover { 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 c4c4a6bf9..ed61aa5a7 100644 --- a/src/client/views/Main.tsx +++ b/src/client/views/Main.tsx @@ -15,7 +15,7 @@ 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 { Utils, returnTrue, emptyFunction } from '../../Utils'; +import { Utils, returnTrue, emptyFunction, emptyDocFunction } from '../../Utils'; import * as rp from 'request-promise'; import { RouteStore } from '../../server/RouteStore'; import { ServerUtils } from '../../server/ServerUtil'; @@ -28,7 +28,7 @@ import '../northstar/model/ModelExtensions'; import { HistogramOperation } from '../northstar/operations/HistogramOperation'; import '../northstar/utils/Extensions'; import { Server } from '../Server'; -import { setupDrag, DragManager } from '../util/DragManager'; +import { SetupDrag, DragManager } from '../util/DragManager'; import { Transform } from '../util/Transform'; import { UndoManager } from '../util/UndoManager'; import { CollectionDockingView } from './collections/CollectionDockingView'; @@ -40,6 +40,8 @@ import { DocumentView } from './nodes/DocumentView'; import { FormattedTextBox } from './nodes/FormattedTextBox'; import { REPLCommand } from 'repl'; import { Key } from '../../fields/Key'; +import { PreviewCursor } from './PreviewCursor'; + @observer export class Main extends React.Component { @@ -202,7 +204,6 @@ export class Main extends React.Component { pwidthFunc = () => this.pwidth; pheightFunc = () => this.pheight; - focusDocument = (doc: Document) => { }; noScaling = () => 1; @observable _textDoc?: Document = undefined; @@ -280,7 +281,8 @@ export class Main extends React.Component { s[0] = Math.sqrt((s[0] - t[0]) * (s[0] - t[0]) + (s[1] - t[1]) * (s[1] - t[1])); return <div className="mainDiv-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} > <div className="mainDiv-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll} style={{ pointerEvents: "none", transform: `scale(${1}, ${1})`, width: `${w * s[0]}px`, height: `${h * s[0]}px` }}> - <FormattedTextBox fieldKey={this._textFieldKey!} isOverlay={true} Document={this._textDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true} selectOnLoad={true} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={(doc) => { }} /> + <FormattedTextBox fieldKey={this._textFieldKey!} isOverlay={true} Document={this._textDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true} + selectOnLoad={true} onActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} /> </div> </ div>; } @@ -299,7 +301,7 @@ export class Main extends React.Component { PanelHeight={this.pheightFunc} isTopMost={true} selectOnLoad={false} - focus={this.focusDocument} + focus={emptyDocFunction} parentActive={returnTrue} onActiveChanged={emptyFunction} ContainingCollectionView={undefined} />; @@ -344,7 +346,7 @@ export class Main extends React.Component { <ul id="add-options-list"> {btns.map(btn => <li key={btn[1]} ><div ref={btn[0]}> - <button className="round-button add-button" title={btn[2]} onPointerDown={setupDrag(btn[0], btn[3])}> + <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}> <FontAwesomeIcon icon={btn[1]} size="sm" /> </button> </div></li>)} @@ -392,6 +394,7 @@ export class Main extends React.Component { {({ measureRef }) => <div ref={measureRef} id="mainContent-div"> {this.mainContent} + <PreviewCursor /> </div> } </Measure> @@ -411,11 +414,11 @@ export class Main extends React.Component { @action AddToNorthstarCatalog(ctlog: Catalog) { CurrentUserUtils.NorthstarDBCatalog = CurrentUserUtils.NorthstarDBCatalog ? CurrentUserUtils.NorthstarDBCatalog : ctlog; if (ctlog && ctlog.schemas) { - this._northstarSchemas.push(...ctlog.schemas.map(schema => { - let schemaDoc = Documents.TreeDocument([], { width: 50, height: 100, title: schema.displayName! }); - let schemaDocuments = schemaDoc.GetList(KeyStore.Data, [] as Document[]); + ctlog.schemas.map(schema => { + let promises: Promise<void>[] = []; + let schemaDocuments: Document[] = []; CurrentUserUtils.GetAllNorthstarColumnAttributes(schema).map(attr => { - Server.GetField(attr.displayName! + ".alias", action((field: Opt<Field>) => { + let prom = Server.GetField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => { if (field instanceof Document) { schemaDocuments.push(field); } else { @@ -427,9 +430,13 @@ export class Main extends React.Component { schemaDocuments.push(Documents.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }, undefined, attr.displayName! + ".alias")); } })); + promises.push(prom); }); - return schemaDoc; - })); + Promise.all(promises).finally(() => { + let schemaDoc = Documents.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! }); + this._northstarSchemas.push(schemaDoc); + }); + }); } } async initializeNorthstar(): Promise<void> { diff --git a/src/client/views/PreviewCursor.scss b/src/client/views/PreviewCursor.scss new file mode 100644 index 000000000..20f9b9a49 --- /dev/null +++ b/src/client/views/PreviewCursor.scss @@ -0,0 +1,9 @@ + +.previewCursor { + color: black; + position: absolute; + transform-origin: left top; + top: 0; + left:0; + pointer-events: none; +}
\ No newline at end of file diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx new file mode 100644 index 000000000..ff8434681 --- /dev/null +++ b/src/client/views/PreviewCursor.tsx @@ -0,0 +1,37 @@ +import { action, observable } from 'mobx'; +import { observer } from 'mobx-react'; +import "normalize.css"; +import * as React from 'react'; +import "./PreviewCursor.scss"; + +@observer +export class PreviewCursor extends React.Component<{}> { + private _prompt = React.createRef<HTMLDivElement>(); + //when focus is lost, this will remove the preview cursor + @action onBlur = (): void => { + PreviewCursor.Visible = false; + PreviewCursor.hide(); + } + + @observable static clickPoint = [0, 0]; + @observable public static Visible = false; + @observable public static hide = () => { }; + @action + public static Show(hide: any, x: number, y: number) { + this.clickPoint = [x, y]; + this.hide = hide; + setTimeout(action(() => this.Visible = true), (1)); + } + render() { + if (!PreviewCursor.clickPoint) { + return (null); + } + if (PreviewCursor.Visible && this._prompt.current) { + this._prompt.current.focus(); + } + return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._prompt} + style={{ transform: `translate(${PreviewCursor.clickPoint[0]}px, ${PreviewCursor.clickPoint[1]}px)`, opacity: PreviewCursor.Visible ? 1 : 0 }}> + I + </div >; + } +}
\ No newline at end of file diff --git a/src/client/views/_global_variables.scss.d.ts b/src/client/views/_global_variables.scss.d.ts deleted file mode 100644 index c902d473f..000000000 --- a/src/client/views/_global_variables.scss.d.ts +++ /dev/null @@ -1,7 +0,0 @@ - -export interface I_globalScss { - contextMenuZindex: string; // context menu shows up over everything -} -export const globalStyleVariables: I_globalScss; - -export default globalStyleVariables;
\ No newline at end of file diff --git a/src/client/views/_global_variables.ts b/src/client/views/_global_variables.ts index e70bfd56c..10482bc8d 100644 --- a/src/client/views/_global_variables.ts +++ b/src/client/views/_global_variables.ts @@ -1,4 +1,4 @@ -import * as globalStyleVariables from "../views/_global_variables.scss" +import * as globalStyleVariables from "../views/globalCssVariables.scss" export interface I_globalScss { contextMenuZindex: string; // context menu shows up over everything diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index 4380c8194..b5eaab349 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -32,19 +32,18 @@ export interface CollectionViewProps extends FieldViewProps { contentRef?: React.Ref<HTMLDivElement>; } -export const COLLECTION_BORDER_WIDTH = 1; @observer export class CollectionBaseView extends React.Component<CollectionViewProps> { - get collectionViewType(): CollectionViewType { + get collectionViewType(): CollectionViewType | undefined { let Document = this.props.Document; let viewField = Document.GetT(KeyStore.ViewType, NumberField); if (viewField === FieldWaiting) { - return CollectionViewType.Invalid; + return undefined; } else if (viewField) { return viewField.Data; } else { - return CollectionViewType.Freeform; + return CollectionViewType.Invalid; } } @@ -107,15 +106,18 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { const field = new ListField([doc]); // const script = CompileScript(` // if(added) { - // console.log("added " + field.Title); + // console.log("added " + field.Title + " " + doc.Title); // } else { - // console.log("removed " + field.Title); + // console.log("removed " + field.Title + " " + doc.Title); // } // `, { // addReturn: false, // params: { // field: Document.name, // added: "boolean" + // }, + // capturedVariables: { + // doc: this.props.Document // } // }); // if (script.compiled) { @@ -177,9 +179,10 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { active: this.active, onActiveChanged: this.onActiveChanged, }; + const viewtype = this.collectionViewType; return ( <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}> - {this.props.children(this.collectionViewType, props)} + {viewtype !== undefined ? this.props.children(viewtype, props) : (null)} </div> ); } diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 583d50c5b..0e7e0afa7 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -1,8 +1,29 @@ +@import "../../views/globalCssVariables.scss"; + .collectiondockingview-content { height: 100%; } +.lm_active .messageCounter{ + color:white; + background: #999999; +} +.messageCounter { + width:18px; + height:20px; + text-align: center; + border-radius: 20px; + margin-left: 5px; + transform: translate(0px, -8px); + display: inline-block; + background: transparent; + border: 1px #999999 solid; +} .collectiondockingview-container { + width: 100%; + height: 100%; + border-style: solid; + border-width: $COLLECTION_BORDER_WIDTH; position: absolute; top: 0; left: 0; diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 212cf8a69..eb1cd1c09 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -7,18 +7,18 @@ import * as ReactDOM from 'react-dom'; import { Document } from "../../../fields/Document"; import { KeyStore } from "../../../fields/KeyStore"; import Measure from "react-measure"; -import { FieldId, Opt, Field } from "../../../fields/Field"; -import { Utils, returnTrue, emptyFunction } from "../../../Utils"; +import { FieldId, Opt, Field, FieldWaiting } from "../../../fields/Field"; +import { Utils, returnTrue, emptyFunction, emptyDocFunction } from "../../../Utils"; import { Server } from "../../Server"; import { undoBatch } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; import "./CollectionDockingView.scss"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView"; import React = require("react"); import { SubCollectionViewProps } from "./CollectionSubView"; import { ServerUtils } from "../../../server/ServerUtil"; -import { DragManager } from "../../util/DragManager"; +import { DragManager, DragLinksAsDocuments } from "../../util/DragManager"; import { TextField } from "../../../fields/TextField"; +import { ListField } from "../../../fields/ListField"; @observer export class CollectionDockingView extends React.Component<SubCollectionViewProps> { @@ -194,23 +194,35 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp @action onPointerDown = (e: React.PointerEvent): void => { var className = (e.target as any).className; - if ((className === "lm_title" || className === "lm_tab lm_active") && (e.ctrlKey || e.altKey)) { + if (className === "messageCounter") { e.stopPropagation(); e.preventDefault(); + let x = e.clientX; + let y = e.clientY; let docid = (e.target as any).DashDocId; let tab = (e.target as any).parentElement as HTMLElement; - Server.GetField(docid, action((f: Opt<Field>) => { - if (f instanceof Document) { - DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), e.pageX, e.pageY, - { - handlers: { - dragComplete: action(emptyFunction), - }, - hideSource: false - }); - } - })); - } + Server.GetField(docid, action(async (sourceDoc: Opt<Field>) => + (sourceDoc instanceof Document) && DragLinksAsDocuments(tab, x, y, sourceDoc))); + } else + if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) { + e.stopPropagation(); + e.preventDefault(); + let x = e.clientX; + let y = e.clientY; + let docid = (e.target as any).DashDocId; + let tab = (e.target as any).parentElement as HTMLElement; + Server.GetField(docid, action((f: Opt<Field>) => { + if (f instanceof Document) { + DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y, + { + handlers: { + dragComplete: action(emptyFunction), + }, + hideSource: false + }); + } + })); + } if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") { this._flush = true; } @@ -229,24 +241,44 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp this.stateChanged(); } + htmlToElement(html: string) { + var template = document.createElement('template'); + html = html.trim(); // Never return a text node of whitespace as the result + template.innerHTML = html; + return template.content.firstChild; + } + tabCreated = (tab: any) => { if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") { - if (tab.titleElement[0].textContent.indexOf("-waiting") !== -1) { - Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => { - if (f !== undefined && f instanceof Document) { - f.GetTAsync(KeyStore.Title, TextField, (tfield) => { - if (tfield !== undefined) { - tab.titleElement[0].textContent = f.Title; - } - }); - } - })); - tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; - } - tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; + Server.GetField(tab.contentItem.config.props.documentId, action((f: Opt<Field>) => { + if (f !== undefined && f instanceof Document) { + f.GetTAsync(KeyStore.Title, TextField, (tfield) => { + if (tfield !== undefined) { + tab.titleElement[0].textContent = f.Title; + } + }); + f.GetTAsync(KeyStore.LinkedFromDocs, ListField).then(lf => + f.GetTAsync(KeyStore.LinkedToDocs, ListField).then(lt => { + let count = (lf ? lf.Data.length : 0) + (lt ? lt.Data.length : 0); + let counter: any = this.htmlToElement(`<div class="messageCounter">${count}</div>`); + tab.element.append(counter); + counter.DashDocId = tab.contentItem.config.props.documentId; + (tab as any).reactionDisposer = reaction(() => [f.GetT(KeyStore.LinkedFromDocs, ListField), f.GetT(KeyStore.LinkedToDocs, ListField)], + (lists) => { + let count = (lists.length > 0 && lists[0] && lists[0]!.Data ? lists[0]!.Data.length : 0) + + (lists.length > 1 && lists[1] && lists[1]!.Data ? lists[1]!.Data.length : 0); + counter.innerHTML = count; + }); + })); + tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; + } + })); } tab.closeElement.off('click') //unbind the current click handler .click(function () { + if (tab.reactionDisposer) { + tab.reactionDisposer(); + } tab.contentItem.remove(); }); } @@ -271,13 +303,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp render() { return ( <div className="collectiondockingview-container" id="menuContainer" - onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} - style={{ - width: "100%", - height: "100%", - borderStyle: "solid", - borderWidth: `${COLLECTION_BORDER_WIDTH}px`, - }} /> + onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} /> ); } } @@ -325,7 +351,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { selectOnLoad={false} parentActive={returnTrue} onActiveChanged={emptyFunction} - focus={(doc: Document) => { }} + focus={emptyDocFunction} ContainingCollectionView={undefined} /> </div>; diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss index 9cd6c8a39..c8bfedff4 100644 --- a/src/client/views/collections/CollectionSchemaView.scss +++ b/src/client/views/collections/CollectionSchemaView.scss @@ -1,4 +1,4 @@ -@import "../global_variables"; +@import "../globalCssVariables"; //options menu styling #schemaOptionsMenuBtn { @@ -58,7 +58,9 @@ ul { .collectionSchemaView-container { - border: 1px solid $intermediate-color; + border-width: $COLLECTION_BORDER_WIDTH; + border-color : $intermediate-color; + border-style: solid; border-radius: $border-radius; box-sizing: border-box; position: absolute; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index f1b3e1b8f..fdb82690a 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -2,31 +2,29 @@ import React = require("react"); import { library } from '@fortawesome/fontawesome-svg-core'; import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, observable, trace, untracked } from "mobx"; +import { action, computed, observable, untracked } from "mobx"; import { observer } from "mobx-react"; import Measure from "react-measure"; import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table"; import "react-table/react-table.css"; import { Document } from "../../../fields/Document"; -import { Field, Opt, FieldWaiting } from "../../../fields/Field"; +import { Field, Opt } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; +import { emptyDocFunction, emptyFunction, returnFalse } from "../../../Utils"; import { Server } from "../../Server"; -import { setupDrag } from "../../util/DragManager"; +import { SetupDrag } from "../../util/DragManager"; import { CompileScript, ToField } from "../../util/Scripting"; import { Transform } from "../../util/Transform"; +import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss"; import { anchorPoints, Flyout } from "../DocumentDecorations"; import '../DocumentDecorations.scss'; import { EditableView } from "../EditableView"; import { DocumentView } from "../nodes/DocumentView"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; import "./CollectionSchemaView.scss"; -import { CollectionView } from "./CollectionView"; import { CollectionSubView } from "./CollectionSubView"; -import { TextField } from "../../../fields/TextField"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionBaseView"; -import { emptyFunction, returnFalse } from "../../../Utils"; // bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657 @@ -82,7 +80,7 @@ export class CollectionSchemaView extends CollectionSubView { isTopMost: false, selectOnLoad: false, ScreenToLocalTransform: Transform.Identity, - focus: emptyFunction, + focus: emptyDocFunction, active: returnFalse, onActiveChanged: emptyFunction, }; @@ -90,7 +88,7 @@ export class CollectionSchemaView extends CollectionSubView { <FieldView {...props} /> ); let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => props.Document, this.props.moveDocument); + let onItemDown = SetupDrag(reference, () => props.Document, this.props.moveDocument); let applyToDoc = (doc: Document, run: (args?: { [name: string]: any }) => any) => { const res = run({ this: doc }); if (!res.success) return false; @@ -245,13 +243,13 @@ export class CollectionSchemaView extends CollectionSubView { this._contentScaling = r.entry.width / selected!.GetNumber(KeyStore.NativeWidth, r.entry.width); } + @computed + get borderWidth() { return COLLECTION_BORDER_WIDTH; } getContentScaling = (): number => this._contentScaling; getPanelWidth = (): number => this._panelWidth; getPanelHeight = (): number => this._panelHeight; - getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); - getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- COLLECTION_BORDER_WIDTH - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - COLLECTION_BORDER_WIDTH).scale(1 / this._contentScaling); - - focusDocument = (doc: Document) => { }; + getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX, - this.borderWidth).scale(1 / this._contentScaling); + getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(- this.borderWidth - this.DIVIDER_WIDTH - this._dividerX - this._tableWidth, - this.borderWidth).scale(1 / this._contentScaling); onPointerDown = (e: React.PointerEvent): void => { if (e.button === 1 && this.props.isSelected() && !e.altKey && !e.ctrlKey && !e.metaKey) { @@ -315,7 +313,7 @@ export class CollectionSchemaView extends CollectionSubView { PanelWidth={this.getPanelWidth} PanelHeight={this.getPanelHeight} ContainingCollectionView={undefined} - focus={this.focusDocument} + focus={emptyDocFunction} parentActive={this.props.active} onActiveChanged={this.props.onActiveChanged} /> : null} <input value={this.previewScript} onChange={this.onPreviewScriptChange} @@ -349,7 +347,7 @@ export class CollectionSchemaView extends CollectionSubView { </Flyout>); return ( - <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} > + <div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel} ref={this._mainCont}> <div className="collectionSchemaView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}> <Measure onResize={this.setTableDimensions}> {({ measureRef }) => diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 6a6a6c900..d91db68bb 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -166,22 +166,16 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> { let item = e.dataTransfer.items[i]; if (item.kind === "string" && item.type.indexOf("uri") !== -1) { let str: string; - let prom = new Promise<string>(res => - e.dataTransfer.items[i].getAsString(res)).then(action((s: string) => { - str = s; - return rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + s)); - })).then(res => { - let type = res.headers["content-type"]; + let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve)) + .then(action((s: string) => rp.head(ServerUtils.prepend(RouteStore.corsProxy + "/" + (str = s))))) + .then(result => { + let type = result.headers["content-type"]; if (type) { - this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 }).then(doc => { - if (doc) { - this.props.addDocument(doc, false); - } - }); + this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 }) + .then(doc => doc && this.props.addDocument(doc, false)); } }); promises.push(prom); - // this.props.addDocument(Documents.WebDocument(s, { ...options, width: 300, height: 300 }), false) } let type = item.type; if (item.kind === "file") { @@ -197,33 +191,30 @@ export class CollectionSubView extends React.Component<SubCollectionViewProps> { method: 'POST', body: formData }).then(async (res: Response) => { - const json = await res.json(); - json.map((file: any) => { + (await res.json()).map(action((file: any) => { let path = window.location.origin + file; - runInAction(() => { - let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName }); + let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName }); - docPromise.then(doc => runInAction(() => { - let docs = this.props.Document.GetT(KeyStore.Data, ListField); - if (docs !== FieldWaiting) { - if (!docs) { - docs = new ListField<Document>(); - this.props.Document.Set(KeyStore.Data, docs); - } - if (doc) { - docs.Data.push(doc); - } + docPromise.then(action((doc?: Document) => { + let docs = this.props.Document.GetT(KeyStore.Data, ListField); + if (docs !== FieldWaiting) { + if (!docs) { + docs = new ListField<Document>(); + this.props.Document.Set(KeyStore.Data, docs); } - })); - }); - }); + if (doc) { + docs.Data.push(doc); + } + } + })); + })); }); promises.push(prom); } } if (promises.length) { - Promise.all(promises).catch(emptyFunction).then(() => batch.end()); + Promise.all(promises).finally(() => batch.end()); } else { batch.end(); } diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 4f940e094..8ecc5b67b 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,7 +1,9 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .collectionTreeView-dropTarget { - border: 0px solid transparent; + border-width: $COLLECTION_BORDER_WIDTH; + border-color: transparent; + border-style: solid; border-radius: $border-radius; box-sizing: border-box; height: 100%; diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 659cff9fe..51a02fc25 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -7,13 +7,13 @@ import { Document } from "../../../fields/Document"; import { FieldWaiting } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import { setupDrag, DragManager } from "../../util/DragManager"; +import { SetupDrag, DragManager } from "../../util/DragManager"; import { EditableView } from "../EditableView"; import "./CollectionTreeView.scss"; import { CollectionView } from "./CollectionView"; +import * as globalCssVariables from "../../views/globalCssVariables.scss"; import { CollectionSubView } from "./CollectionSubView"; import React = require("react"); -import { COLLECTION_BORDER_WIDTH } from './CollectionBaseView'; import { props } from 'bluebird'; @@ -77,7 +77,7 @@ class TreeView extends React.Component<TreeViewProps> { */ renderTitle() { let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag); + let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.copyOnDrag); let editableView = (titleString: string) => (<EditableView display={"inline"} @@ -139,7 +139,7 @@ export class CollectionTreeView extends CollectionSubView { ); return ( - <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}> + <div id="body" className="collectionTreeView-dropTarget" onWheel={(e: React.WheelEvent) => e.stopPropagation()} onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}> <div className="coll-title"> <EditableView contents={this.props.Document.Title} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index cf058090d..647c83d4d 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -1,4 +1,4 @@ -import { computed, reaction } from "mobx"; +import { computed, reaction, trace, IReactionDisposer } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -15,18 +15,15 @@ import React = require("react"); @observer export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> { - HackToAvoidReactionFiringUnnecessarily?: Document = undefined; + _brushReactionDisposer?: IReactionDisposer; componentDidMount() { - this.HackToAvoidReactionFiringUnnecessarily = this.props.Document; - reaction(() => - DocumentManager.Instance.getAllDocumentViews(this.HackToAvoidReactionFiringUnnecessarily!). - map(dv => dv.props.Document.GetNumber(KeyStore.X, 0)), + this._brushReactionDisposer = reaction(() => this.props.Document.GetList<Document>(this.props.fieldKey, []).map(doc => doc.GetNumber(KeyStore.X, 0)), () => { - let views = DocumentManager.Instance.getAllDocumentViews(this.props.Document); + let views = this.props.Document.GetList<Document>(this.props.fieldKey, []); 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 srcDoc = views[j]; + let dstDoc = views[i]; let x1 = srcDoc.GetNumber(KeyStore.X, 0); let x1w = srcDoc.GetNumber(KeyStore.Width, -1); let x2 = dstDoc.GetNumber(KeyStore.X, 0); @@ -53,7 +50,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP linkDoc.SetText(KeyStore.LinkDescription, "Brush between " + srcTarg.Title + " and " + dstTarg.Title); linkDoc.SetData(KeyStore.BrushingDocs, [dstTarg, srcTarg], ListField); - brushAction = brushAction = (field: ListField<Document>) => { + brushAction = (field: ListField<Document>) => { if (findBrush(field) === -1) { console.log("ADD BRUSH " + srcTarg.Title + " " + dstTarg.Title); (findBrush(field) === -1) && field.Data.push(linkDoc); @@ -67,6 +64,11 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP } }); } + componentWillUnmount() { + if (this._brushReactionDisposer) { + this._brushReactionDisposer(); + } + } documentAnchors(view: DocumentView) { let equalViews = [view]; let containerDoc = view.props.Document.GetT(KeyStore.AnnotationOn, Document); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss index c38787802..c5b8fc5e8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss @@ -1,4 +1,4 @@ -@import "global_variables"; +@import "globalCssVariables"; .collectionFreeFormRemoteCursors-cont { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 31809f30b..392bd514f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -1,13 +1,13 @@ -@import "../../global_variables"; +@import "../../globalCssVariables"; .collectionfreeformview-measure { - position: absolute; + position: inherit; top: 0; left: 0; width: 100%; height: 100%; } .collectionfreeformview { - position: absolute; + position: inherit; top: 0; left: 0; width: 100%; @@ -16,7 +16,7 @@ } .collectionfreeformview-container { .collectionfreeformview > .jsx-parser { - position: absolute; + position: inherit; height: 100%; width: 100%; } @@ -28,8 +28,10 @@ // background-size: 30px 30px; // } + border-width: $COLLECTION_BORDER_WIDTH; box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; - border: 0px solid $light-color-secondary; + border-color: $light-color-secondary; + border-style: solid; border-radius: $border-radius; box-sizing: border-box; position: absolute; @@ -41,7 +43,7 @@ } .collectionfreeformview-overlay { .collectionfreeformview > .jsx-parser { - position: absolute; + position: inherit; height: 100%; } .formattedTextBox-cont { @@ -50,10 +52,12 @@ } opacity: 0.99; - border: 0px solid transparent; + border-width: 0; + border-color: transparent; + border-style: solid; border-radius: $border-radius; box-sizing: border-box; - position:absolute; + position: absolute; overflow: hidden; top: 0; left: 0; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 01ebbe0e1..e19dc98fa 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,31 +1,30 @@ -import { action, computed, observable, trace, ObservableSet, runInAction } from "mobx"; +import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; +import Measure from "react-measure"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; import { KeyStore } from "../../../../fields/KeyStore"; +import { NumberField } from "../../../../fields/NumberField"; import { TextField } from "../../../../fields/TextField"; +import { emptyFunction, returnFalse } from "../../../../Utils"; +import { DocumentManager } from "../../../util/DocumentManager"; import { DragManager } from "../../../util/DragManager"; +import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; +import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss"; import { InkingCanvas } from "../../InkingCanvas"; +import { Main } from "../../Main"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentContentsView } from "../../nodes/DocumentContentsView"; import { DocumentViewProps } from "../../nodes/DocumentView"; -import { COLLECTION_BORDER_WIDTH } from "../CollectionBaseView"; import { CollectionSubView } from "../CollectionSubView"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; +import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import v5 = require("uuid/v5"); -import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; -import { PreviewCursor } from "./PreviewCursor"; -import { DocumentManager } from "../../../util/DocumentManager"; -import { SelectionManager } from "../../../util/SelectionManager"; -import { NumberField } from "../../../../fields/NumberField"; -import { Main } from "../../Main"; -import Measure from "react-measure"; -import { returnFalse, emptyFunction } from "../../../../Utils"; @observer export class CollectionFreeFormView extends CollectionSubView { @@ -297,8 +296,12 @@ export class CollectionFreeFormView extends CollectionSubView { layoutKey={KeyStore.OverlayLayout} isTopMost={this.props.isTopMost} isSelected={returnFalse} select={emptyFunction} />); } - getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()); - getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-COLLECTION_BORDER_WIDTH, -COLLECTION_BORDER_WIDTH); + @computed + get borderWidth() { + return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; + } + getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX, -this.centeringShiftY).transform(this.getLocalTransform()); + getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.scale).translate(this.panX, this.panY); noScaling = () => 1; childViews = () => this.views; @@ -307,9 +310,9 @@ export class CollectionFreeFormView extends CollectionSubView { const [dx, dy] = [this.centeringShiftX, this.centeringShiftY]; const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0); const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0); - const zoom: number = this.zoomScaling; - const blay = this.backgroundView; - const olay = this.overlayView; + const zoom: number = this.zoomScaling;// needs to be a variable outside of the <Measure> otherwise, reactions won't fire + const backgroundView = this.backgroundView; // needs to be a variable outside of the <Measure> otherwise, reactions won't fire + const overlayView = this.overlayView;// needs to be a variable outside of the <Measure> otherwise, reactions won't fire return ( <Measure onResize={(r: any) => runInAction(() => { this._pwidth = r.entry.width; this._pheight = r.entry.height; })}> @@ -317,25 +320,21 @@ export class CollectionFreeFormView extends CollectionSubView { <div className={`collectionfreeformview-measure`} ref={measureRef}> <div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`} onPointerDown={this.onPointerDown} onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))} - onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} - style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }} ref={this.createDropTarget}> + onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onWheel={this.onPointerWheel} ref={this.createDropTarget}> <MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} - addDocument={this.addDocument} removeDocument={this.props.removeDocument} + addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}> - <PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} - getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} > - <div className="collectionfreeformview" ref={this._canvasRef} - style={{ transform: `translate(${dx}px, ${dy}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}> - {blay} - <CollectionFreeFormLinksView {...this.props}> - <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} > - {this.childViews} - </InkingCanvas> - </CollectionFreeFormLinksView> - <CollectionFreeFormRemoteCursors {...this.props} /> - </div> - {olay} - </PreviewCursor> + <div className="collectionfreeformview" ref={this._canvasRef} + style={{ transform: `translate(${dx}px, ${dy}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}> + {backgroundView} + <CollectionFreeFormLinksView {...this.props}> + <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} > + {this.childViews} + </InkingCanvas> + </CollectionFreeFormLinksView> + <CollectionFreeFormRemoteCursors {...this.props} /> + </div> + {overlayView} </MarqueeView> </div> </div>)} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 0b406e722..e5ffcec76 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -1,6 +1,6 @@ .marqueeView { - position: absolute; + position: inherit; top:0; left:0; width:100%; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 1e6faafb3..a4722a6f8 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,4 +1,4 @@ -import { action, computed, observable, trace } from "mobx"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../../fields/Document"; import { FieldWaiting } from "../../../../fields/Field"; @@ -7,10 +7,11 @@ import { KeyStore } from "../../../../fields/KeyStore"; import { Documents } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; +import { undoBatch } from "../../../util/UndoManager"; import { InkingCanvas } from "../../InkingCanvas"; +import { PreviewCursor } from "../../PreviewCursor"; import { CollectionFreeFormView } from "./CollectionFreeFormView"; import "./MarqueeView.scss"; -import { PreviewCursor } from "./PreviewCursor"; import React = require("react"); interface MarqueeViewProps { @@ -21,6 +22,7 @@ interface MarqueeViewProps { activeDocuments: () => Document[]; selectDocuments: (docs: Document[]) => void; removeDocument: (doc: Document) => boolean; + addLiveTextDocument: (doc: Document) => void; } @observer @@ -32,6 +34,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> @observable _downY: number = 0; @observable _used: boolean = false; @observable _visible: boolean = false; + _showOnUp: boolean = false; static DRAG_THRESHOLD = 4; @action @@ -47,11 +50,31 @@ export class MarqueeView extends React.Component<MarqueeViewProps> } @action + onKeyPress = (e: KeyboardEvent) => { + // Mixing events between React and Native is finicky. In FormattedTextBox, we set the + // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore + // the keyPress here. + //if not these keys, make a textbox if preview cursor is active! + if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { + //make textbox and add it to this collection + let [x, y] = this.props.getTransform().transformPoint(this._downX, this._downY); + let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); + this.props.addLiveTextDocument(newBox); + PreviewCursor.Visible = false; + e.stopPropagation(); + } + } + hideCursor = () => { + document.removeEventListener("keypress", this.onKeyPress, false); + } + @action onPointerDown = (e: React.PointerEvent): void => { if (e.buttons === 1 && !e.altKey && !e.metaKey && this.props.container.props.active()) { this._downX = this._lastX = e.pageX; this._downY = this._lastY = e.pageY; this._used = false; + this._showOnUp = true; + document.removeEventListener("keypress", this.onKeyPress, false); document.addEventListener("pointermove", this.onPointerMove, true); document.addEventListener("pointerup", this.onPointerUp, true); document.addEventListener("keydown", this.marqueeCommand, true); @@ -63,6 +86,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps> this._lastX = e.pageX; this._lastY = e.pageY; if (!e.cancelBubble) { + if (Math.abs(this._downX - e.clientX) > 4 || Math.abs(this._downY - e.clientY) > 4) { + this._showOnUp = false; + PreviewCursor.Visible = false; + } if (!this._used && e.buttons === 1 && !e.altKey && !e.metaKey && (Math.abs(this._lastX - this._downX) > MarqueeView.DRAG_THRESHOLD || Math.abs(this._lastY - this._downY) > MarqueeView.DRAG_THRESHOLD)) { this._visible = true; @@ -76,11 +103,16 @@ export class MarqueeView extends React.Component<MarqueeViewProps> onPointerUp = (e: PointerEvent): void => { this.cleanupInteractions(true); this._visible = false; - let mselect = this.marqueeSelect(); - if (!e.shiftKey) { - SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); + if (this._showOnUp) { + PreviewCursor.Show(this.hideCursor, this._downX, this._downY); + document.addEventListener("keypress", this.onKeyPress, false); + } else { + let mselect = this.marqueeSelect(); + if (!e.shiftKey) { + SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document); + } + this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } - this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]); } intersectRect(r1: { left: number, top: number, width: number, height: number }, @@ -97,6 +129,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) }; } + @undoBatch @action marqueeCommand = (e: KeyboardEvent) => { if (e.key === "Backspace" || e.key === "Delete") { diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss b/src/client/views/collections/collectionFreeForm/PreviewCursor.scss deleted file mode 100644 index 7a67c29bf..000000000 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.scss +++ /dev/null @@ -1,27 +0,0 @@ - -.previewCursor { - color: black; - position: absolute; - transform-origin: left top; - top: 0; - left:0; - pointer-events: none; -} -.previewCursorView { - top: 0; - left:0; - position: absolute; - width:100%; - height:100%; -} - -//this is an animation for the blinking cursor! -// @keyframes blink { -// 0% {opacity: 0} -// 49%{opacity: 0} -// 50% {opacity: 1} -// } - -// #previewCursor { -// animation: blink 1s infinite; -// }
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx b/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx deleted file mode 100644 index 8eabb020a..000000000 --- a/src/client/views/collections/collectionFreeForm/PreviewCursor.tsx +++ /dev/null @@ -1,120 +0,0 @@ -import { action, observable, trace, computed, reaction } from "mobx"; -import { observer } from "mobx-react"; -import { Document } from "../../../../fields/Document"; -import { Documents } from "../../../documents/Documents"; -import { Transform } from "../../../util/Transform"; -import { CollectionFreeFormView } from "./CollectionFreeFormView"; -import "./PreviewCursor.scss"; -import React = require("react"); -import { interfaceDeclaration } from "babel-types"; - - -export interface PreviewCursorProps { - getTransform: () => Transform; - getContainerTransform: () => Transform; - container: CollectionFreeFormView; - addLiveTextDocument: (doc: Document) => void; -} - -@observer -export class PreviewCursor extends React.Component<PreviewCursorProps> { - @observable _lastX: number = 0; - @observable _lastY: number = 0; - @observable public _visible: boolean = false; - @observable public DownX: number = 0; - @observable public DownY: number = 0; - _showOnUp: boolean = false; - - @action - cleanupInteractions = () => { - document.removeEventListener("pointerup", this.onPointerUp, true); - document.removeEventListener("pointermove", this.onPointerMove, true); - } - - @action - onPointerDown = (e: React.PointerEvent) => { - if (e.button === 0 && this.props.container.props.active()) { - document.removeEventListener("keypress", this.onKeyPress, false); - this._showOnUp = true; - this.DownX = e.pageX; - this.DownY = e.pageY; - document.addEventListener("pointerup", this.onPointerUp, true); - document.addEventListener("pointermove", this.onPointerMove, true); - } - } - @action - onPointerMove = (e: PointerEvent): void => { - if (Math.abs(this.DownX - e.clientX) > 4 || Math.abs(this.DownY - e.clientY) > 4) { - this._showOnUp = false; - this._visible = false; - } - } - - @action - onPointerUp = (e: PointerEvent): void => { - if (this._showOnUp) { - document.addEventListener("keypress", this.onKeyPress, false); - this._lastX = this.DownX; - this._lastY = this.DownY; - this._visible = true; - } - this.cleanupInteractions(); - } - - @action - onKeyPress = (e: KeyboardEvent) => { - // Mixing events between React and Native is finicky. In FormattedTextBox, we set the - // DASHFormattedTextBoxHandled flag when a text box consumes a key press so that we can ignore - // the keyPress here. - //if not these keys, make a textbox if preview cursor is active! - if (!e.ctrlKey && !e.altKey && !e.defaultPrevented && !(e as any).DASHFormattedTextBoxHandled) { - //make textbox and add it to this collection - let [x, y] = this.props.getTransform().transformPoint(this._lastX, this._lastY); - let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "typed text" }); - this.props.addLiveTextDocument(newBox); - document.removeEventListener("keypress", this.onKeyPress, false); - this._visible = false; - e.stopPropagation(); - } - } - - getPoint = () => this.props.getContainerTransform().transformPoint(this._lastX, this._lastY); - getVisible = () => this._visible; - setVisible = (v: boolean) => { - this._visible = v; - document.removeEventListener("keypress", this.onKeyPress, false); - } - render() { - return ( - <div className="previewCursorView" onPointerDown={this.onPointerDown}> - {this.props.children} - <PreviewCursorPrompt setVisible={this.setVisible} getPoint={this.getPoint} getVisible={this.getVisible} /> - </div> - ); - } -} - -export interface PromptProps { - getPoint: () => number[]; - getVisible: () => boolean; - setVisible: (v: boolean) => void; -} - -@observer -export class PreviewCursorPrompt extends React.Component<PromptProps> { - private _promptRef = React.createRef<HTMLDivElement>(); - - //when focus is lost, this will remove the preview cursor - @action onBlur = (): void => this.props.setVisible(false); - - render() { - let p = this.props.getPoint(); - if (this.props.getVisible() && this._promptRef.current) { - this._promptRef.current.focus(); - } - return <div className="previewCursor" id="previewCursor" onBlur={this.onBlur} tabIndex={0} ref={this._promptRef} - style={{ transform: `translate(${p[0]}px, ${p[1]}px)`, opacity: this.props.getVisible() ? 1 : 0 }}> - I - </div >; - } -}
\ No newline at end of file diff --git a/src/client/views/_global_variables.scss b/src/client/views/globalCssVariables.scss index cd6af2dac..5c8e9c8fc 100644 --- a/src/client/views/_global_variables.scss +++ b/src/client/views/globalCssVariables.scss @@ -22,7 +22,8 @@ $contextMenu-zindex: 1000; // context menu shows up over everything $mainTextInput-zindex: 999; // then text input overlay so that it's context menu will appear over decorations, etc $docDecorations-zindex: 998; // then doc decorations appear over everything else $remoteCursors-zindex: 997; // ... not sure what level the remote cursors should go -- is this right? - +$COLLECTION_BORDER_WIDTH: 1; :export { contextMenuZindex: $contextMenu-zindex; + COLLECTION_BORDER_WIDTH: $COLLECTION_BORDER_WIDTH; }
\ No newline at end of file diff --git a/src/client/views/globalCssVariables.scss.d.ts b/src/client/views/globalCssVariables.scss.d.ts new file mode 100644 index 000000000..e874b815d --- /dev/null +++ b/src/client/views/globalCssVariables.scss.d.ts @@ -0,0 +1,8 @@ + +interface IGlobalScss { + contextMenuZindex: string; // context menu shows up over everything + COLLECTION_BORDER_WIDTH: number; +} +declare const globalCssVariables: IGlobalScss; + +export = globalCssVariables;
\ No newline at end of file diff --git a/src/client/views/nodes/DocumentView.scss b/src/client/views/nodes/DocumentView.scss index 5126e69f9..a946ac1a8 100644 --- a/src/client/views/nodes/DocumentView.scss +++ b/src/client/views/nodes/DocumentView.scss @@ -1,7 +1,7 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .documentView-node { - position: absolute; + position: inherit; top: 0; left:0; background: $light-color; //overflow: hidden; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9c31a83c1..4d7a85316 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -20,6 +20,7 @@ import { ContextMenu } from "../ContextMenu"; import { DocumentContentsView } from "./DocumentContentsView"; import "./DocumentView.scss"; import React = require("react"); +import { undoBatch, UndoManager } from "../../util/UndoManager"; export interface DocumentViewProps { @@ -238,19 +239,18 @@ export class DocumentView extends React.Component<DocumentViewProps> { SelectionManager.DeselectAll(); } + @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { if (de.data instanceof DragManager.LinkDragData) { - let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document; + let sourceDoc: Document = de.data.linkSourceDocument; let destDoc: Document = this.props.Document; - if (this.props.isTopMost) { - return; - } let linkDoc: Document = new Document(); destDoc.GetTAsync(KeyStore.Prototype, Document).then(protoDest => sourceDoc.GetTAsync(KeyStore.Prototype, Document).then(protoSrc => runInAction(() => { + let batch = UndoManager.StartBatch("document view drop"); linkDoc.Set(KeyStore.Title, new TextField("New Link")); linkDoc.Set(KeyStore.LinkDescription, new TextField("")); linkDoc.Set(KeyStore.LinkTags, new TextField("Default")); @@ -259,20 +259,23 @@ export class DocumentView extends React.Component<DocumentViewProps> { let srcTarg = protoSrc ? protoSrc : sourceDoc; linkDoc.Set(KeyStore.LinkedToDocs, dstTarg); linkDoc.Set(KeyStore.LinkedFromDocs, srcTarg); - dstTarg.GetOrCreateAsync( + const prom1 = new Promise(resolve => dstTarg.GetOrCreateAsync( KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc); + resolve(); } - ); - srcTarg.GetOrCreateAsync( + )); + const prom2 = new Promise(resolve => srcTarg.GetOrCreateAsync( KeyStore.LinkedToDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc); + resolve(); } - ); + )); + Promise.all([prom1, prom2]).finally(() => batch.end()); }) ) ); diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx index 40b44aae5..0037d7b28 100644 --- a/src/client/views/nodes/FieldView.tsx +++ b/src/client/views/nodes/FieldView.tsx @@ -19,7 +19,7 @@ import { ListField } from "../../../fields/ListField"; import { DocumentContentsView } from "./DocumentContentsView"; import { Transform } from "../../util/Transform"; import { KeyStore } from "../../../fields/KeyStore"; -import { returnFalse, emptyFunction } from "../../../Utils"; +import { returnFalse, emptyDocFunction } from "../../../Utils"; // @@ -85,7 +85,7 @@ export class FieldView extends React.Component<FieldViewProps> { PanelHeight={() => 100} isTopMost={true} //TODO Why is this top most? selectOnLoad={false} - focus={emptyFunction} + focus={emptyDocFunction} isSelected={returnFalse} select={returnFalse} layoutKey={KeyStore.Layout} diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss index d2ba52cf9..3978c3d38 100644 --- a/src/client/views/nodes/FormattedTextBox.scss +++ b/src/client/views/nodes/FormattedTextBox.scss @@ -1,4 +1,4 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .ProseMirror { width: 100%; height: auto; diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx index 41cb34416..19ed65272 100644 --- a/src/client/views/nodes/FormattedTextBox.tsx +++ b/src/client/views/nodes/FormattedTextBox.tsx @@ -17,6 +17,8 @@ import "./FormattedTextBox.scss"; import React = require("react"); import { undoItem } from "prosemirror-menu"; import buildKeymap from "../../util/ProsemirrorKeymap"; +import { TextField } from "../../../fields/TextField"; +import { KeyStore } from "../../../fields/KeyStore"; const { buildMenuItems } = require("prosemirror-example-setup"); const { menuBar } = require("prosemirror-menu"); @@ -58,15 +60,20 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte this.onChange = this.onChange.bind(this); } + _applyingChange: boolean = false; + dispatchTransaction = (tx: Transaction) => { if (this._editorView) { const state = this._editorView.state.apply(tx); this._editorView.updateState(state); + this._applyingChange = true; this.props.Document.SetDataOnPrototype( this.props.fieldKey, JSON.stringify(state.toJSON()), RichTextField ); + this.props.Document.SetDataOnPrototype(KeyStore.DocumentText, state.doc.textBetween(0, state.doc.content.size, "\n\n"), TextField); + this._applyingChange = false; // doc.SetData(fieldKey, JSON.stringify(state.toJSON()), RichTextField); } } @@ -113,7 +120,7 @@ export class FormattedTextBox extends React.Component<(FieldViewProps & Formatte return field && field !== FieldWaiting ? field.Data : undefined; }, field => { - if (field && this._editorView) { + if (field && this._editorView && !this._applyingChange) { this._editorView.updateState( EditorState.fromJSON(config, JSON.parse(field)) ); diff --git a/src/client/views/nodes/KeyValueBox.scss b/src/client/views/nodes/KeyValueBox.scss index 63ae75424..6ebd73f2c 100644 --- a/src/client/views/nodes/KeyValueBox.scss +++ b/src/client/views/nodes/KeyValueBox.scss @@ -1,6 +1,7 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .keyValueBox-cont { overflow-y: scroll; + width:100%; height: 100%; background-color: $light-color; border: 1px solid $intermediate-color; @@ -8,31 +9,58 @@ box-sizing: border-box; display: inline-block; .imageBox-cont img { - max-height: 45px; - height: auto; - } - td { - padding: 6px 8px; - border-right: 1px solid $intermediate-color; - border-top: 1px solid $intermediate-color; - &:last-child { - border-right: none; - } + width: auto; } } +$header-height: 30px; +.keyValueBox-tbody { + width:100%; + height:100%; + position: absolute; + overflow-y: scroll; +} +.keyValueBox-key { + display: inline-block; + height:100%; + width:50%; + text-align: center; +} +.keyValueBox-fields { + display: inline-block; + height:100%; + width:50%; + text-align: center; +} .keyValueBox-table { - position: relative; + position: absolute; + width:100%; + height:100%; border-collapse: collapse; } - +.keyValueBox-td-key { + display:inline-block; + height:30px; +} +.keyValueBox-td-value { + display:inline-block; + height:30px; +} +.keyValueBox-valueRow { + width:100%; + height:30px; + display: inline-block; +} .keyValueBox-header { + width:100%; + position: relative; + display: inline-block; background: $intermediate-color; color: $light-color; text-transform: uppercase; letter-spacing: 2px; font-size: 12px; - height: 30px; + height: $header-height; padding-top: 4px; th { font-weight: normal; @@ -43,13 +71,50 @@ } .keyValueBox-evenRow { + position: relative; + display: inline-block; + width:100%; + height:$header-height; background: $light-color; .formattedTextBox-cont { background: $light-color; } } +.keyValueBox-cont { + .collectionfreeformview-overlay { + position: relative; + } +} +.keyValueBox-dividerDraggerThumb{ + position: relative; + width: 4px; + float: left; + height: 30px; + width: 10px; + z-index: 20; + right: 0; + top: 0; + border-radius: 10px; + background: gray; + pointer-events: all; +} +.keyValueBox-dividerDragger{ + position: relative; + width: 100%; + float: left; + height: 37px; + z-index: 20; + right: 0; + top: 0; + background: transparent; + pointer-events: none; +} .keyValueBox-oddRow { + position: relative; + display: inline-block; + width:100%; + height:30px; background: $light-color-secondary; .formattedTextBox-cont { background: $light-color-secondary; diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx index bcac113f0..29e4af160 100644 --- a/src/client/views/nodes/KeyValueBox.tsx +++ b/src/client/views/nodes/KeyValueBox.tsx @@ -1,23 +1,25 @@ +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app import { Document } from '../../../fields/Document'; -import { FieldWaiting, Field } from '../../../fields/Field'; +import { Field, FieldWaiting } from '../../../fields/Field'; +import { Key } from '../../../fields/Key'; import { KeyStore } from '../../../fields/KeyStore'; +import { CompileScript, ToField } from "../../util/Scripting"; import { FieldView, FieldViewProps } from './FieldView'; import "./KeyValueBox.scss"; import { KeyValuePair } from "./KeyValuePair"; import React = require("react"); -import { CompileScript, ToField } from "../../util/Scripting"; -import { Key } from '../../../fields/Key'; -import { observable, action } from "mobx"; @observer export class KeyValueBox extends React.Component<FieldViewProps> { + private _mainCont = React.createRef<HTMLDivElement>(); public static LayoutString(fieldStr: string = "DataKey") { return FieldView.LayoutString(KeyValueBox, fieldStr); } @observable private _keyInput: string = ""; @observable private _valueInput: string = ""; + @computed get splitPercentage() { return this.props.Document.GetNumber(KeyStore.SchemaSplitPercentage, 50); } constructor(props: FieldViewProps) { @@ -90,7 +92,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> { let rows: JSX.Element[] = []; let i = 0; for (let key in ids) { - rows.push(<KeyValuePair doc={realDoc} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />); + rows.push(<KeyValuePair doc={realDoc} keyWidth={100 - this.splitPercentage} rowStyle={"keyValueBox-" + (i++ % 2 ? "oddRow" : "evenRow")} fieldId={key} key={key} />); } return rows; } @@ -107,24 +109,51 @@ export class KeyValueBox extends React.Component<FieldViewProps> { newKeyValue = () => ( - <tr> - <td><input type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} /></td> - <td><input type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} /></td> + <tr className="keyValueBox-valueRow"> + <td className="keyValueBox-td-key" style={{ width: `${100 - this.splitPercentage}%` }}> + <input style={{ width: "100%" }} type="text" value={this._keyInput} placeholder="Key" onChange={this.keyChanged} /> + </td> + <td className="keyValueBox-td-value" style={{ width: `${this.splitPercentage}%` }}> + <input style={{ width: "100%" }} type="text" value={this._valueInput} placeholder="Value" onChange={this.valueChanged} onKeyPress={this.onEnterKey} /> + </td> </tr> ) + @action + onDividerMove = (e: PointerEvent): void => { + let nativeWidth = this._mainCont.current!.getBoundingClientRect(); + this.props.Document.SetNumber(KeyStore.SchemaSplitPercentage, Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100))); + } + @action + onDividerUp = (e: PointerEvent): void => { + document.removeEventListener("pointermove", this.onDividerMove); + document.removeEventListener('pointerup', this.onDividerUp); + } + onDividerDown = (e: React.PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + document.addEventListener("pointermove", this.onDividerMove); + document.addEventListener('pointerup', this.onDividerUp); + } + render() { - return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel}> + let dividerDragger = this.splitPercentage === 0 ? (null) : + <div className="keyValueBox-dividerDragger" style={{ transform: `translate(calc(${100 - this.splitPercentage}% - 5px), 0px)` }}> + <div className="keyValueBox-dividerDraggerThumb" onPointerDown={this.onDividerDown} /> + </div>; + + return (<div className="keyValueBox-cont" onWheel={this.onPointerWheel} ref={this._mainCont}> <table className="keyValueBox-table"> - <tbody> + <tbody className="keyValueBox-tbody"> <tr className="keyValueBox-header"> - <th>Key</th> - <th>Fields</th> + <th className="keyValueBox-key" style={{ width: `${100 - this.splitPercentage}%` }}>Key</th> + <th className="keyValueBox-fields" style={{ width: `${this.splitPercentage}%` }}>Fields</th> </tr> {this.createTable()} {this.newKeyValue()} </tbody> </table> + {dividerDragger} </div>); } }
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss index 64e871e1c..04d002c7b 100644 --- a/src/client/views/nodes/KeyValuePair.scss +++ b/src/client/views/nodes/KeyValuePair.scss @@ -1,12 +1,30 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .container{ + width:100%; + height:100%; display: flex; flex-direction: row; flex-wrap: nowrap; justify-content: space-between; } +.keyValuePair-td-key { + display:inline-block; + width: 50%; +} +.keyValuePair-td-value { + display:inline-block; + width: 50%; +} +.keyValuePair-keyField { + width:100%; + text-align: center; + position: relative; + overflow: auto; +} .delete{ - color: red; + position: relative; + background-color: transparent; + color:red; }
\ No newline at end of file diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx index a1050dc6e..3e0b61c3d 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -12,7 +12,7 @@ import { Server } from "../../Server"; import { EditableView } from "../EditableView"; import { CompileScript, ToField } from "../../util/Scripting"; import { Transform } from '../../util/Transform'; -import { returnFalse, emptyFunction } from '../../../Utils'; +import { returnFalse, emptyFunction, emptyDocFunction } from '../../../Utils'; // Represents one row in a key value plane @@ -20,6 +20,7 @@ export interface KeyValuePairProps { rowStyle: string; fieldId: string; doc: Document; + keyWidth: number; } @observer export class KeyValuePair extends React.Component<KeyValuePairProps> { @@ -54,52 +55,53 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { active: returnFalse, onActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, - focus: emptyFunction, + focus: emptyDocFunction, }; let contents = ( <FieldView {...props} /> ); return ( <tr className={this.props.rowStyle}> - {/* <button>X</button> */} - <td> + <td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}> <div className="container"> - <div>{this.key.Name}</div> <button className="delete" onClick={() => { let field = props.Document.Get(props.fieldKey); if (field && field instanceof Field) { props.Document.Set(props.fieldKey, undefined); } }}>X</button> + <div className="keyValuePair-keyField">{this.key.Name}</div> </div> </td> - <td><EditableView contents={contents} height={36} GetValue={() => { - let field = props.Document.Get(props.fieldKey); - if (field && field instanceof Field) { - return field.ToScriptString(); - } - return field || ""; - }} - SetValue={(value: string) => { - let script = CompileScript(value, { addReturn: true }); - if (!script.compiled) { - return false; + <td className="keyValuePair-td-value" style={{ width: `${100 - this.props.keyWidth}%` }}> + <EditableView contents={contents} height={36} GetValue={() => { + let field = props.Document.Get(props.fieldKey); + if (field && field instanceof Field) { + return field.ToScriptString(); } - let res = script.run(); - if (!res.success) return false; - const field = res.result; - if (field instanceof Field) { - props.Document.Set(props.fieldKey, field); - return true; - } else { - let dataField = ToField(field); - if (dataField) { - props.Document.Set(props.fieldKey, dataField); + return field || ""; + }} + SetValue={(value: string) => { + let script = CompileScript(value, { addReturn: true }); + if (!script.compiled) { + return false; + } + let res = script.run(); + if (!res.success) return false; + const field = res.result; + if (field instanceof Field) { + props.Document.Set(props.fieldKey, field); return true; + } else { + let dataField = ToField(field); + if (dataField) { + props.Document.Set(props.fieldKey, dataField); + return true; + } } - } - return false; - }}></EditableView></td> + return false; + }}> + </EditableView></td> </tr> ); } diff --git a/src/client/views/nodes/LinkBox.scss b/src/client/views/nodes/LinkBox.scss index 5d5f782d2..8bc70b48f 100644 --- a/src/client/views/nodes/LinkBox.scss +++ b/src/client/views/nodes/LinkBox.scss @@ -1,4 +1,4 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .link-container { width: 100%; height: 35px; diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx index b016a3d48..1c0e316e8 100644 --- a/src/client/views/nodes/LinkBox.tsx +++ b/src/client/views/nodes/LinkBox.tsx @@ -1,24 +1,16 @@ -import { observable, computed, action } from "mobx"; -import React = require("react"); -import { SelectionManager } from "../../util/SelectionManager"; +import { library } from '@fortawesome/fontawesome-svg-core'; +import { faEdit, faEye, faTimes } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { observer } from "mobx-react"; -import './LinkBox.scss'; -import { KeyStore } from '../../../fields/KeyStore'; -import { props } from "bluebird"; -import { DocumentView } from "./DocumentView"; import { Document } from "../../../fields/Document"; +import { KeyStore } from '../../../fields/KeyStore'; import { ListField } from "../../../fields/ListField"; +import { NumberField } from "../../../fields/NumberField"; import { DocumentManager } from "../../util/DocumentManager"; -import { LinkEditor } from "./LinkEditor"; -import { CollectionDockingView } from "../collections/CollectionDockingView"; -import { library } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faEye } from '@fortawesome/free-solid-svg-icons'; -import { faEdit } from '@fortawesome/free-solid-svg-icons'; -import { faTimes } from '@fortawesome/free-solid-svg-icons'; import { undoBatch } from "../../util/UndoManager"; -import { FieldWaiting } from "../../../fields/Field"; -import { NumberField } from "../../../fields/NumberField"; +import { CollectionDockingView } from "../collections/CollectionDockingView"; +import './LinkBox.scss'; +import React = require("react"); library.add(faEye); diff --git a/src/client/views/nodes/LinkEditor.scss b/src/client/views/nodes/LinkEditor.scss index fb0c69cff..ea2e7289c 100644 --- a/src/client/views/nodes/LinkEditor.scss +++ b/src/client/views/nodes/LinkEditor.scss @@ -1,4 +1,4 @@ -@import "../global_variables"; +@import "../globalCssVariables"; .edit-container { width: 100%; height: auto; diff --git a/src/fields/Document.ts b/src/fields/Document.ts index 60eaf5b51..628fe684c 100644 --- a/src/fields/Document.ts +++ b/src/fields/Document.ts @@ -41,14 +41,14 @@ export class Document extends Field { @computed public get Title(): string { let title = this.Get(KeyStore.Title, true); - if (title) { + if (title || title === FieldWaiting) { if (title !== FieldWaiting && title instanceof TextField) { return title.Data; } else return "-waiting-"; } let parTitle = this.GetT(KeyStore.Title, TextField); - if (parTitle) { + if (parTitle || parTitle === FieldWaiting) { if (parTitle !== FieldWaiting) return parTitle.Data + ".alias"; else return "-waiting-.alias"; } diff --git a/src/fields/ScriptField.ts b/src/fields/ScriptField.ts index 24c1d9b3a..34c8a7544 100644 --- a/src/fields/ScriptField.ts +++ b/src/fields/ScriptField.ts @@ -2,33 +2,34 @@ import { Field, FieldId } from "./Field"; import { Types } from "../server/Message"; import { CompileScript, ScriptOptions, CompiledScript } from "../client/util/Scripting"; import { Server } from "../client/Server"; +import { Without } from "../Utils"; + +export interface SerializableOptions extends Without<ScriptOptions, "capturedVariables"> { + capturedIds: { [id: string]: string }; +} export interface ScriptData { script: string; - options: ScriptOptions; + options: SerializableOptions; } export class ScriptField extends Field { - readonly script: CompiledScript; + private _script?: CompiledScript; + get script(): CompiledScript { + return this._script!; + } + private options?: ScriptData; - constructor(script: CompiledScript, id?: FieldId, save: boolean = true) { + constructor(script?: CompiledScript, id?: FieldId, save: boolean = true) { super(id); - this.script = script; + this._script = script; if (save) { Server.UpdateField(this); } } - static FromJson(id: string, data: ScriptData): ScriptField { - const script = CompileScript(data.script, data.options); - if (!script.compiled) { - throw new Error("Can't compile script"); - } - return new ScriptField(script, id, false); - } - ToScriptString() { return "new ScriptField(...)"; } @@ -45,14 +46,50 @@ export class ScriptField extends Field { throw new Error("Script fields currently can't be updated"); } + static FromJson(id: string, data: ScriptData): ScriptField { + let field = new ScriptField(undefined, id, false); + field.options = data; + return field; + } + + init(callback: (res: Field) => any) { + const options = this.options!; + const keys = Object.keys(options.options.capturedIds); + Server.GetFields(keys).then(fields => { + let captured: { [name: string]: Field } = {}; + keys.forEach(key => captured[options.options.capturedIds[key]] = fields[key]); + const opts: ScriptOptions = { + addReturn: options.options.addReturn, + params: options.options.params, + requiredType: options.options.requiredType, + capturedVariables: captured + }; + const script = CompileScript(options.script, opts); + if (!script.compiled) { + throw new Error("Can't compile script"); + } + this._script = script; + callback(this); + }); + } + ToJson(): { _id: string, type: Types, data: ScriptData } { const { options, originalScript } = this.script; + let capturedIds: { [id: string]: string } = {}; + for (const capt in options.capturedVariables) { + capturedIds[options.capturedVariables[capt].Id] = capt; + } + const opts: SerializableOptions = { + ...options, + capturedIds + }; + delete (opts as any).capturedVariables; return { _id: this.Id, type: Types.Script, data: { script: originalScript, - options + options: opts, }, }; } diff --git a/src/server/Client.ts b/src/server/Client.ts index 02402a5a0..b4c419438 100644 --- a/src/server/Client.ts +++ b/src/server/Client.ts @@ -1,15 +1,12 @@ import { computed } from "mobx"; export class Client { + private _guid: string; + constructor(guid: string) { - this.guid = guid; + this._guid = guid; } - private guid: string; - - @computed - public get GUID(): string { - return this.guid; - } + @computed public get GUID(): string { return this._guid; } }
\ No newline at end of file diff --git a/src/server/database.ts b/src/server/database.ts index 0bc806253..3290edde0 100644 --- a/src/server/database.ts +++ b/src/server/database.ts @@ -1,107 +1,74 @@ import * as mongodb from 'mongodb'; export class Database { + public static DocumentsCollection = 'documents'; public static Instance = new Database(); private MongoClient = mongodb.MongoClient; private url = 'mongodb://localhost:27017/Dash'; + private currentWrites: { [_id: string]: Promise<void> } = {}; private db?: mongodb.Db; constructor() { - this.MongoClient.connect(this.url, (err, client) => { - this.db = client.db(); - }); + this.MongoClient.connect(this.url, (err, client) => this.db = client.db()); } - private currentWrites: { [_id: string]: Promise<void> } = {}; - - public update(id: string, value: any, callback: () => void) { + public update(id: string, value: any, callback: () => void, collectionName = Database.DocumentsCollection) { if (this.db) { - let collection = this.db.collection('documents'); + let collection = this.db.collection(collectionName); const prom = this.currentWrites[id]; - const run = (promise: Promise<void>, resolve?: () => void) => { - collection.updateOne({ _id: id }, { $set: value }, { - upsert: true - }, (err, res) => { - if (err) { - console.log(err.message); - console.log(err.errmsg); - } - // if (res) { - // console.log(JSON.stringify(res.result)); - // } - if (this.currentWrites[id] === promise) { - delete this.currentWrites[id]; - } - if (resolve) { - resolve(); - } - callback(); + let newProm: Promise<void>; + const run = (): Promise<void> => { + return new Promise<void>(resolve => { + collection.updateOne({ _id: id }, { $set: value }, { upsert: true } + , (err, res) => { + if (err) { + console.log(err.message); + console.log(err.errmsg); + } + // if (res) { + // console.log(JSON.stringify(res.result)); + // } + if (this.currentWrites[id] === newProm) { + delete this.currentWrites[id]; + } + resolve(); + callback(); + }); }); }; - if (prom) { - const newProm: Promise<void> = prom.then(() => run(newProm)); - this.currentWrites[id] = newProm; - } else { - const newProm: Promise<void> = new Promise<void>(res => run(newProm, res)); - this.currentWrites[id] = newProm; - } + newProm = prom ? prom.then(run) : run(); + this.currentWrites[id] = newProm; } } - public delete(id: string) { - if (this.db) { - let collection = this.db.collection('documents'); - collection.remove({ _id: id }); - } + public delete(id: string, collectionName = Database.DocumentsCollection) { + this.db && this.db.collection(collectionName).remove({ _id: id }); } - public deleteAll(collectionName: string = 'documents'): Promise<any> { - return new Promise(res => { - if (this.db) { - let collection = this.db.collection(collectionName); - collection.deleteMany({}, res); - } - }); + public deleteAll(collectionName = Database.DocumentsCollection): Promise<any> { + return new Promise(res => + this.db && this.db.collection(collectionName).deleteMany({}, res)); } - public insert(kvpairs: any) { - if (this.db) { - let collection = this.db.collection('documents'); - collection.insertOne(kvpairs, (err: any, res: any) => { - if (err) { - // console.log(err) - return; - } - }); - } + public insert(kvpairs: any, collectionName = Database.DocumentsCollection) { + this.db && this.db.collection(collectionName).insertOne(kvpairs, (err, res) => + err // && console.log(err) + ); } - public getDocument(id: string, fn: (res: any) => void) { - var result: JSON; - if (this.db) { - let collection = this.db.collection('documents'); - collection.findOne({ _id: id }, (err: any, res: any) => { - result = res; - if (!result) { - fn(undefined); - } - fn(result); - }); - } + public getDocument(id: string, fn: (res: any) => void, collectionName = Database.DocumentsCollection) { + this.db && this.db.collection(collectionName).findOne({ _id: id }, (err, result) => + fn(result ? result : undefined)); } - public getDocuments(ids: string[], fn: (res: any) => void) { - if (this.db) { - let collection = this.db.collection('documents'); - let cursor = collection.find({ _id: { "$in": ids } }); - cursor.toArray((err, docs) => { - if (err) { - console.log(err.message); - console.log(err.errmsg); - } - fn(docs); - }); - } + public getDocuments(ids: string[], fn: (res: any) => void, collectionName = Database.DocumentsCollection) { + this.db && this.db.collection(collectionName).find({ _id: { "$in": ids } }).toArray((err, docs) => { + if (err) { + console.log(err.message); + console.log(err.errmsg); + } + fn(docs); + }); } public print() { diff --git a/src/server/index.ts b/src/server/index.ts index b9c7448b4..a6fe6fa2c 100644 --- a/src/server/index.ts +++ b/src/server/index.ts @@ -1,49 +1,45 @@ +import * as bodyParser from 'body-parser'; +import { exec } from 'child_process'; +import * as cookieParser from 'cookie-parser'; import * as express from 'express'; -const app = express(); -import * as webpack from 'webpack'; -import * as wdm from 'webpack-dev-middleware'; -import * as whm from 'webpack-hot-middleware'; -import * as path from 'path'; +import * as session from 'express-session'; +import * as expressValidator from 'express-validator'; import * as formidable from 'formidable'; +import * as fs from 'fs'; +import * as mobileDetect from 'mobile-detect'; +import { ObservableMap } from 'mobx'; import * as passport from 'passport'; -import { MessageStore, Transferable } from "./Message"; -import { Client } from './Client'; +import * as path from 'path'; +import * as request from 'request'; +import * as io from 'socket.io'; import { Socket } from 'socket.io'; +import * as webpack from 'webpack'; +import * as wdm from 'webpack-dev-middleware'; +import * as whm from 'webpack-hot-middleware'; +import { Field, FieldId } from '../fields/Field'; import { Utils } from '../Utils'; -import { ObservableMap } from 'mobx'; -import { FieldId, Field } from '../fields/Field'; +import { getForgot, getLogin, getLogout, getReset, getSignup, postForgot, postLogin, postReset, postSignup } from './authentication/controllers/user_controller'; +import { DashUserModel } from './authentication/models/user_model'; +import { Client } from './Client'; import { Database } from './database'; -import * as io from 'socket.io'; -import { getLogin, postLogin, getSignup, postSignup, getLogout, postReset, getForgot, postForgot, getReset } from './authentication/controllers/user_controller'; +import { MessageStore, Transferable } from "./Message"; +import { RouteStore } from './RouteStore'; +const app = express(); const config = require('../../webpack.config'); const compiler = webpack(config); const port = 1050; // default port to listen const serverPort = 4321; -import * as expressValidator from 'express-validator'; import expressFlash = require('express-flash'); import flash = require('connect-flash'); -import * as bodyParser from 'body-parser'; -import * as session from 'express-session'; -import * as cookieParser from 'cookie-parser'; -import * as mobileDetect from 'mobile-detect'; import c = require("crypto"); const MongoStore = require('connect-mongo')(session); const mongoose = require('mongoose'); -import { DashUserModel } from './authentication/models/user_model'; -import * as fs from 'fs'; -import * as request from 'request'; -import { RouteStore } from './RouteStore'; -import { exec } from 'child_process'; -const download = (url: string, dest: fs.PathLike) => { - request.get(url).pipe(fs.createWriteStream(dest)); -}; +const download = (url: string, dest: fs.PathLike) => request.get(url).pipe(fs.createWriteStream(dest));; const mongoUrl = 'mongodb://localhost:27017/Dash'; mongoose.connect(mongoUrl); -mongoose.connection.on('connected', function () { - console.log("connected"); -}); +mongoose.connection.on('connected', () => console.log("connected")); // SESSION MANAGEMENT AND AUTHENTICATION MIDDLEWARE // ORDER OF IMPORTS MATTERS @@ -54,9 +50,7 @@ app.use(session({ resave: true, cookie: { maxAge: 7 * 24 * 60 * 60 * 1000 }, saveUninitialized: true, - store: new MongoStore({ - url: 'mongodb://localhost:27017/Dash' - }) + store: new MongoStore({ url: 'mongodb://localhost:27017/Dash' }) })); app.use(flash()); @@ -71,9 +65,7 @@ app.use((req, res, next) => { next(); }); -app.get("/hello", (req, res) => { - res.send("<p>Hello</p>"); -}); +app.get("/hello", (req, res) => res.send("<p>Hello</p>")); enum Method { GET, @@ -112,21 +104,17 @@ function addSecureRoute(method: Method, } // STATIC FILE SERVING - -let FieldStore: ObservableMap<FieldId, Field> = new ObservableMap(); - app.use(express.static(__dirname + RouteStore.public)); app.use(RouteStore.images, express.static(__dirname + RouteStore.public)); -app.get("/pull", (req, res) => { +app.get("/pull", (req, res) => exec('"C:\\Program Files\\Git\\git-bash.exe" -c "git pull"', (err, stdout, stderr) => { if (err) { res.send(err.message); return; } res.redirect("/"); - }); -}); + })); // GETTERS @@ -143,11 +131,8 @@ addSecureRoute( Method.GET, (user, res, req) => { let detector = new mobileDetect(req.headers['user-agent'] || ""); - if (detector.mobile() !== null) { - res.sendFile(path.join(__dirname, '../../deploy/mobile/image.html')); - } else { - res.sendFile(path.join(__dirname, '../../deploy/index.html')); - } + let filename = detector.mobile() !== null ? 'mobile/image.html' : 'index.html'; + res.sendFile(path.join(__dirname, '../../deploy/' + filename)); }, undefined, RouteStore.home, @@ -163,12 +148,7 @@ addSecureRoute( addSecureRoute( Method.GET, - (user, res) => { - res.send(JSON.stringify({ - id: user.id, - email: user.email - })); - }, + (user, res) => res.send(JSON.stringify({ id: user.id, email: user.email })), undefined, RouteStore.getCurrUser ); @@ -185,10 +165,9 @@ addSecureRoute( console.log("upload"); form.parse(req, (err, fields, files) => { console.log("parsing"); - let names: any[] = []; + let names: string[] = []; for (const name in files) { - let file = files[name]; - names.push(`/files/` + path.basename(file.path)); + names.push(`/files/` + path.basename(files[name].path)); } res.send(names); }); @@ -218,28 +197,22 @@ app.post(RouteStore.forgot, postForgot); app.get(RouteStore.reset, getReset); app.post(RouteStore.reset, postReset); -app.use(RouteStore.corsProxy, (req, res) => { - req.pipe(request(req.url.substring(1))).pipe(res); -}); +app.use(RouteStore.corsProxy, (req, res) => + req.pipe(request(req.url.substring(1))).pipe(res)); -app.get(RouteStore.delete, (req, res) => { - deleteFields().then(() => res.redirect(RouteStore.home)); -}); +app.get(RouteStore.delete, (req, res) => + deleteFields().then(() => res.redirect(RouteStore.home))); -app.get(RouteStore.deleteAll, (req, res) => { - deleteAll().then(() => res.redirect(RouteStore.home)); -}); +app.get(RouteStore.deleteAll, (req, res) => + deleteAll().then(() => res.redirect(RouteStore.home))); -app.use(wdm(compiler, { - publicPath: config.output.publicPath -})); +app.use(wdm(compiler, { publicPath: config.output.publicPath })); app.use(whm(compiler)); // start the Express server -app.listen(port, () => { - console.log(`server started at http://localhost:${port}`); -}); +app.listen(port, () => + console.log(`server started at http://localhost:${port}`)); const server = io(); interface Map { @@ -274,14 +247,8 @@ function barReceived(guid: String) { } function getField([id, callback]: [string, (result: any) => void]) { - Database.Instance.getDocument(id, (result: any) => { - if (result) { - callback(result); - } - else { - callback(undefined); - } - }); + Database.Instance.getDocument(id, (result: any) => + callback(result ? result : undefined)); } function getFields([ids, callback]: [string[], (result: any) => void]) { @@ -289,9 +256,8 @@ function getFields([ids, callback]: [string[], (result: any) => void]) { } function setField(socket: Socket, newValue: Transferable) { - Database.Instance.update(newValue._id, newValue, () => { - socket.broadcast.emit(MessageStore.SetField.Message, newValue); - }); + Database.Instance.update(newValue._id, newValue, () => + socket.broadcast.emit(MessageStore.SetField.Message, newValue)); } server.listen(serverPort); |