aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Utils.ts2
-rw-r--r--src/client/documents/Documents.ts2
-rw-r--r--src/client/util/DocumentManager.ts19
-rw-r--r--src/client/util/DragManager.ts11
-rw-r--r--src/client/util/SelectionManager.ts8
-rw-r--r--src/client/util/UndoManager.ts1
-rw-r--r--src/client/views/DocumentDecorations.tsx166
-rw-r--r--src/client/views/Main.scss4
-rw-r--r--src/client/views/Main.tsx3
-rw-r--r--src/client/views/MainOverlayTextBox.scss1
-rw-r--r--src/client/views/MainOverlayTextBox.tsx32
-rw-r--r--src/client/views/PreviewCursor.tsx37
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx53
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx16
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx12
-rw-r--r--src/client/views/collections/CollectionSubView.tsx24
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx5
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx10
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx2
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx78
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx123
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx173
-rw-r--r--src/client/views/nodes/DocumentView.tsx235
-rw-r--r--src/client/views/nodes/FieldView.tsx7
-rw-r--r--src/client/views/nodes/FormattedTextBox.scss4
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx53
-rw-r--r--src/client/views/nodes/ImageBox.scss4
-rw-r--r--src/client/views/nodes/ImageBox.tsx42
-rw-r--r--src/client/views/nodes/VideoBox.tsx3
-rw-r--r--src/client/views/nodes/WebBox.tsx19
-rw-r--r--src/new_fields/Doc.ts30
-rw-r--r--src/new_fields/Types.ts8
33 files changed, 652 insertions, 536 deletions
diff --git a/src/Utils.ts b/src/Utils.ts
index 59ff45dc9..c1ad88e2f 100644
--- a/src/Utils.ts
+++ b/src/Utils.ts
@@ -5,6 +5,8 @@ import { Message } from './server/Message';
export class Utils {
+ public static DRAG_THRESHOLD = 4;
+
public static GenerateGuid(): string {
return v4();
}
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index a5ce7c076..ed76f32ff 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -49,6 +49,8 @@ export interface DocumentOptions {
copyDraggedItems?: boolean;
backgroundLayout?: string;
curPage?: number;
+ documentText?: string;
+ borderRounding?: number;
// [key: string]: Opt<Field>;
}
const delegateKeys = ["x", "y", "width", "height", "panX", "panY"];
diff --git a/src/client/util/DocumentManager.ts b/src/client/util/DocumentManager.ts
index 04e2e2917..69964e2c9 100644
--- a/src/client/util/DocumentManager.ts
+++ b/src/client/util/DocumentManager.ts
@@ -26,22 +26,23 @@ export class DocumentManager {
public getDocumentView(toFind: Doc): DocumentView | null {
- let toReturn: DocumentView | null;
- toReturn = null;
+ let toReturn: DocumentView | null = null;
//gets document view that is in a freeform canvas collection
DocumentManager.Instance.DocumentViews.map(view => {
- let doc = view.props.Document;
-
- if (doc === toFind) {
+ if (view.props.Document === toFind) {
toReturn = view;
return;
}
- let docSrc = FieldValue(doc.proto);
- if (docSrc && Object.is(docSrc, toFind)) {
- toReturn = view;
- }
});
+ if (!toReturn) {
+ DocumentManager.Instance.DocumentViews.map(view => {
+ let doc = view.props.Document.proto;
+ if (doc && Object.is(doc, toFind)) {
+ toReturn = view;
+ }
+ });
+ }
return toReturn;
}
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index 46658867b..5aa7ad8e2 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -41,11 +41,11 @@ export function SetupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc:
}
export async function DragLinksAsDocuments(dragEle: HTMLElement, x: number, y: number, sourceDoc: Document) {
- let srcTarg = sourceDoc.GetT(KeyStore.Prototype, Document);
- let draggedDocs = (srcTarg && srcTarg !== FieldWaiting) ?
+ let srcTarg = sourceDoc.GetPrototype();
+ let draggedDocs = srcTarg ?
srcTarg.GetList(KeyStore.LinkedToDocs, [] as Document[]).map(linkDoc =>
(linkDoc.GetT(KeyStore.LinkedToDocs, Document)) as Document) : [];
- let draggedFromDocs = (srcTarg && srcTarg !== FieldWaiting) ?
+ let draggedFromDocs = srcTarg ?
srcTarg.GetList(KeyStore.LinkedFromDocs, [] as Document[]).map(linkDoc =>
(linkDoc.GetT(KeyStore.LinkedFromDocs, Document)) as Document) : [];
draggedDocs.push(...draggedFromDocs);
@@ -158,11 +158,13 @@ export namespace DragManager {
}
export class LinkDragData {
- constructor(linkSourceDoc: Document) {
+ constructor(linkSourceDoc: Document, blacklist: Document[] = []) {
this.linkSourceDocument = linkSourceDoc;
+ this.blacklist = blacklist;
}
droppedDocuments: Document[] = [];
linkSourceDocument: Document;
+ blacklist: Document[];
[id: string]: any;
}
@@ -174,6 +176,7 @@ export namespace DragManager {
if (!dragDiv) {
dragDiv = document.createElement("div");
dragDiv.className = "dragManager-dragDiv";
+ dragDiv.style.pointerEvents = "none";
DragManager.Root().appendChild(dragDiv);
}
MainOverlayTextBox.Instance.SetTextDoc();
diff --git a/src/client/util/SelectionManager.ts b/src/client/util/SelectionManager.ts
index a1a22f732..fe5acf4b4 100644
--- a/src/client/util/SelectionManager.ts
+++ b/src/client/util/SelectionManager.ts
@@ -1,9 +1,7 @@
import { observable, action } from "mobx";
-import { DocumentView } from "../views/nodes/DocumentView";
-import { Main } from "../views/Main";
-import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
-import { DragManager } from "./DragManager";
import { Doc } from "../../new_fields/Doc";
+import { MainOverlayTextBox } from "../views/MainOverlayTextBox";
+import { DocumentView } from "../views/nodes/DocumentView";
export namespace SelectionManager {
class Manager {
@@ -65,7 +63,7 @@ export namespace SelectionManager {
export function ReselectAll() {
let sdocs = manager.ReselectAll();
- manager.ReselectAll2(sdocs);
+ setTimeout(() => manager.ReselectAll2(sdocs), 0);
}
export function SelectedDocuments(): Array<DocumentView> {
return manager.SelectedDocuments;
diff --git a/src/client/util/UndoManager.ts b/src/client/util/UndoManager.ts
index f91ca2e06..f7c3e5a7b 100644
--- a/src/client/util/UndoManager.ts
+++ b/src/client/util/UndoManager.ts
@@ -140,6 +140,7 @@ export namespace UndoManager {
}
});
+ //TODO Make this return the return value
export function RunInBatch(fn: () => void, batchName: string) {
let batch = StartBatch(batchName);
try {
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3b6381ec2..6e361d76a 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -1,6 +1,6 @@
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { emptyFunction } from "../../Utils";
+import { emptyFunction, Utils } from "../../Utils";
import { DragLinksAsDocuments, DragManager } from "../util/DragManager";
import { SelectionManager } from "../util/SelectionManager";
import { undoBatch } from "../util/UndoManager";
@@ -11,9 +11,11 @@ import { LinkMenu } from "./nodes/LinkMenu";
import React = require("react");
import { CompileScript } from "../util/Scripting";
import { IconBox } from "./nodes/IconBox";
-import { Cast, FieldValue } from "../../new_fields/Types";
-import { Doc } from "../../new_fields/Doc";
+import { Cast, FieldValue, NumCast, StrCast } from "../../new_fields/Types";
+import { Doc, FieldResult } from "../../new_fields/Doc";
import { listSpec } from "../../new_fields/Schema";
+import { Docs } from "../documents/Documents";
+import { List } from "../../new_fields/List";
const higflyout = require("@hig/flyout");
export const { anchorPoints } = higflyout;
export const Flyout = higflyout.default;
@@ -21,8 +23,8 @@ export const Flyout = higflyout.default;
@observer
export class DocumentDecorations extends React.Component<{}, { value: string }> {
static Instance: DocumentDecorations;
- private _resizer = "";
private _isPointerDown = false;
+ private _resizing = "";
private keyinput: React.RefObject<HTMLInputElement>;
private _documents: DocumentView[] = SelectionManager.SelectedDocuments();
private _resizeBorderWidth = 16;
@@ -30,6 +32,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
private _titleHeight = 20;
private _linkButton = React.createRef<HTMLDivElement>();
private _linkerButton = React.createRef<HTMLDivElement>();
+ private _downX = 0;
+ private _downY = 0;
+ @observable private _minimizedX = 0;
+ @observable private _minimizedY = 0;
//@observable private _title: string = this._documents[0].props.Document.Title;
@observable private _title: string = this._documents.length > 0 ? Cast(this._documents[0].props.Document.title, "string", "") : "";
@observable private _fieldKey: string = "title";
@@ -37,6 +43,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@observable private _opacity = 1;
@observable private _dragging = false;
@observable private _iconifying = false;
+ @observable public Interacting = false;
constructor(props: Readonly<{}>) {
@@ -125,19 +132,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
@action
onBackgroundMove = (e: PointerEvent): void => {
let dragDocView = SelectionManager.SelectedDocuments()[0];
- const [left, top] = dragDocView.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const [left, top] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).inverse().transformPoint(0, 0);
let dragData = new DragManager.DocumentDragData(SelectionManager.SelectedDocuments().map(dv => dv.props.Document));
+ const [xoff, yoff] = dragDocView.props.ScreenToLocalTransform().scale(dragDocView.props.ContentScaling()).transformDirection(e.x - left, e.y - top);
+ dragData.xOffset = xoff;
+ dragData.yOffset = yoff;
dragData.aliasOnDrop = false;
- dragData.xOffset = e.x - left;
- dragData.yOffset = e.y - top;
let move = SelectionManager.SelectedDocuments()[0].props.moveDocument;
dragData.moveDocument = move;
- this._dragging = true;
+ this.Interacting = this._dragging = true;
document.removeEventListener("pointermove", this.onBackgroundMove);
document.removeEventListener("pointerup", this.onBackgroundUp);
DragManager.StartDocumentDrag(SelectionManager.SelectedDocuments().map(docView => docView.ContentDiv!), dragData, e.x, e.y, {
handlers: {
- dragComplete: action(() => this._dragging = false),
+ dragComplete: action(() => this.Interacting = this._dragging = false),
},
hideSource: true
});
@@ -176,13 +184,16 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
document.removeEventListener("pointerup", this.onCloseUp);
}
}
- _downX = 0;
- _downY = 0;
+ @action
onMinimizeDown = (e: React.PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
this._downX = e.pageX;
this._downY = e.pageY;
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+ this._minimizedX = selDocPos[0] + 12;
+ this._minimizedY = selDocPos[1] + 12;
document.removeEventListener("pointermove", this.onMinimizeMove);
document.addEventListener("pointermove", this.onMinimizeMove);
document.removeEventListener("pointerup", this.onMinimizeUp);
@@ -190,57 +201,99 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
}
- @observable _minimizedX = 0;
- @observable _minimizedY = 0;
@action
onMinimizeMove = (e: PointerEvent): void => {
e.stopPropagation();
- let dx = e.pageX - this._downX;
- let dy = e.pageY - this._downY;
- if (Math.abs(dx) > 4 || Math.abs(dy) > 4) {
- this._iconifying = true;
- let xf = SelectionManager.SelectedDocuments()[0].props.ScreenToLocalTransform().scale(SelectionManager.SelectedDocuments()[0].props.ContentScaling()).inverse().transformPoint(0, 0);
- let dx = e.pageX - xf[0];
- let dy = e.pageY - xf[1];
- this._minimizedX = e.clientX;
- this._minimizedY = e.clientY;
- if (Math.abs(dx) < 20 && Math.abs(dy) < 20) {
- this._minimizedX = xf[0];
- this._minimizedY = xf[1];
- }
- SelectionManager.SelectedDocuments().map(dv => {
- let minDoc = FieldValue(Cast(dv.props.Document.minimizedDoc, Doc));
- if (minDoc) {
- let where = (dv.props.ScreenToLocalTransform()).scale(dv.props.ContentScaling()).transformPoint(this._minimizedX, this._minimizedY);
- minDoc.x = where[0] + Cast(dv.props.Document.x, "number", 0);
- minDoc.y = where[1] + Cast(dv.props.Document.y, "number", 0);
- }
- });
+ if (Math.abs(e.pageX - this._downX) > Utils.DRAG_THRESHOLD ||
+ Math.abs(e.pageY - this._downY) > Utils.DRAG_THRESHOLD) {
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let selDocPos = selDoc.props.ScreenToLocalTransform().scale(selDoc.props.ContentScaling()).inverse().transformPoint(0, 0);
+ let snapped = Math.abs(e.pageX - selDocPos[0]) < 20 && Math.abs(e.pageY - selDocPos[1]) < 20;
+ this._minimizedX = snapped ? selDocPos[0] + 4 : e.clientX;
+ this._minimizedY = snapped ? selDocPos[1] - 18 : e.clientY;
+ let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+ Promise.all(selectedDocs.map(selDoc => this.getIconDoc(selDoc))).then(minDocSet =>
+ this.moveIconDocs(SelectionManager.SelectedDocuments())
+ );
+ this._iconifying = snapped;
}
}
+
+
+ @action createIcon = (docView: DocumentView, layoutString: string): Doc => {
+ let doc = docView.props.Document;
+ let iconDoc = Docs.IconDocument(layoutString);
+ iconDoc.title = "ICON" + StrCast(doc.title);
+ iconDoc.isMinimized = false;
+ iconDoc.nativeWidth = 0;
+ iconDoc.nativeHeight = 0;
+ iconDoc.x = NumCast(doc.x);
+ iconDoc.y = NumCast(doc.y) - 24;
+ iconDoc.proto = doc;
+ iconDoc.maximizedDoc = doc;
+ doc.minimizedDoc = iconDoc;
+ docView.props.addDocument && docView.props.addDocument(iconDoc, false);
+ return iconDoc;
+ }
+ @action
+ public getIconDoc = async (docView: DocumentView): Promise<Doc | undefined> => {
+ let doc = docView.props.Document;
+
+ const minDoc = await Cast(doc.minimizedDoc, Doc);
+ if (minDoc) return minDoc;
+
+ const field = StrCast(doc.backgroundLayout, undefined);
+ if (field !== undefined) return this.createIcon(docView, field);
+
+ const layout = StrCast(doc.layout, undefined);
+ if (layout !== undefined) return this.createIcon(docView, field);
+
+ return undefined;
+ }
@action
onMinimizeUp = (e: PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
- let dx = e.clientX - this._downX;
- let dy = e.clientY - this._downY;
- if (Math.abs(dx) < 4 && Math.abs(dy) < 4 && !this._iconifying) {
- SelectionManager.SelectedDocuments().map(dv => dv.minimize());
- SelectionManager.DeselectAll();
- } else {
- this._minimizedX = this._minimizedY = 0;
- }
document.removeEventListener("pointermove", this.onMinimizeMove);
document.removeEventListener("pointerup", this.onMinimizeUp);
+ let selectedDocs = SelectionManager.SelectedDocuments().map(sd => sd);
+ Promise.all(selectedDocs.map(selDoc => this.getIconDoc(selDoc))).then(minDocSet => {
+ let minDocs = minDocSet.filter(minDoc => minDoc instanceof Doc).map(minDoc => minDoc as Doc);
+ minDocs.map(minDoc => {
+ minDoc.x = NumCast(minDocs[0].x);
+ minDoc.y = NumCast(minDocs[0].y);
+ minDoc.linkTags = new List(minDocs);
+ if (this._iconifying && selectedDocs[0].props.removeDocument) {
+ selectedDocs[0].props.removeDocument(minDoc);
+ (minDoc.maximizedDoc as Doc).minimizedDoc = undefined;
+ }
+ });
+ runInAction(() => this._minimizedX = this._minimizedY = 0);
+ if (!this._iconifying) selectedDocs[0].props.toggleMinimized();
+ this._iconifying = false;
+ });
}
- this._iconifying = false;
+ }
+ moveIconDocs(selViews: DocumentView[], minDocSet?: FieldResult[]) {
+ selViews.map(selDoc => {
+ let minDoc = selDoc.props.Document.minimizedDoc;
+ if (minDoc instanceof Doc) {
+ let zoom = NumCast(selDoc.props.Document.zoomBasis, 1);
+ let where = (selDoc.props.ScreenToLocalTransform()).scale(selDoc.props.ContentScaling()).scale(1 / zoom).
+ transformPoint(this._minimizedX - 12, this._minimizedY - 12);
+ minDoc.x = where[0] + NumCast(selDoc.props.Document.x);
+ minDoc.y = where[1] + NumCast(selDoc.props.Document.y);
+ }
+ });
}
+ @action
onPointerDown = (e: React.PointerEvent): void => {
e.stopPropagation();
if (e.button === 0) {
this._isPointerDown = true;
- this._resizer = e.currentTarget.id;
+ this._resizing = e.currentTarget.id;
+ this.Interacting = true;
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -265,7 +318,9 @@ 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].props.Document);
+ let selDoc = SelectionManager.SelectedDocuments()[0];
+ let container = selDoc.props.ContainingCollectionView ? selDoc.props.ContainingCollectionView.props.Document.proto : undefined;
+ let dragData = new DragManager.LinkDragData(selDoc.props.Document, container ? [container] : []);
DragManager.StartLinkDrag(this._linkerButton.current, dragData, e.pageX, e.pageY, {
handlers: {
dragComplete: action(emptyFunction),
@@ -309,7 +364,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
let dX = 0, dY = 0, dW = 0, dH = 0;
- switch (this._resizer) {
+ switch (this._resizing) {
case "":
break;
case "documentDecorations-topLeftResizer":
@@ -381,8 +436,12 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
});
}
+ @action
onPointerUp = (e: PointerEvent): void => {
e.stopPropagation();
+ this._resizing = "";
+ this.Interacting = false;
+ SelectionManager.ReselectAll();
if (e.button === 0) {
e.preventDefault();
this._isPointerDown = false;
@@ -392,7 +451,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
}
getValue = (): string => {
- if (this._title === "changed" && this._documents.length > 0) {
+ if (this._documents.length > 0) {
let field = this._documents[0].props.Document[this._fieldKey];
if (typeof field === "string") {
return field;
@@ -416,17 +475,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (bounds.x === Number.MAX_VALUE || !seldoc) {
return (null);
}
- let selpos = this._minimizedX !== 0 || this._minimizedY !== 0 ?
- [this._minimizedX - 12 + (!this._iconifying ? 8 : 0), this._minimizedY - 12 + (!this._iconifying ? 28 : 0)] :
- [0, this._iconifying ? -18 : 0];
let minimizeIcon = (
- <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}
- style={{ transform: `translate(${selpos[0]}px,${selpos[1]}px)`, }}>
- {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(Cast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "string", "...")) : "..."}
+ <div className="documentDecorations-minimizeButton" onPointerDown={this.onMinimizeDown}>
+ {SelectionManager.SelectedDocuments().length === 1 ? IconBox.DocumentIcon(StrCast(SelectionManager.SelectedDocuments()[0].props.Document.layout, "...")) : "..."}
</div>);
- if (this._iconifying) {
- return (<div className="documentDecorations-container" > {minimizeIcon} </div>);
- }
if (this.Hidden) {
return (null);
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 4373534b2..2f899ff28 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -42,7 +42,9 @@ h1 {
}
.jsx-parser {
- width:100%
+ width:100%;
+ pointer-events: none;
+ border-radius: inherit;
}
p {
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 09ef30f6b..c6b3f06d8 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -17,7 +17,7 @@ import { CurrentUserUtils } from '../../server/authentication/models/current_use
import { MessageStore } from '../../server/Message';
import { RouteStore } from '../../server/RouteStore';
import { ServerUtils } from '../../server/ServerUtil';
-import { emptyDocFunction, emptyFunction, returnTrue, Utils, returnOne } from '../../Utils';
+import { emptyDocFunction, emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
import { Documents } from '../documents/Documents';
import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
@@ -187,6 +187,7 @@ export class Main extends React.Component {
<div ref={measureRef} id="mainContent-div">
{!mainCont ? (null) :
<DocumentView Document={mainCont}
+ toggleMinimized={emptyFunction}
addDocument={undefined}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
diff --git a/src/client/views/MainOverlayTextBox.scss b/src/client/views/MainOverlayTextBox.scss
index 697d68c8c..f6a746e63 100644
--- a/src/client/views/MainOverlayTextBox.scss
+++ b/src/client/views/MainOverlayTextBox.scss
@@ -7,6 +7,7 @@
overflow: visible;
top: 0;
left: 0;
+ pointer-events: none;
z-index: $mainTextInput-zindex;
.formattedTextBox-cont {
background-color: rgba(248, 6, 6, 0.001);
diff --git a/src/client/views/MainOverlayTextBox.tsx b/src/client/views/MainOverlayTextBox.tsx
index 8cb01117c..be8d67925 100644
--- a/src/client/views/MainOverlayTextBox.tsx
+++ b/src/client/views/MainOverlayTextBox.tsx
@@ -5,7 +5,7 @@ import * as React from 'react';
import { Document } from '../../fields/Document';
import { Key } from '../../fields/Key';
import { KeyStore } from '../../fields/KeyStore';
-import { emptyDocFunction, emptyFunction, returnTrue } from '../../Utils';
+import { emptyDocFunction, emptyFunction, returnTrue, returnZero } from '../../Utils';
import '../northstar/model/ModelExtensions';
import '../northstar/utils/Extensions';
import { DragManager } from '../util/DragManager';
@@ -21,8 +21,8 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
public static Instance: MainOverlayTextBox;
@observable public TextDoc?: Document = undefined;
public TextScroll: number = 0;
- private _textRect: any;
- private _textXf: Transform = Transform.Identity();
+ @observable _textRect: any;
+ @observable _textXf: () => Transform = () => Transform.Identity();
private _textFieldKey: Key = KeyStore.Data;
private _textColor: string | null = null;
private _textTargetDiv: HTMLDivElement | undefined;
@@ -35,14 +35,14 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
}
@action
- SetTextDoc(textDoc?: Document, textFieldKey?: Key, div?: HTMLDivElement, tx?: Transform) {
+ SetTextDoc(textDoc?: Document, textFieldKey?: Key, div?: HTMLDivElement, tx?: () => Transform) {
if (this._textTargetDiv) {
this._textTargetDiv.style.color = this._textColor;
}
this.TextDoc = textDoc;
this._textFieldKey = textFieldKey!;
- this._textXf = tx ? tx : Transform.Identity();
+ this._textXf = tx ? tx : () => Transform.Identity();
this._textTargetDiv = div;
if (div) {
this._textColor = div.style.color;
@@ -71,9 +71,7 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
document.removeEventListener("pointermove", this.textBoxMove);
document.removeEventListener('pointerup', this.textBoxUp);
let dragData = new DragManager.DocumentDragData([this.TextDoc!]);
- const [left, top] = this._textXf
- .inverse()
- .transformPoint(0, 0);
+ const [left, top] = this._textXf().inverse().transformPoint(0, 0);
dragData.xOffset = e.clientX - left;
dragData.yOffset = e.clientY - top;
DragManager.StartDocumentDrag([this._textTargetDiv!], dragData, e.clientX, e.clientY, {
@@ -91,17 +89,15 @@ export class MainOverlayTextBox extends React.Component<MainOverlayTextBoxProps>
render() {
if (this.TextDoc) {
- let x: number = this._textRect.x;
- let y: number = this._textRect.y;
- let w: number = this._textRect.width;
- let h: number = this._textRect.height;
- let t = this._textXf.transformPoint(0, 0);
- let s = this._textXf.transformPoint(1, 0);
- s[0] = Math.sqrt((s[0] - t[0]) * (s[0] - t[0]) + (s[1] - t[1]) * (s[1] - t[1]));
- return <div className="mainOverlayTextBox-textInput" style={{ pointerEvents: "none", transform: `translate(${x}px, ${y}px) scale(${1 / s[0]},${1 / s[0]})`, width: "auto", height: "auto" }} >
- <div className="mainOverlayTextBox-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` }}>
+ let toScreenXf = this._textXf().inverse();
+ let pt = toScreenXf.transformPoint(0, 0);
+ let s = 1 / this._textXf().Scale;
+ return <div className="mainOverlayTextBox-textInput" style={{ transform: `translate(${pt[0]}px, ${pt[1]}px) scale(${s},${s})`, width: "auto", height: "auto" }} >
+ <div className="mainOverlayTextBox-textInput" onPointerDown={this.textBoxDown} ref={this._textProxyDiv} onScroll={this.textScroll}
+ style={{ width: `${this.TextDoc.Width()}px`, height: `${this.TextDoc.Height()}px` }}>
<FormattedTextBox fieldKey={this._textFieldKey} isOverlay={true} Document={this.TextDoc} isSelected={returnTrue} select={emptyFunction} isTopMost={true}
- selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue} ScreenToLocalTransform={() => this._textXf} focus={emptyDocFunction} />
+ selectOnLoad={true} ContainingCollectionView={undefined} whenActiveChanged={emptyFunction} active={returnTrue}
+ ScreenToLocalTransform={this._textXf} PanelWidth={returnZero} PanelHeight={returnZero} focus={emptyDocFunction} />
</div>
</ div>;
}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index ff8434681..744ec0535 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -7,30 +7,49 @@ import "./PreviewCursor.scss";
@observer
export class PreviewCursor extends React.Component<{}> {
private _prompt = React.createRef<HTMLDivElement>();
+ static _onKeyPress?: (e: KeyboardEvent) => void;
+ @observable static _clickPoint = [0, 0];
+ @observable public static Visible = false;
//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 = () => { };
+ constructor(props: any) {
+ super(props);
+ document.addEventListener("keydown", this.onKeyPress)
+ }
+
+ @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) {
+ PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
+ PreviewCursor.Visible = false;
+ } else if (e.ctrlKey) {
+ if (e.key == "v") {
+ PreviewCursor.Visible && PreviewCursor._onKeyPress && PreviewCursor._onKeyPress(e);
+ }
+ }
+ }
@action
- public static Show(hide: any, x: number, y: number) {
- this.clickPoint = [x, y];
- this.hide = hide;
+ public static Show(x: number, y: number, onKeyPress: (e: KeyboardEvent) => void) {
+ this._clickPoint = [x, y];
+ this._onKeyPress = onKeyPress;
setTimeout(action(() => this.Visible = true), (1));
}
render() {
- if (!PreviewCursor.clickPoint) {
+ 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 }}>
+ style={{ transform: `translate(${PreviewCursor._clickPoint[0]}px, ${PreviewCursor._clickPoint[1]}px)`, opacity: PreviewCursor.Visible ? 1 : 0 }}>
I
</div >;
}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 058893198..4807dc40a 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -3,7 +3,7 @@ import { observer } from 'mobx-react';
import * as React from 'react';
import { ContextMenu } from '../ContextMenu';
import { FieldViewProps } from '../nodes/FieldView';
-import { Cast, FieldValue, PromiseValue } from '../../../new_fields/Types';
+import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Types';
import { Doc, FieldResult, Opt, Id } from '../../../new_fields/Doc';
import { listSpec } from '../../../new_fields/Schema';
import { List } from '../../../new_fields/List';
@@ -87,51 +87,24 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
let props = this.props;
var curPage = Cast(props.Document.curPage, "number", -1);
Doc.SetOnPrototype(doc, "page", curPage);
- if (true || this.isAnnotationOverlay) {
- doc.zoom = Cast(this.props.Document.scale, "number", 1);
- }
if (curPage >= 0) {
Doc.SetOnPrototype(doc, "annotationOn", props.Document);
}
- const data = props.Document[props.fieldKey];
- if (data !== undefined) {
+ if (!this.createsCycle(doc, props.Document)) {
//TODO This won't create the field if it doesn't already exist
- const value = Cast(data, listSpec(Doc));
- if (!this.createsCycle(doc, props.Document) && value !== undefined) {
+ const value = Cast(props.Document[props.fieldKey], listSpec(Doc));
+ if (value !== undefined) {
if (allowDuplicates || !value.some(v => v.Id === doc.Id)) {
value.push(doc);
}
+ } else {
+ this.props.Document[this.props.fieldKey] = new List([doc]);
}
- else {
- return false;
- }
- } else {
- let proto = FieldValue(props.Document.proto);
- if (!proto || !this.createsCycle(proto, doc)) {
- const field = new List([doc]);
- // const script = CompileScript(`
- // if(added) {
- // console.log("added " + field.Title + " " + doc.Title);
- // } else {
- // console.log("removed " + field.Title + " " + doc.Title);
- // }
- // `, {
- // addReturn: false,
- // params: {
- // field: Document.name,
- // added: "boolean"
- // },
- // capturedVariables: {
- // doc: this.props.Document
- // }
- // });
- // if (script.compiled) {
- // field.addScript(new ScriptField(script));
- // }
- Doc.SetOnPrototype(props.Document, props.fieldKey, field);
- }
- else {
- return false;
+ // set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
+ if (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid) {
+ let zoom = NumCast(this.props.Document.scale, 1);
+ let screen = this.props.ScreenToLocalTransform().inverse().Scale / (this.props as any).ContentScaling() * zoom;
+ doc.zoomBasis = screen;
}
}
return true;
@@ -186,7 +159,9 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
};
const viewtype = this.collectionViewType;
return (
- <div className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
+ <div className={this.props.className || "collectionView-cont"}
+ style={{ borderRadius: "inherit", pointerEvents: "all" }}
+ onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
{viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
</div>
);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 86ee7cea9..ec5f823b0 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -5,7 +5,7 @@ import { action, observable, reaction, trace } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
import Measure from "react-measure";
-import { Utils, returnTrue, emptyFunction, returnOne } from "../../../Utils";
+import { Utils, returnTrue, emptyFunction, returnOne, returnZero } from "../../../Utils";
import { Server } from "../../Server";
import { undoBatch } from "../../util/UndoManager";
import { DocumentView } from "../nodes/DocumentView";
@@ -16,9 +16,10 @@ import { ServerUtils } from "../../../server/ServerUtil";
import { DragManager, DragLinksAsDocuments } from "../../util/DragManager";
import { Transform } from '../../util/Transform';
import { Doc, Id, Opt, Field, FieldId } from "../../../new_fields/Doc";
-import { Cast } from "../../../new_fields/Types";
+import { Cast, NumCast } from "../../../new_fields/Types";
import { List } from "../../../new_fields/List";
import { DocServer } from "../../DocServer";
+import { listSpec } from "../../../new_fields/Schema";
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -256,8 +257,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (tfield !== undefined) {
tab.titleElement[0].textContent = f.Title;
}
- const lf = await Cast(f.linkedFromDocs, List);
- const lt = await Cast(f.linkedToDocs, List);
+ const lf = await Cast(f.linkedFromDocs, listSpec(Doc));
+ const lt = await Cast(f.linkedToDocs, listSpec(Doc));
let count = (lf ? lf.length : 0) + (lt ? lt.length : 0);
let counter: any = this.htmlToElement(`<div class="messageCounter">${count}</div>`);
tab.element.append(counter);
@@ -322,8 +323,8 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
}
- nativeWidth = () => Cast(this._document!.nativeWidth, "number", this._panelWidth);
- nativeHeight = () => Cast(this._document!.nativeHeight, "number", this._panelHeight);
+ nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
+ nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight);
contentScaling = () => {
const nativeH = this.nativeHeight();
const nativeW = this.nativeWidth();
@@ -349,6 +350,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
<div className="collectionDockingView-content" ref={this._mainCont}
style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
<DocumentView key={this._document![Id]} Document={this._document!}
+ toggleMinimized={emptyFunction}
addDocument={undefined}
removeDocument={undefined}
ContentScaling={this.contentScaling}
@@ -361,7 +363,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
whenActiveChanged={emptyFunction}
focus={emptyFunction}
ContainingCollectionView={undefined} />
- </div>);
+ </div >);
}
render() {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index e556151f9..2e1175f28 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -7,10 +7,9 @@ import { observer } from "mobx-react";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss'
import "react-table/react-table.css";
-import { emptyFunction, returnFalse } from "../../../Utils";
-import { Server } from "../../Server";
+import { emptyFunction, returnFalse, returnZero } from "../../../Utils";
import { SetupDrag } from "../../util/DragManager";
-import { CompileScript, ToField } from "../../util/Scripting";
+import { CompileScript } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";
import { anchorPoints, Flyout } from "../DocumentDecorations";
@@ -21,7 +20,7 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
import { Opt, Field, Doc, Id } from "../../../new_fields/Doc";
-import { Cast, FieldValue } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { listSpec } from "../../../new_fields/Schema";
import { List } from "../../../new_fields/List";
@@ -57,7 +56,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@observable _keys: string[] = [];
@observable _newKeyName: string = "";
- @computed get splitPercentage() { return Cast(this.props.Document.schemaSplitPercentage, "number", 0); }
+ @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage); }
@computed get columns() { return Cast(this.props.Document.columns, listSpec("string"), []); }
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
@@ -74,6 +73,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
focus: emptyFunction,
active: returnFalse,
whenActiveChanged: emptyFunction,
+ PanelHeight: returnZero,
+ PanelWidth: returnZero,
};
let contents = (
<FieldView {...props} />
@@ -256,6 +257,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<div className="collectionSchemaView-previewRegion" style={{ width: `${this.previewRegionWidth}px` }}>
<div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
<DocumentView Document={this.previewDocument} isTopMost={false} selectOnLoad={false}
+ toggleMinimized={emptyFunction}
addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
ScreenToLocalTransform={this.getPreviewTransform}
ContentScaling={this.previewContentScaling}
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 642e5aed6..558a8728f 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -4,11 +4,7 @@ import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DragManager } from "../../util/DragManager";
import { Docs, DocumentOptions } from "../../documents/Documents";
import { RouteStore } from "../../../server/RouteStore";
-import { TupleField } from "../../../fields/TupleField";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
-import { NumberField } from "../../../fields/NumberField";
-import { ServerUtils } from "../../../server/ServerUtil";
-import { Server } from "../../Server";
import { FieldViewProps } from "../nodes/FieldView";
import * as rp from 'request-promise';
import { CollectionView } from "./CollectionView";
@@ -85,7 +81,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
if (de.data instanceof DragManager.DocumentDragData) {
if (de.data.aliasOnDrop || de.data.copyOnDrop) {
["width", "height", "curPage"].map(key =>
- de.data.draggedDocuments.map((draggedDocument: Document, i: number) =>
+ de.data.draggedDocuments.map((draggedDocument: Doc, i: number) =>
PromiseValue(Cast(draggedDocument[key], "number")).then(f => f && (de.data.droppedDocuments[i][key] = f))));
}
let added = false;
@@ -148,7 +144,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
return undefined;
}
ctor = Docs.WebDocument;
- options = { height: options.width, ...options, title: path };
+ options = { height: options.width, ...options, title: path, nativeWidth: undefined };
}
return ctor ? ctor(path, options) : undefined;
}
@@ -166,9 +162,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
e.preventDefault();
if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
- console.log("not good");
- let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300 });
- htmlDoc.documentText = text;
+ let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
this.props.addDocument(htmlDoc, false);
return;
}
@@ -182,7 +176,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
let str: string;
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(action((s: string) => rp.head(DocServer.prepend(RouteStore.corsProxy + "/" + (str = s)))))
.then(result => {
let type = result.headers["content-type"];
if (type) {
@@ -210,15 +204,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
let path = window.location.origin + file;
let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
- docPromise.then(async doc => {
- let docs = await Cast(this.props.Document[this.props.fieldKey], listSpec(Doc));
- if (!docs) {
- this.props.Document[this.props.fieldKey] = docs = new List();
- }
- if (doc) {
- docs.push(doc);
- }
- });
+ docPromise.then(doc => doc && this.props.addDocument(doc));
}));
});
promises.push(prom);
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index e0387f4b4..905b48db7 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -136,7 +136,10 @@ 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}>
+ <div id="body" className="collectionTreeView-dropTarget"
+ style={{ borderRadius: "inherit" }}
+ 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/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 29fb342c6..779dc8fc3 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -37,10 +37,13 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
]);
}
+ _ele: HTMLDivElement | null = null;
@action
mainCont = (ele: HTMLDivElement | null) => {
+ this._ele = ele;
if (ele) {
this._player = ele.getElementsByTagName("video")[0];
+ console.log(this._player);
if (this.props.Document.GetNumber(KeyStore.CurPage, -1) >= 0) {
this._currentTimecode = this.props.Document.GetNumber(KeyStore.CurPage, -1);
}
@@ -57,9 +60,12 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
@action
updateTimecode = () => {
+ this._player = this._player ? this._player : this._ele ? this._ele.getElementsByTagName("video")[0] : undefined;
if (this._player) {
- if ((this._player as any).AHackBecauseSomethingResetsTheVideoToZero !== -1) {
- this._player.currentTime = (this._player as any).AHackBecauseSomethingResetsTheVideoToZero;
+ let timecode = (this._player as any).hasOwnProperty("AHackBecauseSomethingResetsTheVideoToZero") ?
+ (this._player as any).AHackBecauseSomethingResetsTheVideoToZero : -1;
+ if (timecode !== -1 && Object) {
+ this._player.currentTime = timecode;
(this._player as any).AHackBecauseSomethingResetsTheVideoToZero = -1;
} else {
this._currentTimecode = this._player.currentTime;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index cd74d3a84..ebdb0c75c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -18,7 +18,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
componentDidMount() {
this._brushReactionDisposer = reaction(() => this.props.Document.GetList(this.props.fieldKey, [] as Document[]).map(doc => doc.GetNumber(KeyStore.X, 0)),
() => {
- let views = this.props.Document.GetList(this.props.fieldKey, [] as Document[]);
+ let views = this.props.Document.GetList(this.props.fieldKey, [] as Document[]).filter(doc => doc.GetText(KeyStore.BackgroundLayout, "").indexOf("istogram") !== -1);
for (let i = 0; i < views.length; i++) {
for (let j = 0; j < views.length; j++) {
let srcDoc = views[j];
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 57706b51e..67a0e532c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -6,6 +6,7 @@
width: 100%;
height: 100%;
transform-origin: left top;
+ pointer-events: none;
}
.collectionfreeformview-container {
.collectionfreeformview > .jsx-parser {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3a9c9780b..80900c450 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,4 +1,4 @@
-import { action, computed, observable, trace } from "mobx";
+import { action, computed } from "mobx";
import { observer } from "mobx-react";
import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";
import { DocumentManager } from "../../../util/DocumentManager";
@@ -8,7 +8,6 @@ import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
import { InkingCanvas } from "../../InkingCanvas";
-import { MainOverlayTextBox } from "../../MainOverlayTextBox";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../../nodes/DocumentContentsView";
import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
@@ -21,7 +20,7 @@ import React = require("react");
import v5 = require("uuid/v5");
import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
import { Doc, Id } from "../../../../new_fields/Doc";
-import { FieldValue, Cast } from "../../../../new_fields/Types";
+import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types";
import { pageSchema } from "../../nodes/ImageBox";
import { List } from "../../../../new_fields/List";
@@ -36,6 +35,7 @@ const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema)
@observer
export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
+ public static RIGHT_BTN_DRAG = false;
private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
private _lastX: number = 0;
private _lastY: number = 0;
@@ -46,7 +46,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
- private childViews = () => this.views;
private panX = () => FieldValue(this.Document.panX, 0);
private panY = () => FieldValue(this.Document.panY, 0);
private zoomScaling = () => FieldValue(this.Document.scale, 1);
@@ -60,10 +59,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.addDocument(newBox, false);
}
private addDocument = (newBox: Doc, allowDuplicates: boolean) => {
- newBox.zoom = FieldValue(this.Document.scale, 1);
- return this.props.addDocument(this.bringToFront(newBox), false);
+ this.props.addDocument(newBox, false);
+ this.bringToFront(newBox);
+ return true;
}
- private selectDocuments = (docs: Document[]) => {
+ private selectDocuments = (docs: Doc[]) => {
SelectionManager.DeselectAll;
docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
SelectionManager.SelectDoc(dv!, true));
@@ -80,23 +80,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
drop = (e: Event, de: DragManager.DropEvent) => {
if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) {
- const [x, y] = this.getTransform().transformPoint(de.x - de.data.xOffset, de.y - de.data.yOffset);
if (de.data.droppedDocuments.length) {
let dragDoc = de.data.droppedDocuments[0];
- let dropX = Cast(dragDoc.x, "number", 0);
- let dropY = Cast(dragDoc.y, "number", 0);
+ let zoom = NumCast(dragDoc.zoomBasis, 1);
+ let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+ let x = xp - de.data.xOffset / zoom;
+ let y = yp - de.data.yOffset / zoom;
+ let dropX = NumCast(de.data.droppedDocuments[0].x);
+ let dropY = NumCast(de.data.droppedDocuments[0].y);
de.data.droppedDocuments.map(d => {
- d.x = x + Cast(d.x, "number", 0) - dropX;
- d.y = y + Cast(d.y, "number", 0) - dropY;
- if (!Cast(d.isMinimized, "boolean", false)) {
- if (!Cast(d.width, "number", 0)) {
- d.width = 300;
- }
- if (!Cast(d.height, "number", 0)) {
- let nw = Cast(d.nativeWidth, "number", 0);
- let nh = Cast(d.nativeHeight, "number", 0);
- d.height = nw && nh ? nh / nw * Cast(d.width, "number", 0) : 300;
- }
+ d.x = x + NumCast(d.x) - dropX;
+ d.y = y + NumCast(d.y) - dropY;
+ if (!NumCast(d.width)) {
+ d.width = 300;
+ }
+ if (!NumCast(d.height)) {
+ let nw = NumCast(d.nativeWidth);
+ let nh = NumCast(d.nativeHeight);
+ d.height = nw && nh ? nh / nw * d.Width() : 300;
}
this.bringToFront(d);
});
@@ -119,20 +120,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
var dv = DocumentManager.Instance.getDocumentView(doc);
return childSelected || (dv && SelectionManager.IsSelected(dv) ? true : false);
}, false);
- if (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) || (e.button === 0 && e.altKey)) && (childSelected || this.props.active())) {
- document.removeEventListener("pointermove", this.onPointerMove);
+ if ((CollectionFreeFormView.RIGHT_BTN_DRAG &&
+ (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) ||
+ (e.button === 0 && e.altKey)) && (childSelected || this.props.active()))) ||
+ (!CollectionFreeFormView.RIGHT_BTN_DRAG &&
+ ((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && (childSelected || this.props.active())))) {
+ this.cleanupInteractions();
document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
this._lastX = e.pageX;
this._lastY = e.pageY;
}
}
- @action
onPointerUp = (e: PointerEvent): void => {
- e.stopPropagation();
-
this.cleanupInteractions();
}
@@ -166,7 +167,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
this.setPan(x - dx, y - dy);
this._lastX = e.pageX;
this._lastY = e.pageY;
- e.stopPropagation();
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
e.preventDefault();
}
}
@@ -196,10 +197,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// if (modes[e.deltaMode] === 'pixels') coefficient = 50;
// else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??
let deltaScale = (1 - (e.deltaY / coefficient));
- if (deltaScale < 0) deltaScale = -deltaScale;
if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
deltaScale = 1 / this.zoomScaling();
}
+ if (deltaScale < 0) deltaScale = -deltaScale;
let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY);
let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
@@ -212,7 +213,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
setPan(panX: number, panY: number) {
- MainOverlayTextBox.Instance.SetTextDoc();
var scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
@@ -229,13 +229,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
onDragOver = (): void => {
}
- @action
bringToFront(doc: Doc) {
- (this.children || []).slice().sort((doc1, doc2) => {
+ const docs = (this.children || []);
+ docs.slice().sort((doc1, doc2) => {
if (doc1 === doc) return 1;
if (doc2 === doc) return -1;
- return Cast(doc1.zIndex, "number", 0) - Cast(doc2.zIndex, "number", 0);
+ return NumCast(doc1.zIndex) - NumCast(doc2.zIndex);
}).forEach((doc, index) => doc.zIndex = index + 1);
+ doc.zIndex = docs.length + 1;
return doc;
}
@@ -249,6 +250,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
getDocumentViewProps(document: Doc): DocumentViewProps {
return {
Document: document,
+ toggleMinimized: emptyFunction,
addDocument: this.props.addDocument,
removeDocument: this.props.removeDocument,
moveDocument: this.props.moveDocument,
@@ -289,17 +291,23 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
+ private childViews = () => [...this.views, <CollectionFreeFormBackgroundView key="backgroundView" {...this.getDocumentViewProps(this.props.Document)} />];
render() {
const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
return (
<div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
+ style={{ borderRadius: "inherit" }}
onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
+ {/* <svg viewBox="0 0 180 18" style={{ top: "50%", opacity: 0.05, position: "absolute" }}>
+ <text y="15" >
+ {this.props.Document.Title}
+ </text>
+ </svg> */}
<MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
<CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY}
zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
- <CollectionFreeFormBackgroundView {...this.getDocumentViewProps(this.props.Document)} />
<CollectionFreeFormLinksView {...this.props} key="freeformLinks">
<InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
{this.childViews}
@@ -356,7 +364,7 @@ class CollectionFreeFormViewPannableContents extends React.Component<CollectionF
const panx = -this.props.panX();
const pany = -this.props.panY();
const zoom = this.props.zoomScaling();// needs to be a variable outside of the <Measure> otherwise, reactions won't fire
- return <div className="collectionfreeformview" style={{ transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
+ return <div className="collectionfreeformview" style={{ borderRadius: "inherit", transform: `translate(${cenx}px, ${ceny}px) scale(${zoom}, ${zoom}) translate(${panx}px, ${pany}px)` }}>
{this.props.children}
</div>;
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index bf918beba..c6681e014 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -11,9 +11,9 @@ import { undoBatch } from "../../../util/UndoManager";
import { InkingCanvas } from "../../InkingCanvas";
import { PreviewCursor } from "../../PreviewCursor";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
-import { MINIMIZED_ICON_SIZE } from '../../../views/globalCssVariables.scss'
import "./MarqueeView.scss";
import React = require("react");
+import { Utils } from "../../../../Utils";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -33,18 +33,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@observable _lastY: number = 0;
@observable _downX: number = 0;
@observable _downY: number = 0;
- @observable _used: boolean = false;
@observable _visible: boolean = false;
- _showOnUp: boolean = false;
- static DRAG_THRESHOLD = 4;
@action
cleanupInteractions = (all: boolean = false) => {
if (all) {
document.removeEventListener("pointermove", this.onPointerMove, true);
document.removeEventListener("pointerup", this.onPointerUp, true);
- } else {
- this._used = true;
}
document.removeEventListener("keydown", this.marqueeCommand, true);
this._visible = false;
@@ -52,34 +47,25 @@ 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);
+ //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);
+ e.stopPropagation();
}
@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);
+ this._downX = this._lastX = e.pageX;
+ this._downY = this._lastY = e.pageY;
+ PreviewCursor.Visible = false;
+ if ((CollectionFreeFormView.RIGHT_BTN_DRAG && e.button === 0 && !e.altKey && !e.metaKey && this.props.container.props.active()) ||
+ (!CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && this.props.container.props.active())) {
document.addEventListener("pointermove", this.onPointerMove, true);
document.addEventListener("pointerup", this.onPointerUp, true);
document.addEventListener("keydown", this.marqueeCommand, true);
}
+ if (e.altKey)
+ e.preventDefault();
}
@action
@@ -87,33 +73,41 @@ 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)) {
+ if (Math.abs(this._lastX - this._downX) > Utils.DRAG_THRESHOLD ||
+ Math.abs(this._lastY - this._downY) > Utils.DRAG_THRESHOLD) {
this._visible = true;
+ e.stopPropagation();
+ e.preventDefault();
}
- e.stopPropagation();
- e.preventDefault();
}
+ if (e.altKey)
+ e.preventDefault();
}
@action
onPointerUp = (e: PointerEvent): void => {
- this.cleanupInteractions(true);
- this._visible = false;
- if (this._showOnUp) {
- PreviewCursor.Show(this.hideCursor, this._downX, this._downY);
- document.addEventListener("keypress", this.onKeyPress, false);
- } else {
+ if (this._visible) {
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.cleanupInteractions(true);
+ if (e.altKey)
+ e.preventDefault();
+ }
+
+ @action
+ onClick = (e: React.MouseEvent): void => {
+ if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ PreviewCursor.Show(e.clientX, e.clientY, this.onKeyPress);
+ // let the DocumentView stopPropagation of this event when it selects this document
+ } else { // why do we get a click event when the cursor have moved a big distance?
+ // let's cut it off here so no one else has to deal with it.
+ e.stopPropagation();
+ }
}
intersectRect(r1: { left: number, top: number, width: number, height: number },
@@ -133,15 +127,17 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@undoBatch
@action
marqueeCommand = (e: KeyboardEvent) => {
- if (e.key === "Backspace" || e.key === "Delete") {
+ if (e.key === "Backspace" || e.key === "Delete" || e.key == "d") {
this.marqueeSelect().map(d => this.props.removeDocument(d));
let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
if (ink && ink !== FieldWaiting) {
this.marqueeInkDelete(ink.Data);
}
- this.cleanupInteractions();
+ this.cleanupInteractions(true);
+ e.stopPropagation();
}
- if (e.key === "c") {
+ if (e.key === "c" || e.key === "r" || e.key === "e") {
+ e.stopPropagation();
let bounds = this.Bounds;
let selected = this.marqueeSelect().map(d => {
this.props.removeDocument(d);
@@ -152,22 +148,45 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
});
let ink = this.props.container.props.Document.GetT(KeyStore.Ink, InkField);
let inkData = ink && ink !== FieldWaiting ? ink.Data : undefined;
- //setTimeout(() => {
+ let zoomBasis = this.props.container.props.Document.GetNumber(KeyStore.Scale, 1);
let newCollection = Documents.FreeformDocument(selected, {
x: bounds.left,
y: bounds.top,
panx: 0,
pany: 0,
- width: bounds.width,
- height: bounds.height,
+ borderRounding: e.key === "e" ? -1 : undefined,
+ backgroundColor: selected.length ? "white" : "",
+ scale: zoomBasis,
+ width: bounds.width * zoomBasis,
+ height: bounds.height * zoomBasis,
ink: inkData ? this.marqueeInkSelect(inkData) : undefined,
title: "a nested collection"
});
- this.props.addDocument(newCollection, false);
+
this.marqueeInkDelete(inkData);
- // }, 100);
- this.cleanupInteractions();
SelectionManager.DeselectAll();
+ if (e.key === "r") {
+ let summary = Documents.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+ summary.GetPrototype()!.CreateLink(newCollection.GetPrototype()!);
+ this.props.addLiveTextDocument(summary);
+ e.preventDefault();
+ }
+ else {
+ this.props.addDocument(newCollection, false);
+ }
+ this.cleanupInteractions(true);
+ }
+ if (e.key === "s") {
+ e.stopPropagation();
+ e.preventDefault();
+ let bounds = this.Bounds;
+ let selected = this.marqueeSelect();
+ SelectionManager.DeselectAll();
+ let summary = Documents.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+ this.props.addLiveTextDocument(summary);
+ selected.map(select => summary.GetPrototype()!.CreateLink(select.GetPrototype()!));
+
+ this.cleanupInteractions(true);
}
}
@action
@@ -208,7 +227,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let selRect = this.Bounds;
let selection: Document[] = [];
this.props.activeDocuments().map(doc => {
- var z = doc.GetNumber(KeyStore.Zoom, 1);
+ var z = doc.GetNumber(KeyStore.ZoomBasis, 1);
var x = doc.GetNumber(KeyStore.X, 0);
var y = doc.GetNumber(KeyStore.Y, 0);
var w = doc.Width() / z;
@@ -230,7 +249,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
render() {
- return <div className="marqueeView" onPointerDown={this.onPointerDown}>
+ return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
{this.props.children}
{!this._visible ? (null) : this.marqueeDiv}
</div>;
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 7b7b7e65e..376af0b36 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,13 +1,16 @@
-import { computed, trace } from "mobx";
+import { computed, trace, action } from "mobx";
import { observer } from "mobx-react";
import { Transform } from "../../util/Transform";
import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
import "./DocumentView.scss";
import React = require("react");
-import { OmitKeys } from "../../../Utils";
import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { FieldValue } from "../../../new_fields/Types";
+import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
+import { FieldValue, Cast, NumCast } from "../../../new_fields/Types";
+import { OmitKeys, Utils } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
+import { matchedData } from "express-validator/filter";
+import { Doc } from "../../../new_fields/Doc";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
}
@@ -23,18 +26,21 @@ const FreeformDocument = makeInterface(schema, positionSchema);
@observer
export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeFormDocumentViewProps, FreeformDocument>(FreeformDocument) {
private _mainCont = React.createRef<HTMLDivElement>();
+ private _downX: number = 0;
+ private _downY: number = 0;
- @computed
- get transform(): string {
+ @computed get transform() {
return `scale(${this.props.ContentScaling()}, ${this.props.ContentScaling()}) translate(${this.X}px, ${this.Y}px) scale(${this.zoom}, ${this.zoom}) `;
}
+ @computed get X() { return FieldValue(this.Document.x, 0); }
+ @computed get Y() { return FieldValue(this.Document.y, 0); }
@computed get zoom(): number { return 1 / FieldValue(this.Document.zoom, 1); }
- @computed get zIndex(): number { return FieldValue(this.Document.zIndex, 0); }
- @computed get width(): number { return FieldValue(this.Document.width, 0); }
- @computed get height(): number { return FieldValue(this.Document.height, 0); }
@computed get nativeWidth(): number { return FieldValue(this.Document.nativeWidth, 0); }
@computed get nativeHeight(): number { return FieldValue(this.Document.nativeHeight, 0); }
+ @computed get width(): number { return FieldValue(this.Document.width, 0); }
+ @computed get height(): number { return FieldValue(this.Document.height, 0); }
+ @computed get zIndex(): number { return FieldValue(this.Document.zIndex, 0); }
set width(w: number) {
this.Document.width = w;
@@ -42,36 +48,28 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
this.Document.height = this.nativeHeight / this.nativeWidth * w;
}
}
-
set height(h: number) {
this.Document.height = h;
if (this.nativeWidth && this.nativeHeight) {
this.Document.width = this.nativeWidth / this.nativeHeight * h;
}
}
-
set zIndex(h: number) {
this.Document.zIndex = h;
}
- get X() {
- return FieldValue(this.Document.x, 0);
- }
- get Y() {
- return FieldValue(this.Document.y, 0);
- }
- getTransform = (): Transform =>
- this.props.ScreenToLocalTransform()
- .translate(-this.X, -this.Y)
- .scale(1 / this.contentScaling()).scale(1 / this.zoom)
-
contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
panelWidth = () => this.props.PanelWidth();
panelHeight = () => this.props.PanelHeight();
+ toggleMinimized = () => this.toggleIcon();
+ getTransform = (): Transform => this.props.ScreenToLocalTransform()
+ .translate(-this.X, -this.Y)
+ .scale(1 / this.contentScaling()).scale(1 / this.zoom)
@computed
get docView() {
return <DocumentView {...OmitKeys(this.props, ['zoomFade']).omit}
+ toggleMinimized={this.toggleMinimized}
ContentScaling={this.contentScaling}
ScreenToLocalTransform={this.getTransform}
PanelWidth={this.panelWidth}
@@ -79,30 +77,121 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
/>;
}
+ animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, target: Doc, maximizing: boolean) {
+ setTimeout(() => {
+ let now = Date.now();
+ let progress = Math.min(1, (now - stime) / 200);
+ let pval = maximizing ?
+ [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] :
+ [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress];
+ target.width = maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress;
+ target.height = maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress;
+ target.x = pval[0];
+ target.y = pval[1];
+ if (first) {
+ target.isMinimized = false;
+ }
+ if (now < stime + 200) {
+ this.animateBetweenIcon(false, icon, targ, width, height, stime, target, maximizing);
+ }
+ else {
+ if (!maximizing) {
+ target.isMinimized = true;
+ target.x = targ[0];
+ target.y = targ[1];
+ target.width = width;
+ target.height = height;
+ }
+ (target as any).isIconAnimating = false;
+ }
+ },
+ 2);
+ }
+ @action
+ public toggleIcon = async (): Promise<void> => {
+ SelectionManager.DeselectAll();
+ let isMinimized: boolean | undefined;
+ let minimizedDocSet = Cast(this.props.Document.linkTags, listSpec(Doc));
+ if (!minimizedDocSet) return;
+ minimizedDocSet.map(async minimizedDoc => {
+ if (minimizedDoc instanceof Document) {
+ this.props.addDocument && this.props.addDocument(minimizedDoc, false);
+ let maximizedDoc = await Cast(minimizedDoc.maximizedDoc, Doc);
+ if (maximizedDoc && !(maximizedDoc as any).isIconAnimating) {
+ (maximizedDoc as any).isIconAnimating = true;
+ if (isMinimized === undefined) {
+ let maximizedDocMinimizedState = Cast(maximizedDoc.isMinimized, "boolean");
+ isMinimized = (maximizedDocMinimizedState) ? true : false;
+ }
+ let minx = NumCast(minimizedDoc.x, undefined);
+ let miny = NumCast(minimizedDoc.y, undefined);
+ let maxx = NumCast(maximizedDoc.x, undefined);
+ let maxy = NumCast(maximizedDoc.y, undefined);
+ let maxw = NumCast(maximizedDoc.width, undefined);
+ let maxh = NumCast(maximizedDoc.height, undefined);
+ if (minx !== undefined && miny !== undefined && maxx !== undefined && maxy !== undefined &&
+ maxw !== undefined && maxh !== undefined) {
+ this.animateBetweenIcon(true, [minx, miny], [maxx, maxy], maxw, maxh, Date.now(), maximizedDoc, isMinimized);
+ }
+ }
+
+ }
+ })
+ }
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ }
+ onClick = async (e: React.MouseEvent) => {
+ e.stopPropagation();
+ if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
+ const maxDoc = await Cast(this.props.Document.maximizedDoc, Doc);
+ if (maxDoc) { // bcz: need a better way to associate behaviors with click events on widget-documents
+ this.props.addDocument && this.props.addDocument(maxDoc, false);
+ this.toggleIcon();
+ }
+ }
+ }
+
+ borderRounding = () => {
+ let br = NumCast(this.props.Document.borderRounding);
+ return br >= 0 ? br :
+ NumCast(this.props.Document.nativeWidth) === 0 ?
+ Math.min(this.props.PanelWidth(), this.props.PanelHeight())
+ : Math.min(this.Document.nativeWidth || 0, this.Document.nativeHeight || 0);
+ }
+
render() {
+ let maximizedDoc = FieldValue(Cast(this.props.Document.maximizedDoc, Doc));
let zoomFade = 1;
- //var zoom = doc.GetNumber(KeyStore.Zoom, 1);
- // let transform = this.getTransform().scale(this.contentScaling()).inverse();
- // var [sptX, sptY] = transform.transformPoint(0, 0);
- // let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
- // let w = bptX - sptX;
- // //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
- // let fadeUp = .75 * 1800;
- // let fadeDown = .075 * 1800;
- // zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
+ //var zoom = doc.GetNumber(KeyStore.ZoomBasis, 1);
+ let transform = this.getTransform().scale(this.contentScaling()).inverse();
+ var [sptX, sptY] = transform.transformPoint(0, 0);
+ let [bptX, bptY] = transform.transformPoint(this.props.PanelWidth(), this.props.PanelHeight());
+ let w = bptX - sptX;
+ //zoomFade = area < 100 || area > 800 ? Math.max(0, Math.min(1, 2 - 5 * (zoom < this.scale ? this.scale / zoom : zoom / this.scale))) : 1;
+ const screenWidth = 1800;
+ let fadeUp = .75 * screenWidth;
+ let fadeDown = (maximizedDoc ? .0075 : .075) * screenWidth;
+ zoomFade = w < fadeDown /* || w > fadeUp */ ? Math.max(0, Math.min(1, 2 - (w < fadeDown ? fadeDown / w : w / fadeUp))) : 1;
return (
- <div className="collectionFreeFormDocumentView-container" ref={this._mainCont} style={{
- opacity: zoomFade,
- transformOrigin: "left top",
- transform: this.transform,
- pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
- width: this.width,
- height: this.height,
- position: "absolute",
- zIndex: this.zIndex,
- backgroundColor: "transparent"
- }} >
+ <div className="collectionFreeFormDocumentView-container" ref={this._mainCont}
+ onPointerDown={this.onPointerDown}
+ onClick={this.onClick}
+ style={{
+ opacity: zoomFade,
+ borderRadius: `${this.borderRounding()}px`,
+ transformOrigin: "left top",
+ transform: this.transform,
+ pointerEvents: (zoomFade < 0.09 ? "none" : "all"),
+ width: this.width,
+ height: this.height,
+ position: "absolute",
+ zIndex: this.zIndex,
+ backgroundColor: "transparent"
+ }} >
{this.docView}
</div>
);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index ce338e9a7..aabc1633e 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,8 +1,7 @@
import { action, computed, runInAction } from "mobx";
import { observer } from "mobx-react";
-import { ServerUtils } from "../../../server/ServerUtil";
import { emptyFunction, Utils } from "../../../Utils";
-import { Documents } from "../../documents/Documents";
+import { Docs } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager } from "../../util/DragManager";
import { SelectionManager } from "../../util/SelectionManager";
@@ -21,6 +20,10 @@ import { DocComponent } from "../DocComponent";
import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
import { FieldValue, Cast, PromiseValue } from "../../../new_fields/Types";
import { List } from "../../../new_fields/List";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { MarqueeView } from "../collections/collectionFreeForm/MarqueeView";
+import { DocServer } from "../../DocServer";
const linkSchema = createSchema({
title: "string",
@@ -48,6 +51,7 @@ export interface DocumentViewProps {
selectOnLoad: boolean;
parentActive: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
+ toggleMinimized: () => void;
}
const schema = createSchema({
@@ -83,39 +87,16 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
@computed get active(): boolean { return SelectionManager.IsSelected(this) || this.props.parentActive(); }
@computed get topMost(): boolean { return this.props.isTopMost; }
- onPointerDown = (e: React.PointerEvent): void => {
- this._downX = e.clientX;
- this._downY = e.clientY;
- if (e.button === 2 && !this.isSelected()) {
- return;
- }
- if (e.shiftKey && e.buttons === 2) {
- if (this.props.isTopMost) {
- this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
- } else {
- CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
- }
- e.stopPropagation();
- } else {
- if (this.active) {
- e.stopPropagation();
- document.removeEventListener("pointermove", this.onPointerMove);
- document.addEventListener("pointermove", this.onPointerMove);
- document.removeEventListener("pointerup", this.onPointerUp);
- document.addEventListener("pointerup", this.onPointerUp);
- }
- }
- }
-
+ @action
componentDidMount() {
if (this._mainCont.current) {
this._dropDisposer = DragManager.MakeDropTarget(this._mainCont.current, {
handlers: { drop: this.drop.bind(this) }
});
}
- runInAction(() => DocumentManager.Instance.DocumentViews.push(this));
+ DocumentManager.Instance.DocumentViews.push(this);
}
-
+ @action
componentDidUpdate() {
if (this._dropDisposer) {
this._dropDisposer();
@@ -126,21 +107,26 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
});
}
}
-
+ @action
componentWillUnmount() {
if (this._dropDisposer) {
this._dropDisposer();
}
- runInAction(() => DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1));
+ DocumentManager.Instance.DocumentViews.splice(DocumentManager.Instance.DocumentViews.indexOf(this), 1);
+ }
+
+ stopPropagation = (e: React.SyntheticEvent) => {
+ e.stopPropagation();
}
startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
if (this._mainCont.current) {
- const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
+ const [left, top] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).inverse().transformPoint(0, 0);
let dragData = new DragManager.DocumentDragData([this.props.Document]);
+ const [xoff, yoff] = this.props.ScreenToLocalTransform().scale(this.props.ContentScaling()).transformDirection(x - left, y - top);
dragData.aliasOnDrop = dropAliasOfDraggedDoc;
- dragData.xOffset = x - left;
- dragData.yOffset = y - top;
+ dragData.xOffset = xoff;
+ dragData.yOffset = yoff;
dragData.moveDocument = this.props.moveDocument;
DragManager.StartDocumentDrag([this._mainCont.current], dragData, x, y, {
handlers: {
@@ -151,49 +137,58 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
}
- onPointerMove = (e: PointerEvent): void => {
- if (e.cancelBubble) {
+ onClick = (e: React.MouseEvent): void => {
+ if (CurrentUserUtils.MainDocId !== this.props.Document.Id &&
+ (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD)) {
+ SelectionManager.SelectDoc(this, e.ctrlKey);
+ }
+ }
+ onPointerDown = (e: React.PointerEvent): void => {
+ this._downX = e.clientX;
+ this._downY = e.clientY;
+ if (CollectionFreeFormView.RIGHT_BTN_DRAG && (e.button === 2 || (e.button === 0 && e.altKey)) && !this.isSelected()) {
return;
}
- if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ if (e.shiftKey && e.buttons === 2) {
+ if (this.props.isTopMost) {
+ this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey);
+ } else {
+ CollectionDockingView.Instance.StartOtherDrag([this.props.Document], e);
+ }
+ e.stopPropagation();
+ } else if (this.active) {
document.removeEventListener("pointermove", this.onPointerMove);
+ document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- if (!e.altKey && (!this.topMost || e.buttons === 2)) {
- this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey);
+ document.addEventListener("pointerup", this.onPointerUp);
+ e.preventDefault();
+ }
+ }
+ onPointerMove = (e: PointerEvent): void => {
+ if (!e.cancelBubble) {
+ if (Math.abs(this._downX - e.clientX) > 3 || Math.abs(this._downY - e.clientY) > 3) {
+ document.removeEventListener("pointermove", this.onPointerMove);
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (!e.altKey && !this.topMost && (!CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 1) || (CollectionFreeFormView.RIGHT_BTN_DRAG && e.buttons === 2)) {
+ this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey);
+ }
}
+ e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers
+ e.preventDefault();
}
- e.stopPropagation();
- e.preventDefault();
}
onPointerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
- e.stopPropagation();
- if (!SelectionManager.IsSelected(this) && e.button !== 2) {
- if (Math.abs(e.clientX - this._downX) < 4 && Math.abs(e.clientY - this._downY) < 4) {
- PromiseValue(Cast(this.props.Document.maximizedDoc, Doc)).then(maxdoc => {
- if (maxdoc instanceof Doc) {
- this.props.addDocument && this.props.addDocument(maxdoc, false);
- this.toggleMinimize(maxdoc, this.props.Document);
- } else {
- SelectionManager.SelectDoc(this, e.ctrlKey);
- }
- });
- }
- }
- }
- stopPropagation = (e: React.SyntheticEvent) => {
- e.stopPropagation();
}
deleteClicked = (): void => {
this.props.removeDocument && this.props.removeDocument(this.props.Document);
}
-
fieldsClicked = (e: React.MouseEvent): void => {
- if (this.props.addDocument) {
- this.props.addDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
- }
+ let kvp = Docs.KVPDocument(this.props.Document, { width: 300, height: 300 });
+ CollectionDockingView.Instance.AddRightSplit(kvp);
}
fullScreenClicked = (e: React.MouseEvent): void => {
const doc = Doc.MakeDelegate(FieldValue(this.Document.proto));
@@ -204,7 +199,6 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ContextMenu.Instance.addItem({ description: "Close Full Screen", event: this.closeFullScreenClicked });
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
-
closeFullScreenClicked = (e: React.MouseEvent): void => {
CollectionDockingView.Instance.CloseFullScreen();
ContextMenu.Instance.clearItems();
@@ -212,118 +206,21 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
- @action createIcon = (layoutString: string): Doc => {
- let iconDoc: Doc = Documents.IconDocument(layoutString);
- iconDoc.isMinimized = false;
- iconDoc.nativeWidth = 0;
- iconDoc.nativeHeight = 0;
- iconDoc.proto = this.props.Document;
- iconDoc.maximizedDoc = this.props.Document;
- this.Document.minimizedDoc = iconDoc;
- this.props.addDocument && this.props.addDocument(iconDoc, false);
- return iconDoc;
- }
-
- animateTransition(icon: number[], targ: number[], width: number, height: number, stime: number, target: Doc, maximizing: boolean) {
- setTimeout(() => {
- let now = Date.now();
- let progress = Math.min(1, (now - stime) / 200);
- let pval = maximizing ?
- [icon[0] + (targ[0] - icon[0]) * progress, icon[1] + (targ[1] - icon[1]) * progress] :
- [targ[0] + (icon[0] - targ[0]) * progress, targ[1] + (icon[1] - targ[1]) * progress];
- target.width = maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress;
- target.height = maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress;
- target.x = pval[0];
- target.y = pval[1];
- if (now < stime + 200) {
- this.animateTransition(icon, targ, width, height, stime, target, maximizing);
- }
- else {
- if (!maximizing) {
- target.isMinimized = true;
- target.x = targ[0];
- target.y = targ[1];
- target.width = width;
- target.height = height;
- }
- this._completed = true;
- }
- },
- 2);
- }
-
- _completed = true;
-
- @action
- public toggleMinimize = (maximized: Doc, minim: Doc): void => {
- SelectionManager.DeselectAll();
- if (this._completed) {
- this._completed = false;
- let minimized = Cast(maximized.isMinimized, "boolean", false);
- maximized.isMinimized = false;
- this.animateTransition(
- [Cast(minim.x, "number", 0), Cast(minim.y, "number", 0)],
- [Cast(maximized.x, "number", 0), Cast(maximized.y, "number", 0)],
- Cast(maximized.width, "number", 0), Cast(maximized.width, "number", 0),
- Date.now(), maximized, minimized);
- }
- }
-
- @action
- public minimize = async (): Promise<void> => {
- const mindoc = await Cast(this.props.Document.minimizedDoc, Doc);
- if (mindoc === undefined) {
- const background = await Cast(this.props.Document.backgroundLayout, "string");
- if (background === undefined) {
- const layout = await Cast(this.props.Document.layout, "string");
- if (layout) {
- this.createIcon(layout);
- this.toggleMinimize(this.props.Document, this.createIcon(layout));
- }
- } else {
- this.toggleMinimize(this.props.Document, this.createIcon(background));
- }
- } else {
- this.props.addDocument && this.props.addDocument(mindoc, false);
- this.toggleMinimize(this.props.Document, mindoc);
- }
- }
-
@undoBatch
@action
drop = async (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.LinkDragData) {
- let sourceDoc: Doc = de.data.linkSourceDocument;
- let destDoc: Doc = this.props.Document;
- let linkDoc = LinkDoc();
-
- const protoDest = await Cast(destDoc.proto, Doc);
- const protoSrc = await Cast(sourceDoc.proto, Doc);
- UndoManager.RunInBatch(() => {
- linkDoc.title = "New Link";
- linkDoc.linkDescription = "";
- linkDoc.linkTags = "Default";
+ let sourceDoc = de.data.linkSourceDocument;
+ let destDoc = this.props.Document;
- let dstTarg = protoDest ? protoDest : destDoc;
- let srcTarg = protoSrc ? protoSrc : sourceDoc;
- linkDoc.linkedTo = dstTarg;
- linkDoc.linkedFrom = srcTarg;
- let linkedFrom = Cast(dstTarg.linkedFrom, listSpec(Doc));
- if (!linkedFrom) {
- dstTarg.linkedFrom = linkedFrom = new List<Doc>();
- }
- linkedFrom.push(linkDoc);
-
- let linkedTo = Cast(srcTarg.linkedTo, listSpec(Doc));
- if (!linkedTo) {
- srcTarg.linkedTo = linkedTo = new List<Doc>();
- }
- linkedTo.push(linkDoc);
- }, "document view drop");
+ const protoDest = destDoc.proto;
+ const protoSrc = sourceDoc.proto;
+ Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
e.stopPropagation();
}
}
+ @action
onDrop = (e: React.DragEvent) => {
let text = e.dataTransfer.getData("text/plain");
if (!e.isDefaultPrevented() && text && text.startsWith("<div")) {
@@ -349,7 +246,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
ContextMenu.Instance.addItem({ description: "Fields", event: this.fieldsClicked });
ContextMenu.Instance.addItem({ description: "Center", event: () => this.props.focus(this.props.Document) });
ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document) });
- ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(ServerUtils.prepend("/doc/" + this.props.Document.Id)) });
+ ContextMenu.Instance.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document.Id)) });
ContextMenu.Instance.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]) });
//ContextMenu.Instance.addItem({ description: "Docking", event: () => this.props.Document.SetNumber(KeyStore.ViewType, CollectionViewType.Docking) })
ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked });
@@ -362,11 +259,10 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
isSelected = () => SelectionManager.IsSelected(this);
select = (ctrlPressed: boolean) => SelectionManager.SelectDoc(this, ctrlPressed);
- @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth) || 0; }
- @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight) || 0; }
+ @computed get nativeWidth() { return FieldValue(this.Document.nativeWidth, 0); }
+ @computed get nativeHeight() { return FieldValue(this.Document.nativeHeight, 0); }
@computed get contents() { return (<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={"layout"} />); }
-
render() {
var scaling = this.props.ContentScaling();
var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%";
@@ -376,11 +272,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
<div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
ref={this._mainCont}
style={{
+ borderRadius: "inherit",
background: FieldValue(this.Document.backgroundColor) || "",
width: nativeWidth, height: nativeHeight,
transform: `scale(${scaling}, ${scaling})`
}}
- onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown}
+ onDrop={this.onDrop} onContextMenu={this.onContextMenu} onPointerDown={this.onPointerDown} onClick={this.onClick}
>
{this.contents}
</div>
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index b11a87d75..c00c47fc4 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -11,11 +11,11 @@ import { returnFalse, emptyFunction } from "../../../Utils";
import { CollectionView } from "../collections/CollectionView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
-import { IconField } from "../../../fields/IconFIeld";
import { IconBox } from "./IconBox";
import { Opt, Doc, FieldResult } from "../../../new_fields/Doc";
import { List } from "../../../new_fields/List";
import { ImageField, VideoField, AudioField } from "../../../new_fields/URLField";
+import { IconField } from "../../../new_fields/IconField";
//
@@ -38,6 +38,8 @@ export interface FieldViewProps {
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
focus: (doc: Doc) => void;
+ PanelWidth: () => number;
+ PanelHeight: () => number;
}
@observer
@@ -91,6 +93,7 @@ export class FieldView extends React.Component<FieldViewProps> {
layoutKey={"layout"}
ContainingCollectionView={this.props.ContainingCollectionView}
parentActive={this.props.active}
+ toggleMinimized={emptyFunction}
whenActiveChanged={this.props.whenActiveChanged} />
);
}
@@ -106,7 +109,7 @@ export class FieldView extends React.Component<FieldViewProps> {
else if (typeof field === "number") {
return <p>{field}</p>;
}
- else if (field !== FieldWaiting) {
+ else if (!(field instanceof Promise)) {
return <p>{JSON.stringify(field)}</p>;
}
else {
diff --git a/src/client/views/nodes/FormattedTextBox.scss b/src/client/views/nodes/FormattedTextBox.scss
index 5eb2bf7ce..f4f37250f 100644
--- a/src/client/views/nodes/FormattedTextBox.scss
+++ b/src/client/views/nodes/FormattedTextBox.scss
@@ -12,9 +12,9 @@
.formattedTextBox-cont-scroll, .formattedTextBox-cont-hidden {
background: $light-color-secondary;
- padding: 0.9em;
+ padding: 0;
border-width: 0px;
- border-radius: $border-radius;
+ border-radius: inherit;
border-color: $intermediate-color;
box-sizing: border-box;
border-style: solid;
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index cb082dc69..c65b1ac89 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,4 +1,4 @@
-import { action, IReactionDisposer, reaction } from "mobx";
+import { action, IReactionDisposer, reaction, trace, computed } from "mobx";
import { baseKeymap } from "prosemirror-commands";
import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
@@ -18,6 +18,9 @@ import { SelectionManager } from "../../util/SelectionManager";
import { DocComponent } from "../DocComponent";
import { createSchema, makeInterface } from "../../../new_fields/Schema";
import { Opt, Doc } from "../../../new_fields/Doc";
+import { observer } from "mobx-react";
+import { InkingControl } from "../InkingControl";
+import { StrCast } from "../../../new_fields/Types";
const { buildMenuItems } = require("prosemirror-example-setup");
const { menuBar } = require("prosemirror-menu");
@@ -49,6 +52,7 @@ const richTextSchema = createSchema({
type RichTextDocument = makeInterface<[typeof richTextSchema]>;
const RichTextDocument = makeInterface(richTextSchema);
+@observer
export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTextBoxOverlay), RichTextDocument>(RichTextDocument) {
public static LayoutString(fieldStr: string = "DataKey") {
return FieldView.LayoutString(FormattedTextBox, fieldStr);
@@ -112,34 +116,26 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (this._editorView) {
this._editorView.destroy();
}
- this.setupEditor(config, MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox
+ this.setupEditor(config, this.props.Document);// MainOverlayTextBox.Instance.TextDoc); // bcz: not sure why, but the order of events is such that this.props.Document hasn't updated yet, so without forcing the editor to the MainOverlayTextBox, it will display the previously focused textbox
}
);
} else {
this._proxyReactionDisposer = reaction(() => this.props.isSelected(),
- () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform()));
+ () => this.props.isSelected() && MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform));
}
+
this._reactionDisposer = reaction(
() => {
const field = this.props.Document ? this.props.Document.GetT(this.props.fieldKey, RichTextField) : undefined;
return field && field !== FieldWaiting ? field.Data : undefined;
},
- field => {
- if (field && this._editorView && !this._applyingChange) {
- this._editorView.updateState(
- EditorState.fromJSON(config, JSON.parse(field))
- );
- }
- }
+ field => field && this._editorView && !this._applyingChange &&
+ this._editorView.updateState(EditorState.fromJSON(config, JSON.parse(field)))
);
this.setupEditor(config, this.props.Document);
}
- shouldComponentUpdate() {
- return false;
- }
-
private setupEditor(config: any, doc?: Document) {
let field = doc ? doc.GetT(this.props.fieldKey, RichTextField) : undefined;
if (this._ref.current) {
@@ -190,7 +186,9 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
onFocused = (e: React.FocusEvent): void => {
if (!this.props.isOverlay) {
- MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform());
+ if (MainOverlayTextBox.Instance.TextDoc !== this.props.Document) {
+ MainOverlayTextBox.Instance.SetTextDoc(this.props.Document, this.props.fieldKey, this._ref.current!, this.props.ScreenToLocalTransform);
+ }
} else {
if (this._ref.current) {
this._ref.current.scrollTop = MainOverlayTextBox.Instance.TextScroll;
@@ -231,6 +229,10 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
}
+ onClick = (e: React.MouseEvent): void => {
+ this._ref.current!.focus();
+ }
+
tooltipTextMenuPlugin() {
let myprops = this.props;
return new Plugin({
@@ -249,7 +251,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
});
}
- onKeyPress(e: React.KeyboardEvent) {
+ onKeyPress = (e: React.KeyboardEvent) => {
if (e.key === "Escape") {
SelectionManager.DeselectAll();
}
@@ -257,22 +259,33 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (e.keyCode === 9) e.preventDefault();
// stop propagation doesn't seem to stop propagation of native keyboard events.
// so we set a flag on the native event that marks that the event's been handled.
- // (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
+ (e.nativeEvent as any).DASHFormattedTextBoxHandled = true;
+ if (StrCast(this.props.Document.title).startsWith("-") && this._editorView) {
+ let str = this._editorView.state.doc.textContent;
+ let titlestr = str.substr(0, Math.min(40, str.length));
+ this.props.Document.title = "-" + titlestr + (str.length > 40 ? "..." : "");
+ }
}
render() {
- let style = this.props.isSelected() || this.props.isOverlay ? "scroll" : "hidden";
+ let style = this.props.isOverlay ? "scroll" : "hidden";
+ let color = StrCast(this.props.Document.backgroundColor);
+ let interactive = InkingControl.Instance.selectedTool ? "" : "interactive";
return (
<div className={`formattedTextBox-cont-${style}`}
+ style={{
+ pointerEvents: interactive ? "all" : "none",
+ background: color,
+ }}
onKeyDown={this.onKeyPress}
onKeyPress={this.onKeyPress}
onFocus={this.onFocused}
+ onClick={this.onClick}
onPointerUp={this.onPointerUp}
onPointerDown={this.onPointerDown}
onContextMenu={this.specificContextMenu}
// tfs: do we need this event handler
onWheel={this.onPointerWheel}
- ref={this._ref}
- />
+ ref={this._ref} />
);
}
}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index f4b3837ff..9fe211df0 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -6,6 +6,10 @@
height: auto;
max-width: 100%;
max-height: 100%;
+ pointer-events: none;
+}
+.imageBox-cont-interactive {
+ pointer-events: all;
}
.imageBox-dot {
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index cd003ba71..0e9e904a8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -10,12 +10,14 @@ import { ContextMenu } from "../../views/ContextMenu";
import { FieldView, FieldViewProps } from './FieldView';
import "./ImageBox.scss";
import React = require("react");
-import { createSchema, makeInterface } from '../../../new_fields/Schema';
+import { createSchema, makeInterface, listSpec } from '../../../new_fields/Schema';
import { DocComponent } from '../DocComponent';
import { positionSchema } from './DocumentView';
-import { FieldValue, Cast } from '../../../new_fields/Types';
+import { FieldValue, Cast, StrCast } from '../../../new_fields/Types';
import { ImageField } from '../../../new_fields/URLField';
import { List } from '../../../new_fields/List';
+import { InkingControl } from '../InkingControl';
+import { Doc } from '../../../new_fields/Doc';
export const pageSchema = createSchema({
curPage: "number"
@@ -28,7 +30,7 @@ const ImageDocument = makeInterface(pageSchema, positionSchema);
export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageDocument) {
public static LayoutString() { return FieldView.LayoutString(ImageBox); }
- private _imgRef: React.RefObject<HTMLImageElement>;
+ private _imgRef: React.RefObject<HTMLImageElement> = React.createRef();
private _downX: number = 0;
private _downY: number = 0;
private _lastTap: number = 0;
@@ -36,12 +38,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
@observable private _isOpen: boolean = false;
private dropDisposer?: DragManager.DragDropDisposer;
- constructor(props: FieldViewProps) {
- super(props);
-
- this._imgRef = React.createRef();
- }
-
@action
onLoad = (target: any) => {
var h = this._imgRef.current!.naturalHeight;
@@ -71,19 +67,18 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
@undoBatch
drop = (e: Event, de: DragManager.DropEvent) => {
if (de.data instanceof DragManager.DocumentDragData) {
- de.data.droppedDocuments.forEach(action((drop: Document) => {
- let layout = drop.GetText(KeyStore.BackgroundLayout, "");
+ de.data.droppedDocuments.forEach(action((drop: Doc) => {
+ let layout = StrCast(drop.backgroundLayout);
if (layout.indexOf(ImageBox.name) !== -1) {
- let imgData = this.props.Document.Get(KeyStore.Data);
- if (imgData instanceof ImageField && imgData) {
- this.props.Document.SetOnPrototype(KeyStore.Data, new ListField([imgData]));
+ let imgData = this.props.Document[this.props.fieldKey];
+ if (imgData instanceof ImageField) {
+ Doc.SetOnPrototype(this.props.Document, "data", new List([imgData]));
}
- let imgList = this.props.Document.GetList(KeyStore.Data, [] as any[]);
+ let imgList = Cast(this.props.Document[this.props.fieldKey], listSpec(ImageField), [] as any[]);
if (imgList) {
- let field = drop.Get(KeyStore.Data);
- if (field === FieldWaiting) { }
- else if (field instanceof ImageField) imgList.push(field);
- else if (field instanceof ListField) imgList.push(field.Data);
+ let field = drop.data;
+ if (field instanceof ImageField) imgList.push(field);
+ else if (field instanceof List) imgList.concat(field);
}
e.stopPropagation();
}
@@ -95,7 +90,6 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
onPointerDown = (e: React.PointerEvent): void => {
if (Date.now() - this._lastTap < 300) {
if (e.buttons === 1) {
- e.stopPropagation();
this._downX = e.clientX;
this._downY = e.clientY;
document.removeEventListener("pointerup", this.onPointerUp);
@@ -135,7 +129,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
specificContextMenu = (e: React.MouseEvent): void => {
let field = Cast(this.Document[this.props.fieldKey], ImageField);
- if (field && field !== FieldWaiting) {
+ if (field) {
let url = field.url.href;
ContextMenu.Instance.addItem({
description: "Copy path", event: () => {
@@ -165,12 +159,12 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
render() {
let field = this.Document[this.props.fieldKey];
let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
- if (field === FieldWaiting) paths = ["https://image.flaticon.com/icons/svg/66/66163.svg"];
- else if (field instanceof ImageField) paths = [field.url.href];
+ if (field instanceof ImageField) paths = [field.url.href];
else if (field instanceof List) paths = field.filter(val => val instanceof ImageField).map(p => (p as ImageField).url.href);
let nativeWidth = FieldValue(this.Document.nativeWidth, 1);
+ let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
return (
- <div className="imageBox-cont" onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+ <div className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
<img src={paths[Math.min(paths.length, this._photoIndex)]} style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }} width={nativeWidth} alt="Image not found" ref={this._imgRef} onLoad={this.onLoad} />
{paths.length > 1 ? this.dots(paths) : (null)}
{this.lightbox(paths)}
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 7b922b620..86276660a 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -2,7 +2,6 @@ import React = require("react");
import { observer } from "mobx-react";
import { FieldView, FieldViewProps } from './FieldView';
import "./VideoBox.scss";
-import Measure from "react-measure";
import { action, computed } from "mobx";
import { DocComponent } from "../DocComponent";
import { positionSchema } from "./DocumentView";
@@ -10,6 +9,8 @@ import { makeInterface } from "../../../new_fields/Schema";
import { pageSchema } from "./ImageBox";
import { Cast, FieldValue } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
+import Measure from "react-measure";
+import "./VideoBox.scss";
type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
const VideoDocument = makeInterface(positionSchema, pageSchema);
diff --git a/src/client/views/nodes/WebBox.tsx b/src/client/views/nodes/WebBox.tsx
index 63778fa50..c12fd5655 100644
--- a/src/client/views/nodes/WebBox.tsx
+++ b/src/client/views/nodes/WebBox.tsx
@@ -1,20 +1,17 @@
import "./WebBox.scss";
import React = require("react");
import { FieldViewProps, FieldView } from './FieldView';
-import { observer } from "mobx-react";
-import { computed } from 'mobx';
import { HtmlField } from "../../../new_fields/HtmlField";
import { WebField } from "../../../new_fields/URLField";
+import { observer } from "mobx-react";
+import { computed, reaction, IReactionDisposer } from 'mobx';
+import { DocumentDecorations } from "../DocumentDecorations";
@observer
export class WebBox extends React.Component<FieldViewProps> {
public static LayoutString() { return FieldView.LayoutString(WebBox); }
- constructor(props: FieldViewProps) {
- super(props);
- }
-
_ignore = 0;
onPreWheel = (e: React.WheelEvent) => {
this._ignore = e.timeStamp;
@@ -36,23 +33,25 @@ export class WebBox extends React.Component<FieldViewProps> {
let field = this.props.Document[this.props.fieldKey];
let view;
if (field instanceof HtmlField) {
- view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />
+ view = <span id="webBox-htmlSpan" dangerouslySetInnerHTML={{ __html: field.html }} />;
} else if (field instanceof WebField) {
- view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />
+ view = <iframe src={field.url.href} style={{ position: "absolute", width: "100%", height: "100%" }} />;
} else {
- view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />
+ view = <iframe src={"https://crossorigin.me/https://cs.brown.edu"} style={{ position: "absolute", width: "100%", height: "100%" }} />;
}
let content =
<div style={{ width: "100%", height: "100%", position: "absolute" }} onWheel={this.onPostWheel} onPointerDown={this.onPostPointer} onPointerMove={this.onPostPointer} onPointerUp={this.onPostPointer}>
{view}
</div>;
+ let frozen = !this.props.isSelected() || DocumentDecorations.Instance.Interacting;
+
return (
<>
<div className="webBox-cont" >
{content}
</div>
- {this.props.isSelected() ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />}
+ {!frozen ? (null) : <div onWheel={this.onPreWheel} onPointerDown={this.onPrePointer} onPointerMove={this.onPrePointer} onPointerUp={this.onPrePointer} style={{ width: "100%", height: "100%", position: "absolute" }} />}
</>);
}
} \ No newline at end of file
diff --git a/src/new_fields/Doc.ts b/src/new_fields/Doc.ts
index bd10e5474..79e5a156d 100644
--- a/src/new_fields/Doc.ts
+++ b/src/new_fields/Doc.ts
@@ -5,6 +5,9 @@ import { Utils } from "../Utils";
import { DocServer } from "../client/DocServer";
import { setter, getter, getField } from "./util";
import { Cast, ToConstructor, PromiseValue, FieldValue } from "./Types";
+import { UndoManager, undoBatch } from "../client/util/UndoManager";
+import { listSpec } from "./Schema";
+import { List } from "./List";
export type FieldId = string;
export const HandleUpdate = Symbol("HandleUpdate");
@@ -54,7 +57,7 @@ export class Doc extends RefField {
return doc;
}
- proto: FieldResult<Doc>;
+ proto: Opt<Doc>;
[key: string]: FieldResult;
@serializable(alias("fields", map(autoObject())))
@@ -126,6 +129,31 @@ export namespace Doc {
return alias;
}
+ export function MakeLink(source: Doc, target: Doc): Doc {
+ let linkDoc = new Doc;
+ UndoManager.RunInBatch(() => {
+ linkDoc.title = "New Link";
+ linkDoc.linkDescription = "";
+ linkDoc.linkTags = "Default";
+
+ linkDoc.linkedTo = target;
+ linkDoc.linkedFrom = source;
+
+ let linkedFrom = Cast(target.linkedFromDocs, listSpec(Doc));
+ if (!linkedFrom) {
+ target.linkedFromDocs = linkedFrom = new List<Doc>();
+ }
+ linkedFrom.push(linkDoc);
+
+ let linkedTo = Cast(source.linkedToDocs, listSpec(Doc));
+ if (!linkedTo) {
+ source.linkedToDocs = linkedTo = new List<Doc>();
+ }
+ linkedTo.push(linkDoc);
+ }, "make link");
+ return linkDoc;
+ }
+
export function MakeDelegate(doc: Doc): Doc;
export function MakeDelegate(doc: Opt<Doc>): Opt<Doc>;
export function MakeDelegate(doc: Opt<Doc>): Opt<Doc> {
diff --git a/src/new_fields/Types.ts b/src/new_fields/Types.ts
index 4ad8dcade..079c7b76d 100644
--- a/src/new_fields/Types.ts
+++ b/src/new_fields/Types.ts
@@ -59,6 +59,14 @@ export function Cast<T extends ToConstructor<Field> | ListSpec<Field>>(field: Fi
return defaultVal;
}
+export function NumCast(field: FieldResult, defaultVal: Opt<number> = 0) {
+ return Cast(field, "number", defaultVal);
+}
+
+export function StrCast(field: FieldResult, defaultVal: Opt<string> = "") {
+ return Cast(field, "string", defaultVal);
+}
+
type WithoutList<T extends Field> = T extends List<infer R> ? R[] : T;
export function FieldValue<T extends Field, U extends WithoutList<T>>(field: Opt<T> | Promise<Opt<T>>, defaultValue: U): WithoutList<T>;