aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/DocumentDecorations.scss34
-rw-r--r--src/DocumentDecorations.tsx178
-rw-r--r--src/Main.tsx2
-rw-r--r--src/documents/Documents.ts2
-rw-r--r--src/util/SelectionManager.ts39
-rw-r--r--src/viewmodels/DocumentViewModel.ts10
-rw-r--r--src/views/freeformcanvas/CollectionFreeFormView.tsx10
-rw-r--r--src/views/freeformcanvas/FreeFormCanvas.tsx4
-rw-r--r--src/views/nodes/DocumentView.tsx57
9 files changed, 325 insertions, 11 deletions
diff --git a/src/DocumentDecorations.scss b/src/DocumentDecorations.scss
new file mode 100644
index 000000000..fff4d201a
--- /dev/null
+++ b/src/DocumentDecorations.scss
@@ -0,0 +1,34 @@
+#documentDecorations-container {
+ position: absolute;
+ z-index: 1000;
+ display: grid;
+ grid-template-rows: 20px 1fr 20px;
+ grid-template-columns: 20px 1fr 20px;
+ pointer-events: none;
+}
+
+#documentDecorations-centerCont {
+ background: none;
+}
+
+.documentDecorations-resizer {
+ pointer-events: auto;
+ background: lightblue;
+ opacity: 0.8;
+}
+
+#documentDecorations-topLeftResizer, #documentDecorations-bottomRightResizer {
+ cursor: nwse-resize;
+}
+
+#documentDecorations-topRightResizer, #documentDecorations-bottomLeftResizer {
+ cursor: nesw-resize;
+}
+
+#documentDecorations-topResizer, #documentDecorations-bottomResizer {
+ cursor: ns-resize;
+}
+
+#documentDecorations-leftResizer, #documentDecorations-rightResizer {
+ cursor: ew-resize;
+} \ No newline at end of file
diff --git a/src/DocumentDecorations.tsx b/src/DocumentDecorations.tsx
new file mode 100644
index 000000000..d7466dcac
--- /dev/null
+++ b/src/DocumentDecorations.tsx
@@ -0,0 +1,178 @@
+import { observable, computed } from "mobx";
+import React = require("react");
+import { DocumentView } from "./views/nodes/DocumentView";
+import { SelectionManager } from "./util/SelectionManager";
+import { observer } from "mobx-react";
+import './DocumentDecorations.scss'
+
+@observer
+export class DocumentDecorations extends React.Component {
+ static Instance: DocumentDecorations
+ constructor(props: Readonly<{}>) {
+ super(props)
+
+ DocumentDecorations.Instance = this
+ }
+
+ get x(): number {
+ let left = Number.MAX_VALUE;
+ SelectionManager.SelectedDocuments().forEach(element => {
+ if (element.mainCont.current !== null) {
+ left = Math.min(element.mainCont.current.getBoundingClientRect().left, left)
+ }
+ });
+ return left;
+ }
+
+ get y(): number {
+ let top = Number.MAX_VALUE;
+ SelectionManager.SelectedDocuments().forEach(element => {
+ if (element.mainCont.current !== null) {
+ top = Math.min(element.mainCont.current.getBoundingClientRect().top, top)
+ }
+ });
+ return top;
+ }
+
+ get height(): number {
+ return (SelectionManager.SelectedDocuments().reduce((bottom, element) => {
+ if (element.mainCont.current !== null) {
+ return Math.max(element.mainCont.current.getBoundingClientRect().bottom, bottom)
+ }
+ else {
+ return bottom
+ }
+ }, Number.MIN_VALUE)) - this.y;
+ }
+
+ get width(): number {
+ let right = Number.MIN_VALUE;
+ SelectionManager.SelectedDocuments().forEach(element => {
+ if (element.mainCont.current !== null) {
+ right = Math.max(element.mainCont.current.getBoundingClientRect().right, right)
+ }
+ });
+ return right - this.x;
+ }
+
+ private _resizer = ""
+ private _isPointerDown = false;
+ @observable private _opacity = 1;
+
+ @computed
+ get opacity(): number {
+ return this._opacity
+ }
+
+ set opacity(o: number) {
+ this._opacity = Math.min(Math.max(0, o), 1)
+ }
+
+ onPointerDown = (e: React.PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ this._isPointerDown = true;
+ this._resizer = e.currentTarget.id;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ }
+
+ onPointerMove = (e: PointerEvent): void => {
+ e.stopPropagation();
+ e.preventDefault();
+ if (!this._isPointerDown) {
+ return;
+ }
+
+ let dX = 0, dY = 0, dW = 0, dH = 0;
+
+ switch (this._resizer) {
+ case "":
+ break;
+ case "documentDecorations-topLeftResizer":
+ dX = -1
+ dY = -1
+ dW = -(e.movementX)
+ dH = -(e.movementY)
+ break;
+ case "documentDecorations-topRightResizer":
+ dW = e.movementX
+ dY = -1
+ dH = -(e.movementY)
+ break;
+ case "documentDecorations-topResizer":
+ dY = -1
+ dH = -(e.movementY)
+ break;
+ case "documentDecorations-bottomLeftResizer":
+ dX = -1
+ dW = -(e.movementX)
+ dH = e.movementY
+ break;
+ case "documentDecorations-bottomRightResizer":
+ dW = e.movementX
+ dH = e.movementY
+ break;
+ case "documentDecorations-bottomResizer":
+ dH = e.movementY
+ break;
+ case "documentDecorations-leftResizer":
+ dX = -1
+ dW = -(e.movementX)
+ break;
+ case "documentDecorations-rightResizer":
+ dW = e.movementX
+ break;
+ }
+
+ SelectionManager.SelectedDocuments().forEach(element => {
+ if (element.mainCont.current !== null) {
+ let scale = element.width / element.mainCont.current.getBoundingClientRect().width;
+ let actualdW = Math.max(element.width + (dW * scale), 20);
+ let actualdH = Math.max(element.height + (dH * scale), 20);
+ element.x += dX * (actualdW - element.width);
+ element.y += dY * (actualdH - element.height);
+ element.width = actualdW;
+ element.height = actualdH;
+ }
+ })
+
+ this.forceUpdate()
+ }
+
+ onPointerUp = (e: PointerEvent): void => {
+ e.stopPropagation();
+ if (e.button === 0) {
+ e.preventDefault();
+ this._isPointerDown = false;
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ }
+ }
+
+ render() {
+ return (
+ <div id="documentDecorations-container" style={{
+ width: `${this.width + 40}px`,
+ height: `${this.height + 40}px`,
+ left: this.x - 20,
+ top: this.y - 20,
+ opacity: this.opacity
+ }}>
+ <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>
+ <div id="documentDecorations-topRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-leftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-centerCont"></div>
+ <div id="documentDecorations-rightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-bottomLeftResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+ <div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
+
+ </div>
+ )
+ }
+} \ No newline at end of file
diff --git a/src/Main.tsx b/src/Main.tsx
index 3d0cf052f..9ce1dd4e9 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -12,6 +12,7 @@ import { Document } from './fields/Document';
import { configure, runInAction } from 'mobx';
import { NodeStore } from './stores/NodeStore';
import { Documents } from './documents/Documents';
+import { DocumentDecorations } from './DocumentDecorations';
configure({
enforceActions: "observed"
@@ -21,6 +22,7 @@ const mainNodeCollection = new NodeCollectionStore();
ReactDOM.render((
<div>
<h1>Dash Web</h1>
+ <DocumentDecorations />
<FreeFormCanvas store={mainNodeCollection} />
</div>), document.getElementById('root'));
diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts
index beee44bee..cc08f4123 100644
--- a/src/documents/Documents.ts
+++ b/src/documents/Documents.ts
@@ -77,7 +77,7 @@ export namespace Documents {
collectionProto.SetField(KeyStore.Y, new NumberField(0));
collectionProto.SetField(KeyStore.Width, new NumberField(300));
collectionProto.SetField(KeyStore.Height, new NumberField(300));
- collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView doc={doc} fieldKey={DataKey}/>'));
+ collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView doc={doc} fieldKey={DataKey} isSelected={isSelected}/>'));
collectionProto.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
}
return collectionProto;
diff --git a/src/util/SelectionManager.ts b/src/util/SelectionManager.ts
new file mode 100644
index 000000000..2a63248c4
--- /dev/null
+++ b/src/util/SelectionManager.ts
@@ -0,0 +1,39 @@
+import { DocumentView } from "../views/nodes/DocumentView";
+import { observable, action } from "mobx";
+
+export namespace SelectionManager {
+ class Manager {
+ @observable
+ SelectedDocuments: Array<DocumentView> = [];
+
+ @action
+ SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
+ // if doc is not in SelectedDocuments, add it
+ if (!ctrlPressed) {
+ manager.SelectedDocuments = [];
+ }
+
+ if (manager.SelectedDocuments.indexOf(doc) === -1) {
+ manager.SelectedDocuments.push(doc)
+ }
+ }
+ }
+
+ const manager = new Manager;
+
+ export function SelectDoc(doc: DocumentView, ctrlPressed: boolean): void {
+ manager.SelectDoc(doc, ctrlPressed)
+ }
+
+ export function IsSelected(doc: DocumentView): boolean {
+ return manager.SelectedDocuments.indexOf(doc) !== -1;
+ }
+
+ export function DeselectAll(): void {
+ manager.SelectedDocuments = []
+ }
+
+ export function SelectedDocuments(): Array<DocumentView> {
+ return manager.SelectedDocuments;
+ }
+} \ No newline at end of file
diff --git a/src/viewmodels/DocumentViewModel.ts b/src/viewmodels/DocumentViewModel.ts
index 008275f3c..f3154db54 100644
--- a/src/viewmodels/DocumentViewModel.ts
+++ b/src/viewmodels/DocumentViewModel.ts
@@ -8,4 +8,14 @@ export class DocumentViewModel {
get Doc(): Document {
return this.doc;
}
+
+ private _selected = false;
+
+ get Selected() : boolean {
+ return this._selected;
+ }
+
+ set Selected(isSelected: boolean) {
+ this._selected = isSelected;
+ }
} \ No newline at end of file
diff --git a/src/views/freeformcanvas/CollectionFreeFormView.tsx b/src/views/freeformcanvas/CollectionFreeFormView.tsx
index d5343536d..4e9e0cd21 100644
--- a/src/views/freeformcanvas/CollectionFreeFormView.tsx
+++ b/src/views/freeformcanvas/CollectionFreeFormView.tsx
@@ -9,10 +9,12 @@ import { DocumentView } from "../nodes/DocumentView";
import { ListField } from "../../fields/ListField";
import { NumberField } from "../../fields/NumberField";
import { SSL_OP_SINGLE_DH_USE } from "constants";
+import { DocumentDecorations } from "../../DocumentDecorations";
interface IProps {
fieldKey: Key;
doc: Document;
+ isSelected: boolean;
}
@observer
@@ -26,6 +28,10 @@ export class CollectionFreeFormView extends React.Component<IProps> {
@action
onPointerDown = (e: React.PointerEvent): void => {
+ if (!this.props.isSelected) {
+ return;
+ }
+
e.stopPropagation();
if (e.button === 2) {
this._isPointerDown = true;
@@ -54,10 +60,14 @@ export class CollectionFreeFormView extends React.Component<IProps> {
return;
}
const { doc } = this.props;
+
let x = doc.GetFieldValue(KeyStore.PanX, NumberField, Number(0));
let y = doc.GetFieldValue(KeyStore.PanY, NumberField, Number(0));
doc.SetFieldValue(KeyStore.PanX, x + e.movementX, NumberField);
doc.SetFieldValue(KeyStore.PanY, y + e.movementY, NumberField);
+
+
+ DocumentDecorations.Instance.forceUpdate()
}
@action
diff --git a/src/views/freeformcanvas/FreeFormCanvas.tsx b/src/views/freeformcanvas/FreeFormCanvas.tsx
index 9ef5ab8f7..de5e88fa1 100644
--- a/src/views/freeformcanvas/FreeFormCanvas.tsx
+++ b/src/views/freeformcanvas/FreeFormCanvas.tsx
@@ -10,6 +10,7 @@ import {DocumentView} from "../nodes/DocumentView";
import {TextField} from "../../fields/TextField";
import {ListField} from "../../fields/ListField";
import {Field} from "../../fields/Field";
+import { SelectionManager } from "../../util/SelectionManager";
interface IProps {
store: NodeCollectionStore;
@@ -34,6 +35,8 @@ export class FreeFormCanvas extends React.Component<IProps> {
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
}
+
+ SelectionManager.DeselectAll()
}
@action
@@ -57,6 +60,7 @@ export class FreeFormCanvas extends React.Component<IProps> {
if (!this._isPointerDown) {
return;
}
+
this.props.store.X += e.movementX;
this.props.store.Y += e.movementY;
}
diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx
index f955a8c39..e37172943 100644
--- a/src/views/nodes/DocumentView.tsx
+++ b/src/views/nodes/DocumentView.tsx
@@ -1,6 +1,6 @@
import { observer } from "mobx-react";
import React = require("react");
-import { computed } from "mobx";
+import { computed, observable } from "mobx";
import { KeyStore, Key } from "../../fields/Key";
import { NumberField } from "../../fields/NumberField";
import { TextField } from "../../fields/TextField";
@@ -10,6 +10,8 @@ import { FieldTextBox } from "../nodes/FieldTextBox"
import { FreeFormCanvas } from "../freeformcanvas/FreeFormCanvas"
import { CollectionFreeFormView } from "../freeformcanvas/CollectionFreeFormView"
import "./NodeView.scss"
+import { SelectionManager } from "../../util/SelectionManager";
+import { DocumentDecorations } from "../../DocumentDecorations";
const JsxParser = require('react-jsx-parser').default;//TODO Why does this need to be imported like this?
interface IProps {
@@ -18,6 +20,12 @@ interface IProps {
@observer
export class DocumentView extends React.Component<IProps> {
+ private _mainCont = React.createRef<HTMLDivElement>();
+
+ get mainCont(): React.RefObject<HTMLDivElement> {
+ return this._mainCont
+ }
+
@computed
get x(): number {
return this.props.dvm.Doc.GetFieldValue(KeyStore.X, NumberField, Number(0));
@@ -46,11 +54,19 @@ export class DocumentView extends React.Component<IProps> {
return this.props.dvm.Doc.GetFieldValue(KeyStore.Width, NumberField, Number(0));
}
+ set width(w: number) {
+ this.props.dvm.Doc.SetFieldValue(KeyStore.Width, w, NumberField)
+ }
+
@computed
get height(): number {
return this.props.dvm.Doc.GetFieldValue(KeyStore.Height, NumberField, Number(0));
}
+ set height(h: number) {
+ this.props.dvm.Doc.SetFieldValue(KeyStore.Height, h, NumberField)
+ }
+
@computed
get layout(): string {
return this.props.dvm.Doc.GetFieldValue(KeyStore.Layout, TextField, String("<p>Error loading layout data</p>"));
@@ -66,6 +82,11 @@ export class DocumentView extends React.Component<IProps> {
return this.props.dvm.Doc.GetFieldValue(KeyStore.LayoutFields, ListField, new Array<Key>());
}
+ @computed
+ get selected() : boolean {
+ return SelectionManager.IsSelected(this)
+ }
+
private _isPointerDown = false;
onPointerDown = (e: React.PointerEvent): void => {
@@ -77,6 +98,7 @@ export class DocumentView extends React.Component<IProps> {
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
}
+ SelectionManager.SelectDoc(this, e.ctrlKey)
}
onPointerUp = (e: PointerEvent): void => {
@@ -86,6 +108,9 @@ export class DocumentView extends React.Component<IProps> {
this._isPointerDown = false;
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
+ console.log(this.x);
+ console.log(this.y)
+ DocumentDecorations.Instance.opacity = 1
}
}
@@ -97,12 +122,21 @@ export class DocumentView extends React.Component<IProps> {
}
this.x += e.movementX;
this.y += e.movementY;
+ DocumentDecorations.Instance.opacity = 0
+ }
+
+ onDragStart = (e: React.DragEvent<HTMLDivElement>): void => {
+ if (this.mainCont.current !== null) {
+ this.mainCont.current.style.opacity = "0";
+ // e.dataTransfer.setDragImage()
+ }
}
render() {
let doc = this.props.dvm.Doc;
let bindings: any = {
- doc: doc
+ doc: doc,
+ isSelected: this.selected
};
for (const key of this.layoutKeys) {
bindings[key.Name + "Key"] = key;
@@ -113,15 +147,18 @@ export class DocumentView extends React.Component<IProps> {
bindings[key.Name] = field.GetValue();
}
}
+
return (
- <div className="node" style={{
- transform: this.transform,
- width: this.width,
- height: this.height
- }} onPointerDown={this.onPointerDown} onContextMenu={
- (e) => {
- e.preventDefault()
- }}>
+ <div className="node" ref={this._mainCont} style={{
+ transform: this.transform,
+ width: this.width,
+ height: this.height,
+ }}
+ onContextMenu={
+ (e) => {
+ e.preventDefault()
+ }}
+ onPointerDown={this.onPointerDown}>
<JsxParser
components={{ FieldTextBox, FreeFormCanvas, CollectionFreeFormView }}
bindings={bindings}