aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/DocumentDecorations.scss50
-rw-r--r--src/DocumentDecorations.tsx40
-rw-r--r--src/Main.scss5
-rw-r--r--src/Main.tsx21
-rw-r--r--src/documents/Documents.ts5
-rw-r--r--src/stores/NodeCollectionStore.ts3
-rw-r--r--src/util/DragManager.ts155
-rw-r--r--src/views/nodes/DocumentView.tsx113
-rw-r--r--src/views/nodes/FieldTextBox.scss4
9 files changed, 272 insertions, 124 deletions
diff --git a/src/DocumentDecorations.scss b/src/DocumentDecorations.scss
index 5a9ff7d01..2840d782b 100644
--- a/src/DocumentDecorations.scss
+++ b/src/DocumentDecorations.scss
@@ -4,30 +4,28 @@
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;
+ #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
index d7466dcac..cf7c6d8b5 100644
--- a/src/DocumentDecorations.tsx
+++ b/src/DocumentDecorations.tsx
@@ -15,44 +15,21 @@ export class DocumentDecorations extends React.Component {
}
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;
+ return SelectionManager.SelectedDocuments().reduce((left, element) => Math.min(element.screenRect.left, left), Number.MAX_VALUE);
}
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;
+ return SelectionManager.SelectedDocuments().reduce((top, element) => Math.min(element.screenRect.top, top), Number.MAX_VALUE);
}
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;
+ return (SelectionManager.SelectedDocuments().reduce((bottom, element) => Math.max(element.screenRect.bottom, 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;
+ return SelectionManager.SelectedDocuments().reduce((right, element) => Math.max(element.screenRect.right, right),
+ Number.MIN_VALUE) - this.x;
}
private _resizer = ""
@@ -129,8 +106,9 @@ export class DocumentDecorations extends React.Component {
}
SelectionManager.SelectedDocuments().forEach(element => {
- if (element.mainCont.current !== null) {
- let scale = element.width / element.mainCont.current.getBoundingClientRect().width;
+ const rect = element.screenRect;
+ if (rect.width !== 0) {
+ let scale = element.width / rect.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);
diff --git a/src/Main.scss b/src/Main.scss
index 441716e5c..e73f62904 100644
--- a/src/Main.scss
+++ b/src/Main.scss
@@ -23,4 +23,9 @@ h1 {
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
+}
+
+p {
+ margin: 0px;
+ padding: 0px;
} \ No newline at end of file
diff --git a/src/Main.tsx b/src/Main.tsx
index c2275b6a7..e5302bdee 100644
--- a/src/Main.tsx
+++ b/src/Main.tsx
@@ -47,35 +47,20 @@ ReactDOM.render((
<ContextMenu />
</div>), document.getElementById('root'));
-
-
-// create a bunch of text and video nodes (you probably want to delete this at some point)
-let numNodes = 300;
-let maxX = 10000;
-let maxY = 10000;
-let nodes:NodeStore[] = []
-for (let i = 0; i < numNodes; i++) {
- nodes.push(new StaticTextNodeStore({ X: Math.random() * maxX, Y: Math.random() * maxY, Title: "Text Node Title", Text: "Sed ut perspiciatis unde omnis iste natus error sit voluptatem accusantium doloremque laudantium, totam rem aperiam, eaque ipsa quae ab illo inventore veritatis et quasi architecto beatae vitae dicta sunt explicabo. Nemo enim ipsam voluptatem quia voluptas sit aspernatur aut odit aut fugit, sed quia consequuntur magni dolores eos qui ratione voluptatem sequi nesciunt. Neque porro quisquam est, qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit, sed quia non numquam eius modi tempora incidunt ut labore et dolore magnam aliquam quaerat voluptatem. Ut enim ad minima veniam, quis nostrum exercitationem ullam corporis suscipit laboriosam, nisi ut aliquid ex ea commodi consequatur? Quis autem vel eum iure reprehenderit qui in ea voluptate velit esse quam nihil molestiae consequatur, vel illum qui dolorem eum fugiat quo voluptas nulla pariatur?" }));
-}
-
-for (let i = 0; i < 20; i++) {
- nodes.push(new VideoNodeStore({ X: Math.random() * maxX, Y: Math.random() * maxY, Title: "Video Node Title", Url: "http://cs.brown.edu/people/peichman/downloads/cted.mp4" }));
-}
-
runInAction(() => {
let doc1 = Documents.TextDocument("Hello world");
let doc2 = doc1.MakeDelegate();
doc2.SetField(KS.X, new NumberField(150));
doc2.SetField(KS.Y, new NumberField(20));
- let doc3 = Documents.ImageDocument("https://static.boredpanda.com/blog/wp-content/uploads/2018/04/5acb63d83493f__700-png.jpg", {
+ let doc3 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
x: 450, y: 500
});
let docset = new Array<Document>(doc1, doc2);
let doc4 = Documents.CollectionDocument(docset, {
x: 0, y: 400
});
- let doc5 = Documents.ImageDocument("https://static.boredpanda.com/blog/wp-content/uploads/2018/04/5acb63d83493f__700-png.jpg", {
- x: 1280, y: 500
+ let doc5 = Documents.ImageDocument("https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg", {
+ x: 650, y: 500
});
let mainNodes = mainContainer.GetFieldT(KeyStore.Data, ListField);
if (!mainNodes) {
diff --git a/src/documents/Documents.ts b/src/documents/Documents.ts
index 5fee7343b..fb7c7c4d9 100644
--- a/src/documents/Documents.ts
+++ b/src/documents/Documents.ts
@@ -59,7 +59,8 @@ export namespace Documents {
imageProto.SetField(KeyStore.Y, new NumberField(0));
imageProto.SetField(KeyStore.Width, new NumberField(300));
imageProto.SetField(KeyStore.Height, new NumberField(300));
- imageProto.SetField(KeyStore.Layout, new TextField('<img src={Data} alt="Image not found"/>'));
+ imageProto.SetField(KeyStore.Layout, new TextField('<img src={Data} draggable="false" width="100%" alt="Image not found"/>'));
+ // imageProto.SetField(KeyStore.Layout, new TextField('<div style={"background-image: " + {Data}} />'));
imageProto.SetField(KeyStore.LayoutFields, new ListField([KeyStore.Data]));
}
return imageProto;
@@ -83,7 +84,7 @@ export namespace Documents {
collectionProto.SetField(KeyStore.PanY, new NumberField(0));
collectionProto.SetField(KeyStore.Width, new NumberField(300));
collectionProto.SetField(KeyStore.Height, new NumberField(300));
- collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView Document={Document} fieldKey={DataKey} ContainingDocumentView={ContainingDocumentView} isSelected={isSelected}/>'));
+ collectionProto.SetField(KeyStore.Layout, new TextField('<CollectionFreeFormView Document={Document} fieldKey={DataKey} ContainingDocumentView={ContainingDocumentView}/>'));
collectionProto.SetField(KeyStore.LayoutKeys, new ListField([KeyStore.Data]));
}
return collectionProto;
diff --git a/src/stores/NodeCollectionStore.ts b/src/stores/NodeCollectionStore.ts
index ac4f515f1..7fac83d51 100644
--- a/src/stores/NodeCollectionStore.ts
+++ b/src/stores/NodeCollectionStore.ts
@@ -15,7 +15,8 @@ export class NodeCollectionStore extends NodeStore {
@computed
public get Transform(): string {
- return "translate(" + this.X + "px," + this.Y + "px) scale(" + this.Scale + "," + this.Scale + ")";
+ const halfWidth = window.innerWidth / 2, halfHeight = window.innerHeight / 2;
+ return `translate(${this.X + halfWidth}px, ${this.Y + halfHeight}px) scale(${this.Scale}) translate(${-halfWidth}px, ${-halfHeight}px)`;
}
@action
diff --git a/src/util/DragManager.ts b/src/util/DragManager.ts
new file mode 100644
index 000000000..3111d589f
--- /dev/null
+++ b/src/util/DragManager.ts
@@ -0,0 +1,155 @@
+import { Opt } from "../fields/Field";
+
+export namespace DragManager {
+ export let rootId = "root";
+ let dragDiv: HTMLDivElement;
+
+ export enum DragButtons {
+ Left = 1, Right = 2, Both = Left | Right
+ }
+
+ interface DragOptions {
+ handlers: DragHandlers;
+
+ buttons: number;
+ }
+
+ export interface DragDropDisposer {
+ (): void;
+ }
+
+ export class DragStartEvent {
+ private _cancelled: boolean = false;
+ get cancelled() { return this._cancelled };
+
+ cancel() { this._cancelled = true; };
+ }
+
+ export class DragCompleteEvent {
+
+ }
+
+ export interface DragHandlers {
+ dragStart: (e: DragStartEvent) => void;
+ dragComplete: (e: DragCompleteEvent) => void;
+ }
+
+ export interface DropOptions {
+ handlers: DropHandlers;
+ }
+
+ export class DropEvent {
+ constructor(readonly x: number, readonly y: number) { }
+ }
+
+ export interface DropHandlers {
+ drop: (e: DropEvent) => void;
+ }
+
+ export function MakeDraggable(element: HTMLElement, options: DragOptions): DragDropDisposer {
+ if ("draggable" in element.dataset) {
+ throw new Error("Element is already draggable, can't make it draggable again");
+ }
+ element.dataset["draggable"] = "true";
+ const dispose = () => {
+ document.removeEventListener("pointerup", upHandler);
+ document.removeEventListener("pointermove", startDragHandler);
+ }
+ const startDragHandler = (e: PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ dispose();
+ StartDrag(element, e, options);
+ }
+ const upHandler = (e: PointerEvent) => {
+ dispose();
+ };
+ const downHandler = (e: PointerEvent) => {
+ e.stopPropagation();
+ document.addEventListener("pointermove", startDragHandler);
+ document.addEventListener("pointerup", upHandler);
+ };
+ element.addEventListener("pointerdown", downHandler);
+
+ return () => {
+ element.removeEventListener("pointerdown", downHandler);
+ delete element.dataset["draggable"];
+ }
+ }
+
+ export function MakeDropTarget(element: HTMLElement, options: DropOptions): DragDropDisposer {
+ if ("draggable" in element.dataset) {
+ throw new Error("Element is already droppable, can't make it droppable again");
+ }
+ element.dataset["canDrop"] = "true";
+ const handler = (e: Event) => {
+ const ce = e as CustomEvent<DropEvent>;
+ options.handlers.drop(ce.detail);
+ };
+ element.addEventListener("dashOnDrop", handler);
+ return () => {
+ element.removeEventListener("dashOnDrop", handler);
+ delete element.dataset["canDrop"]
+ };
+ }
+
+ function StartDrag(ele: HTMLElement, e: PointerEvent, options: DragOptions) {
+ if (!dragDiv) {
+ const root = document.getElementById(rootId);
+ if (!root) {
+ throw new Error("No root element found");
+ }
+ dragDiv = document.createElement("div");
+ root.appendChild(dragDiv);
+ }
+ if ((e.buttons & options.buttons) === 0) {
+ return;
+ }
+ let event = new DragStartEvent();
+ options.handlers.dragStart(event);
+ if (event.cancelled) {
+ return;
+ }
+ const w = ele.offsetWidth, h = ele.offsetHeight;
+ const rect = ele.getBoundingClientRect();
+ const scaleX = rect.width / w, scaleY = rect.height / h;
+ let x = rect.left, y = rect.top;
+ // const offsetX = e.x - rect.left, offsetY = e.y - rect.top;
+ let dragElement = ele.cloneNode(true) as HTMLElement;
+ dragElement.style.opacity = "0.7";
+ dragElement.style.position = "absolute";
+ dragElement.style.transformOrigin = "0 0";
+ dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ dragDiv.appendChild(dragElement);
+
+ const moveHandler = (e: PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ x += e.movementX;
+ y += e.movementY;
+ dragElement.style.transform = `translate(${x}px, ${y}px) scale(${scaleX}, ${scaleY})`;
+ };
+ const upHandler = (e: PointerEvent) => {
+ document.removeEventListener("pointermove", moveHandler, true);
+ document.removeEventListener("pointerup", upHandler);
+ FinishDrag(dragElement, e, options);
+ };
+ document.addEventListener("pointermove", moveHandler, true);
+ document.addEventListener("pointerup", upHandler);
+ }
+
+ function FinishDrag(ele: HTMLElement, e: PointerEvent, options: DragOptions) {
+ dragDiv.removeChild(ele);
+ const target = document.elementFromPoint(e.x, e.y);
+ if (!target) {
+ return;
+ }
+ target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
+ bubbles: true,
+ detail: {
+ x: e.x,
+ y: e.y,
+ }
+ }));
+ }
+} \ No newline at end of file
diff --git a/src/views/nodes/DocumentView.tsx b/src/views/nodes/DocumentView.tsx
index f7d4aa838..33a126a7b 100644
--- a/src/views/nodes/DocumentView.tsx
+++ b/src/views/nodes/DocumentView.tsx
@@ -23,6 +23,51 @@ interface IProps {
}
@observer
+class DocumentContents extends React.Component<IProps> {
+
+ @computed
+ get layout(): string {
+ return this.props.Document.GetFieldValue(KeyStore.Layout, TextField, String("<p>Error loading layout data</p>"));
+ }
+
+ @computed
+ get layoutKeys(): Key[] {
+ return this.props.Document.GetFieldValue(KeyStore.LayoutKeys, ListField, new Array<Key>());
+ }
+
+ @computed
+ get layoutFields(): Key[] {
+ return this.props.Document.GetFieldValue(KeyStore.LayoutFields, ListField, new Array<Key>());
+ }
+ render() {
+ let doc = this.props.Document;
+ let bindings: any = {
+ doc: doc,
+ // isSelected: this.props.isSelected
+ };
+ for (const key of this.layoutKeys) {
+ bindings[key.Name + "Key"] = key;
+ }
+ for (const key of this.layoutFields) {
+ let field = doc.GetField(key);
+ if (field) {
+ bindings[key.Name] = field.GetValue();
+ }
+ }
+ return <JsxParser
+ components={{ FieldTextBox, CollectionFreeFormView }}
+ bindings={bindings}
+ jsx={this.layout}
+ showWarnings={true}
+ onError={(test: any) => { console.log(test) }}
+ />
+
+
+ }
+
+}
+
+@observer
export class DocumentView extends React.Component<IProps> {
private _mainCont = React.createRef<HTMLDivElement>();
private _contextMenuCanOpen = false;
@@ -31,8 +76,11 @@ export class DocumentView extends React.Component<IProps> {
private _lastX:number = 0;
private _lastY:number = 0;
- get mainCont(): React.RefObject<HTMLDivElement> {
- return this._mainCont
+ get screenRect(): ClientRect | DOMRect {
+ if (this._mainCont.current) {
+ return this._mainCont.current.getBoundingClientRect();
+ }
+ return new DOMRect();
}
@computed
@@ -76,24 +124,16 @@ export class DocumentView extends React.Component<IProps> {
this.props.Document.SetFieldValue(KeyStore.Height, h, NumberField)
}
- @computed
- get layout(): string {
- return this.props.Document.GetFieldValue(KeyStore.Layout, TextField, String("<p>Error loading layout data</p>"));
- }
-
- @computed
- get layoutKeys(): Key[] {
- return this.props.Document.GetFieldValue(KeyStore.LayoutKeys, ListField, new Array<Key>());
- }
-
- @computed
- get layoutFields(): Key[] {
- return this.props.Document.GetFieldValue(KeyStore.LayoutFields, ListField, new Array<Key>());
- }
-
- @computed
- get selected() : boolean {
- return SelectionManager.IsSelected(this)
+ componentDidMount() {
+ // if(this._mainCont.current) {
+ // DragManager.MakeDraggable(this._mainCont.current, {
+ // buttons: 2,
+ // handlers: {
+ // dragComplete: () => {},
+ // dragStart: () => {}
+ // }
+ // })
+ // }
}
@computed
@@ -152,8 +192,8 @@ export class DocumentView extends React.Component<IProps> {
}
onDragStart = (e: React.DragEvent<HTMLDivElement>): void => {
- if (this.mainCont.current !== null) {
- this.mainCont.current.style.opacity = "0";
+ if (this._mainCont.current !== null) {
+ this._mainCont.current.style.opacity = "0";
// e.dataTransfer.setDragImage()
}
}
@@ -189,35 +229,16 @@ export class DocumentView extends React.Component<IProps> {
}
render() {
- let doc = this.props.Document;
- let bindings: any = {
- Document: this.props.Document,
- ContainingDocumentView: this
- };
- for (const key of this.layoutKeys) {
- bindings[key.Name + "Key"] = key;
- }
- for (const key of this.layoutFields) {
- let field = doc.GetField(key);
- if (field) {
- bindings[key.Name] = field.GetValue();
- }
- }
-
return (
<div className="node" ref={this._mainCont} style={{
- transform: this.transform,
- width: this.width,
- height: this.height,
- }}
+ transform: this.transform,
+ width: this.width,
+ height: this.height,
+ }}
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown}
onClick={this.onClick}>
- <JsxParser
- components={{ FieldTextBox, CollectionFreeFormView }}
- bindings={bindings}
- jsx={this.layout}
- />
+ <DocumentContents {...this.props} />
</div>
);
}
diff --git a/src/views/nodes/FieldTextBox.scss b/src/views/nodes/FieldTextBox.scss
index 5c95fe2b2..b6ce2fabc 100644
--- a/src/views/nodes/FieldTextBox.scss
+++ b/src/views/nodes/FieldTextBox.scss
@@ -4,6 +4,10 @@
height: 100%;
}
+.ProseMirror:focus {
+ outline: none !important
+}
+
.fieldTextBox-cont {
background: white;
padding: 1vw;