aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--package.json2
-rw-r--r--src/Utils.ts4
-rw-r--r--src/client/Server.ts2
-rw-r--r--src/client/util/UndoManager.ts57
-rw-r--r--src/client/views/EditableView.tsx4
-rw-r--r--src/client/views/InkingCanvas.tsx2
-rw-r--r--src/client/views/collections/CollectionFreeFormView.scss35
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx4
-rw-r--r--src/client/views/collections/CollectionTreeView.scss5
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx156
-rw-r--r--src/client/views/collections/CollectionView.tsx4
-rw-r--r--src/client/views/nodes/DocumentView.tsx4
-rw-r--r--src/client/views/nodes/KeyValuePair.scss12
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx14
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) {