diff options
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | src/Utils.ts | 4 | ||||
-rw-r--r-- | src/client/Server.ts | 2 | ||||
-rw-r--r-- | src/client/util/UndoManager.ts | 57 | ||||
-rw-r--r-- | src/client/views/EditableView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/InkingCanvas.tsx | 2 | ||||
-rw-r--r-- | src/client/views/collections/CollectionFreeFormView.scss | 35 | ||||
-rw-r--r-- | src/client/views/collections/CollectionFreeFormView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/collections/CollectionTreeView.scss | 5 | ||||
-rw-r--r-- | src/client/views/collections/CollectionTreeView.tsx | 156 | ||||
-rw-r--r-- | src/client/views/collections/CollectionView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/DocumentView.tsx | 4 | ||||
-rw-r--r-- | src/client/views/nodes/KeyValuePair.scss | 12 | ||||
-rw-r--r-- | src/client/views/nodes/KeyValuePair.tsx | 14 |
14 files changed, 168 insertions, 137 deletions
diff --git a/package.json b/package.json index e4187c17d..c3306a4cb 100644 --- a/package.json +++ b/package.json @@ -141,7 +141,7 @@ "react-dom": "^16.8.4", "react-golden-layout": "^1.0.6", "react-image-lightbox": "^5.1.0", - "react-jsx-parser": "^1.14.1", + "react-jsx-parser": "^1.15.0", "react-measure": "^2.2.4", "react-mosaic": "0.0.20", "react-pdf": "^4.0.2", diff --git a/src/Utils.ts b/src/Utils.ts index d4b7da52c..a4db94809 100644 --- a/src/Utils.ts +++ b/src/Utils.ts @@ -52,4 +52,6 @@ export class Utils { public static AddServerHandlerCallback<T>(socket: Socket, message: Message<T>, handler: (args: [T, (res: any) => any]) => any) { socket.on(message.Message, (arg: T, fn: (res: any) => any) => handler([arg, fn])); } -}
\ No newline at end of file +} + +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/Server.ts b/src/client/Server.ts index bbdc27397..7d882c76d 100644 --- a/src/client/Server.ts +++ b/src/client/Server.ts @@ -39,7 +39,7 @@ export class Server { reaction(() => { return this.ClientFieldsCached.get(fieldid); }, (field, reaction) => { - if (field !== "<Waiting>") { + if (field !== FieldWaiting) { reaction.dispose() callback(field) } diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts index 46ad558f3..6d1b2f1b8 100644 --- a/src/client/util/UndoManager.ts +++ b/src/client/util/UndoManager.ts @@ -1,4 +1,14 @@ import { observable, action } from "mobx"; +import 'source-map-support/register' +import { Without } from "../../Utils"; + +function getBatchName(target: any, key: string | symbol): string { + let keyName = key.toString(); + if (target && target.constructor && target.constructor.name) { + return `${target.constructor.name}.${keyName}`; + } + return keyName; +} function propertyDecorator(target: any, key: string | symbol) { Object.defineProperty(target, key, { @@ -13,11 +23,11 @@ function propertyDecorator(target: any, key: string | symbol) { writable: true, configurable: true, value: function (...args: any[]) { + let batch = UndoManager.StartBatch(getBatchName(target, key)); try { - UndoManager.StartBatch(); return value.apply(this, args); } finally { - UndoManager.EndBatch(); + batch.end(); } } }) @@ -32,11 +42,11 @@ export function undoBatch(target: any, key: string | symbol, descriptor?: TypedP const oldFunction = descriptor.value; descriptor.value = function (...args: any[]) { + let batch = UndoManager.StartBatch(getBatchName(target, key)); try { - UndoManager.StartBatch() return oldFunction.apply(this, args) } finally { - UndoManager.EndBatch() + batch.end(); } } @@ -70,26 +80,53 @@ export namespace UndoManager { return redoStack.length > 0; } - export function StartBatch(): void { + let openBatches: Batch[] = []; + export function GetOpenBatches(): Without<Batch, 'end'>[] { + return openBatches; + } + export class Batch { + private disposed: boolean = false; + + constructor(readonly batchName: string) { + openBatches.push(this); + } + + private dispose = (cancel: boolean) => { + if (this.disposed) { + throw new Error("Cannot dispose an already disposed batch"); + } + this.disposed = true; + openBatches.splice(openBatches.indexOf(this)); + EndBatch(cancel); + } + + end = () => { this.dispose(false); } + cancel = () => { this.dispose(true); } + } + + export function StartBatch(batchName: string): Batch { batchCounter++; if (batchCounter > 0) { currentBatch = []; } + return new Batch(batchName); } - export const EndBatch = action(() => { + const EndBatch = action((cancel: boolean = false) => { batchCounter--; if (batchCounter === 0 && currentBatch && currentBatch.length) { - undoStack.push(currentBatch); + if (!cancel) { + undoStack.push(currentBatch); + } redoStack.length = 0; currentBatch = undefined; } }) - export function RunInBatch(fn: () => void) { - StartBatch(); + export function RunInBatch(fn: () => void, batchName: string) { + let batch = StartBatch(batchName); fn(); - EndBatch(); + batch.end(); } export const Undo = action(() => { diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx index 98a6ed1ba..579d6e6ad 100644 --- a/src/client/views/EditableView.tsx +++ b/src/client/views/EditableView.tsx @@ -1,6 +1,6 @@ import React = require('react') import { observer } from 'mobx-react'; -import { observable, action } from 'mobx'; +import { observable, action, trace } from 'mobx'; import "./EditableView.scss" export interface EditableProps { @@ -52,7 +52,7 @@ export class EditableView extends React.Component<EditableProps> { } else { return ( <div className="editableView-container-editing" style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }} - onClick={action(() => this.editing = true)}> + onClick={action(() => this.editing = true)} > {this.props.contents} </div> ) diff --git a/src/client/views/InkingCanvas.tsx b/src/client/views/InkingCanvas.tsx index 8d0121035..36a8834a0 100644 --- a/src/client/views/InkingCanvas.tsx +++ b/src/client/views/InkingCanvas.tsx @@ -137,7 +137,7 @@ export class InkingCanvas extends React.Component<InkCanvasProps> { // get data from server // let inkField = this.props.Document.GetT(KeyStore.Ink, InkField); - // if (!inkField || inkField == "<Waiting>") { + // if (!inkField || inkField == FieldWaiting) { // return (<div className="inking-canvas" style={canvasStyle} // onMouseDown={this.handleMouseDown} onMouseMove={this.handleMouseMove} > // <svg> diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss index 0e0570397..bdc597a25 100644 --- a/src/client/views/collections/CollectionFreeFormView.scss +++ b/src/client/views/collections/CollectionFreeFormView.scss @@ -7,6 +7,9 @@ width: 100%; } + .inking-canvas { + transform-origin: 50000px 50000px; + } //nested freeform views // .collectionfreeformview-container { // background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px), @@ -14,22 +17,25 @@ // background-size: 30px 30px; // } + box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; border: 0px solid $light-color-secondary; border-radius: $border-radius; box-sizing: border-box; position: relative; + overflow: hidden; top: 0; left: 0; width: 100%; height: 100%; - overflow: hidden; - box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw; .collectionfreeformview { position: absolute; top: 0; left: 0; width: 100%; height: 100%; + .inking-canvas { + transform-origin: 50000px 50000px; + } } } .collectionfreeformview-overlay { @@ -41,33 +47,32 @@ background: $light-color-secondary; } + .inking-canvas { + transform-origin: 50000px 50000px; + } + opacity: 0.99; - position:absolute; border: 0px solid transparent; border-radius: $border-radius; - overflow: hidden; box-sizing: border-box; + position:relative; + overflow: hidden; top: 0; left: 0; width: 100%; height: 100%; .collectionfreeformview { - .collectionfreeformview > .jsx-parser{ - position:absolute; - height: 100%; - } - .formattedTextBox-cont { - background:yellow; - } - - // overflow: hidden; - // border-style: solid; - // box-sizing: border-box; position: absolute; top: 0; left: 0; width: 100%; height: 100%; + .inking-canvas { + transform-origin: 50000px 50000px; + } + .formattedTextBox-cont { + background:yellow; + } } } diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx index 8bf4a7539..da9f7b392 100644 --- a/src/client/views/collections/CollectionFreeFormView.tsx +++ b/src/client/views/collections/CollectionFreeFormView.tsx @@ -219,13 +219,13 @@ export class CollectionFreeFormView extends CollectionViewBase { @computed get backgroundLayout(): string | undefined { let field = this.props.Document.GetT(KeyStore.BackgroundLayout, TextField); - if (field && field !== "<Waiting>") { + if (field && field !== FieldWaiting) { return field.Data; } } @computed get overlayLayout(): string | undefined { let field = this.props.Document.GetT(KeyStore.OverlayLayout, TextField); - if (field && field !== "<Waiting>") { + if (field && field !== FieldWaiting) { return field.Data; } } diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index fa0f1c761..5a14aa54d 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -1,6 +1,8 @@ @import "../global_variables"; #body { padding: 20px; + padding-left: 10px; + padding-right: 0px; background: $light-color-secondary; font-size: 13px; overflow: scroll; @@ -49,6 +51,7 @@ li { .docContainer:hover { .delete-button { display: inline; + width: auto; } } @@ -57,5 +60,5 @@ li { float: right; margin-left: 15px; margin-top: 3px; - display: none; + display: inline; }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index f9da759fd..6edd90215 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -1,20 +1,19 @@ +import { IconProp, library } from '@fortawesome/fontawesome-svg-core'; +import { faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons'; +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { action, observable } from "mobx"; import { observer } from "mobx-react"; -import { CollectionViewBase } from "./CollectionViewBase"; import { Document } from "../../../fields/Document"; +import { FieldWaiting } from "../../../fields/Field"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; -import React = require("react") -import { TextField } from "../../../fields/TextField"; -import { observable, action } from "mobx"; -import "./CollectionTreeView.scss"; -import { EditableView } from "../EditableView"; import { setupDrag } from "../../util/DragManager"; -import { FieldWaiting } from "../../../fields/Field"; -import { COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { EditableView } from "../EditableView"; +import "./CollectionTreeView.scss"; +import { CollectionView, COLLECTION_BORDER_WIDTH } from "./CollectionView"; +import { CollectionViewBase } from "./CollectionViewBase"; +import React = require("react") -import { library } from '@fortawesome/fontawesome-svg-core'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faTrashAlt, faCaretRight, faCaretDown } from '@fortawesome/free-solid-svg-icons'; export interface TreeViewProps { document: Document; @@ -37,13 +36,9 @@ library.add(faCaretRight); */ class TreeView extends React.Component<TreeViewProps> { - @observable - collapsed: boolean = false; - - delete = () => { - this.props.deleteDoc(this.props.document); - } + @observable _collapsed: boolean = true; + delete = () => this.props.deleteDoc(this.props.document); @action remove = (document: Document) => { @@ -54,93 +49,63 @@ class TreeView extends React.Component<TreeViewProps> { } renderBullet(type: BulletType) { - let onClicked = action(() => this.collapsed = !this.collapsed); - + let onClicked = action(() => this._collapsed = !this._collapsed); + let bullet: IconProp | undefined = undefined; switch (type) { - case BulletType.Collapsed: - return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-right" /></div> - case BulletType.Collapsible: - return <div className="bullet" onClick={onClicked}><FontAwesomeIcon icon="caret-down" /></div> - case BulletType.List: - return <div className="bullet"></div> + case BulletType.Collapsed: bullet = "caret-right"; break; + case BulletType.Collapsible: bullet = "caret-down"; break; } + return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div> } /** * Renders the EditableView title element for placement into the tree. */ renderTitle() { - let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField); - - // if the title hasn't loaded, immediately return the div - if (!title || title === "<Waiting>") { - return <div key={this.props.document.Id}></div>; - } - - return <div className="docContainer"> <EditableView - display={"inline"} - contents={title.Data} - height={36} GetValue={() => { - let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField); - if (title && title !== "<Waiting>") - return title.Data; - return ""; - }} SetValue={(value: string) => { - this.props.document.SetData(KeyStore.Title, value, TextField); - return true; - }} /> - <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div> - </div > + let reference = React.createRef<HTMLDivElement>(); + let onItemDown = setupDrag(reference, () => this.props.document, (containingCollection: CollectionView) => this.props.deleteDoc(this.props.document)); + let editableView = (titleString: string) => + (<EditableView + display={"inline"} + contents={titleString} + height={36} + GetValue={() => this.props.document.Title} + SetValue={(value: string) => { + this.props.document.SetText(KeyStore.Title, value); + return true; + }} + />); + return ( + <div key={this.props.document.Id} className="docContainer" ref={reference} onPointerDown={onItemDown}> + {editableView(this.props.document.Title)} + <div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div> + </div >) } render() { - var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField); - - let reference = React.createRef<HTMLDivElement>(); - let onItemDown = setupDrag(reference, () => this.props.document); - let titleElement = this.renderTitle(); + let bulletType = BulletType.List; + let childElements: JSX.Element | undefined = undefined; - // check if this document is a collection - if (children && children !== FieldWaiting) { - let subView; - - // if uncollapsed, then add the children elements - if (!this.collapsed) { - // render all children elements - let childrenElement = (children.Data.map(value => - <TreeView document={value} deleteDoc={this.remove} />) - ) - subView = - <li className="collection-child" key={this.props.document.Id} > - {this.renderBullet(BulletType.Collapsible)} - {titleElement} - <ul key={this.props.document.Id}> - {childrenElement} - </ul> - </li> - } else { - subView = <li className="collection-child" key={this.props.document.Id}> - {this.renderBullet(BulletType.Collapsed)} - {titleElement} - </li> + var children = this.props.document.GetT<ListField<Document>>(KeyStore.Data, ListField); + if (children && children !== FieldWaiting) { // add children for a collection + if (!this._collapsed) { + bulletType = BulletType.Collapsible; + childElements = <ul key={this.props.document.Id}> + {children.Data.map(value => <TreeView key={value.Id} document={value} deleteDoc={this.remove} />)} + </ul> } - - return <div className="treeViewItem-container" onPointerDown={onItemDown} ref={reference}> - {subView} - </div> - } - - // otherwise this is a normal leaf node - else { - return <li key={this.props.document.Id}> - {this.renderBullet(BulletType.List)} - {titleElement} - </li>; + else bulletType = BulletType.Collapsed; } + return <div className="treeViewItem-container" > + <li className="collection-child" key={this.props.document.Id}> + {this.renderBullet(bulletType)} + {this.renderTitle()} + {childElements ? childElements : (null)} + </li> + </div> } } - @observer export class CollectionTreeView extends CollectionViewBase { @@ -153,12 +118,6 @@ export class CollectionTreeView extends CollectionViewBase { } render() { - let titleStr = ""; - let title = this.props.Document.GetT<TextField>(KeyStore.Title, TextField); - if (title && title !== FieldWaiting) { - titleStr = title.Data; - } - var children = this.props.Document.GetT<ListField<Document>>(KeyStore.Data, ListField); let childrenElement = !children || children === FieldWaiting ? (null) : (children.Data.map(value => @@ -168,12 +127,13 @@ export class CollectionTreeView extends CollectionViewBase { return ( <div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}> <div className="coll-title"> - <EditableView contents={titleStr} + <EditableView + contents={this.props.Document.Title} display={"inline"} - height={72} GetValue={() => { - return this.props.Document.Title; - }} SetValue={(value: string) => { - this.props.Document.SetData(KeyStore.Title, value, TextField); + height={72} + GetValue={() => this.props.Document.Title} + SetValue={(value: string) => { + this.props.Document.SetText(KeyStore.Title, value); return true; }} /> </div> diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index d4afc69cf..7e1d31018 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -12,7 +12,7 @@ import { CollectionDockingView } from "./CollectionDockingView"; import { CollectionSchemaView } from "./CollectionSchemaView"; import { CollectionViewProps } from "./CollectionViewBase"; import { CollectionTreeView } from "./CollectionTreeView"; -import { Field, FieldId } from "../../../fields/Field"; +import { Field, FieldId, FieldWaiting } from "../../../fields/Field"; import { Main } from "../Main"; export enum CollectionViewType { @@ -86,7 +86,7 @@ export class CollectionView extends React.Component<CollectionViewProps> { get collectionViewType(): CollectionViewType { let Document = this.props.Document; let viewField = Document.GetT(KeyStore.ViewType, NumberField); - if (viewField === "<Waiting>") { + if (viewField === FieldWaiting) { return CollectionViewType.Invalid; } else if (viewField) { return viewField.Data; diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index 9e34b2b60..fec451b09 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -1,7 +1,7 @@ import { action, computed, IReactionDisposer, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; import { Document } from "../../../fields/Document"; -import { Field, Opt } from "../../../fields/Field"; +import { Field, Opt, FieldWaiting } from "../../../fields/Field"; import { Key } from "../../../fields/Key"; import { KeyStore } from "../../../fields/KeyStore"; import { ListField } from "../../../fields/ListField"; @@ -315,7 +315,7 @@ export class DocumentView extends React.Component<DocumentViewProps> { return (null); } let lkeys = this.props.Document.GetT(KeyStore.LayoutKeys, ListField); - if (!lkeys || lkeys === "<Waiting>") { + if (!lkeys || lkeys === FieldWaiting) { return <p>Error loading layout keys</p>; } var scaling = this.props.ContentScaling(); diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss new file mode 100644 index 000000000..64e871e1c --- /dev/null +++ b/src/client/views/nodes/KeyValuePair.scss @@ -0,0 +1,12 @@ +@import "../global_variables"; + +.container{ + display: flex; + flex-direction: row; + flex-wrap: nowrap; + justify-content: space-between; +} + +.delete{ + 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 111f85a05..7ed5ee272 100644 --- a/src/client/views/nodes/KeyValuePair.tsx +++ b/src/client/views/nodes/KeyValuePair.tsx @@ -1,5 +1,6 @@ import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app import "./KeyValueBox.scss"; +import "./KeyValuePair.scss"; import React = require("react") import { FieldViewProps, FieldView } from './FieldView'; import { Opt, Field } from '../../../fields/Field'; @@ -55,7 +56,18 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> { ); return ( <tr className={this.props.rowStyle}> - <td>{this.key.Name}</td> + {/* <button>X</button> */} + <td> + <div className="container"> + <div>{this.key.Name}</div> + <button className="delete" onClick={() => { + let field = props.doc.Get(props.fieldKey); + if (field && field instanceof Field) { + props.doc.Set(props.fieldKey, undefined); + } + }}>X</button> + </div> + </td> <td><EditableView contents={contents} height={36} GetValue={() => { let field = props.doc.Get(props.fieldKey); if (field && field instanceof Field) { |