aboutsummaryrefslogtreecommitdiff
path: root/src/client
diff options
context:
space:
mode:
Diffstat (limited to 'src/client')
-rw-r--r--src/client/Server.ts5
-rw-r--r--src/client/documents/Documents.ts6
-rw-r--r--src/client/util/DragManager.ts54
-rw-r--r--src/client/views/DocumentDecorations.tsx15
-rw-r--r--src/client/views/EditableView.tsx5
-rw-r--r--src/client/views/InkingControl.tsx3
-rw-r--r--src/client/views/Main.tsx400
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx2
-rw-r--r--src/client/views/collections/CollectionFreeFormView.scss4
-rw-r--r--src/client/views/collections/CollectionFreeFormView.tsx103
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx20
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx5
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx4
-rw-r--r--src/client/views/collections/CollectionView.tsx7
-rw-r--r--src/client/views/collections/CollectionViewBase.tsx78
-rw-r--r--src/client/views/collections/MarqueeView.tsx15
-rw-r--r--src/client/views/collections/PreviewCursor.tsx1
-rw-r--r--src/client/views/nodes/DocumentView.tsx58
-rw-r--r--src/client/views/nodes/ImageBox.tsx1
-rw-r--r--src/client/views/nodes/LinkBox.tsx3
-rw-r--r--src/client/views/nodes/PDFBox.tsx5
22 files changed, 537 insertions, 259 deletions
diff --git a/src/client/Server.ts b/src/client/Server.ts
index f2d7de75c..3fb1ae878 100644
--- a/src/client/Server.ts
+++ b/src/client/Server.ts
@@ -56,6 +56,7 @@ export class Server {
let field = this.ClientFieldsCached.get(id);
if (!field) {
neededFieldIds.push(id);
+ this.ClientFieldsCached.set(id, FieldWaiting);
} else if (field === FieldWaiting) {
waitingFieldIds.push(id);
} else {
@@ -65,7 +66,7 @@ export class Server {
SocketStub.SEND_FIELDS_REQUEST(neededFieldIds, (fields) => {
for (let key in fields) {
let field = fields[key];
- if (!this.ClientFieldsCached.has(field.Id)) {
+ if (!(this.ClientFieldsCached.get(field.Id) instanceof Field)) {
this.ClientFieldsCached.set(field.Id, field)
}
}
@@ -145,4 +146,4 @@ export class Server {
}
Server.Socket.on(MessageStore.Foo.Message, Server.connected);
-Server.Socket.on(MessageStore.SetField.Message, Server.updateField); \ No newline at end of file
+Server.Socket.on(MessageStore.SetField.Message, Server.updateField);
diff --git a/src/client/documents/Documents.ts b/src/client/documents/Documents.ts
index 3aa575dbb..fabdaad17 100644
--- a/src/client/documents/Documents.ts
+++ b/src/client/documents/Documents.ts
@@ -60,14 +60,14 @@ export namespace Documents {
const videoProtoId = "videoProto"
const audioProtoId = "audioProto";
- export function initProtos(mainDocId: string, callback: (mainDoc?: Document) => void) {
- Server.GetFields([collProtoId, textProtoId, imageProtoId, mainDocId], (fields) => {
+ export function initProtos(callback: () => void) {
+ Server.GetFields([collProtoId, textProtoId, imageProtoId], (fields) => {
collProto = fields[collProtoId] as Document;
imageProto = fields[imageProtoId] as Document;
textProto = fields[textProtoId] as Document;
webProto = fields[webProtoId] as Document;
kvpProto = fields[kvpProtoId] as Document;
- callback(fields[mainDocId] as Document)
+ callback();
});
}
function assignOptions(doc: Document, options: DocumentOptions): Document {
diff --git a/src/client/util/DragManager.ts b/src/client/util/DragManager.ts
index c0abec407..753115f76 100644
--- a/src/client/util/DragManager.ts
+++ b/src/client/util/DragManager.ts
@@ -2,18 +2,21 @@ import { DocumentDecorations } from "../views/DocumentDecorations";
import { CollectionDockingView } from "../views/collections/CollectionDockingView";
import { Document } from "../../fields/Document"
import { action } from "mobx";
-import { DocumentView } from "../views/nodes/DocumentView";
import { ImageField } from "../../fields/ImageField";
import { KeyStore } from "../../fields/KeyStore";
+import { CollectionView } from "../views/collections/CollectionView";
+import { DocumentView } from "../views/nodes/DocumentView";
-export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document) {
+export function setupDrag(_reference: React.RefObject<HTMLDivElement>, docFunc: () => Document, removeFunc: (containingCollection: CollectionView) => void = () => { }) {
let onRowMove = action((e: PointerEvent): void => {
e.stopPropagation();
e.preventDefault();
document.removeEventListener("pointermove", onRowMove);
document.removeEventListener('pointerup', onRowUp);
- DragManager.StartDrag(_reference.current!, { document: docFunc() });
+ var dragData = new DragManager.DocumentDragData(docFunc());
+ dragData.removeDocument = removeFunc;
+ DragManager.StartDocumentDrag(_reference.current!, dragData);
});
let onRowUp = action((e: PointerEvent): void => {
document.removeEventListener("pointermove", onRowMove);
@@ -70,11 +73,12 @@ export namespace DragManager {
export interface DropOptions {
handlers: DropHandlers;
}
-
export class DropEvent {
constructor(readonly x: number, readonly y: number, readonly data: { [id: string]: any }) { }
}
+
+
export interface DropHandlers {
drop: (e: Event, de: DropEvent) => void;
}
@@ -96,7 +100,34 @@ export namespace DragManager {
};
}
- export function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions) {
+ export class DocumentDragData {
+ constructor(dragDoc: Document) {
+ this.draggedDocument = dragDoc;
+ this.droppedDocument = dragDoc;
+ }
+ draggedDocument: Document;
+ droppedDocument: Document;
+ xOffset?: number;
+ yOffset?: number;
+ aliasOnDrop?: boolean;
+ removeDocument?: (collectionDrop: CollectionView) => void;
+ [id: string]: any;
+ }
+ export function StartDocumentDrag(ele: HTMLElement, dragData: DocumentDragData, options?: DragOptions) {
+ StartDrag(ele, dragData, options, (dropData: { [id: string]: any }) => dropData.droppedDocument = dragData.aliasOnDrop ? dragData.draggedDocument.CreateAlias() : dragData.draggedDocument);
+ }
+
+ export class LinkDragData {
+ constructor(linkSourceDoc: DocumentView) {
+ this.linkSourceDocumentView = linkSourceDoc;
+ }
+ linkSourceDocumentView: DocumentView;
+ [id: string]: any;
+ }
+ export function StartLinkDrag(ele: HTMLElement, dragData: LinkDragData, options?: DragOptions) {
+ StartDrag(ele, dragData, options);
+ }
+ function StartDrag(ele: HTMLElement, dragData: { [id: string]: any }, options?: DragOptions, finishDrag?: (dropData: { [id: string]: any }) => void) {
if (!dragDiv) {
dragDiv = document.createElement("div");
dragDiv.className = "dragManager-dragDiv"
@@ -122,9 +153,7 @@ export namespace DragManager {
// bcz: PDFs don't show up if you clone them because they contain a canvas.
// however, PDF's have a thumbnail field that contains an image of their canvas.
// So we replace the pdf's canvas with the image thumbnail
- const docView: DocumentView = dragData["documentView"];
- const doc: Document = dragData["document"];
-
+ const doc: Document = dragData["draggedDocument"];
if (doc) {
var pdfBox = dragElement.getElementsByClassName("pdfBox-cont")[0] as HTMLElement;
let thumbnail = doc.GetT(KeyStore.Thumbnail, ImageField);
@@ -138,7 +167,6 @@ export namespace DragManager {
}
}
-
dragDiv.appendChild(dragElement);
let hideSource = false;
@@ -175,13 +203,13 @@ export namespace DragManager {
}
const upHandler = (e: PointerEvent) => {
abortDrag();
- FinishDrag(ele, e, dragData, options);
+ FinishDrag(ele, e, dragData, options, finishDrag);
};
document.addEventListener("pointermove", moveHandler, true);
document.addEventListener("pointerup", upHandler);
}
- function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions) {
+ function FinishDrag(dragEle: HTMLElement, e: PointerEvent, dragData: { [index: string]: any }, options?: DragOptions, finishDrag?: (dragData: { [index: string]: any }) => void) {
let parent = dragEle.parentElement;
if (parent)
parent.removeChild(dragEle);
@@ -191,6 +219,9 @@ export namespace DragManager {
if (!target) {
return;
}
+ if (finishDrag)
+ finishDrag(dragData);
+
target.dispatchEvent(new CustomEvent<DropEvent>("dashOnDrop", {
bubbles: true,
detail: {
@@ -199,6 +230,7 @@ export namespace DragManager {
data: dragData
}
}));
+
if (options) {
options.handlers.dragComplete({});
}
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 3bdb7d5b3..a8090dc8f 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -77,7 +77,6 @@ export class DocumentDecorations extends React.Component {
}
onLinkButtonUp = (e: PointerEvent): void => {
- console.log("up");
document.removeEventListener("pointermove", this.onLinkButtonMoved)
document.removeEventListener("pointerup", this.onLinkButtonUp)
e.stopPropagation();
@@ -85,19 +84,17 @@ export class DocumentDecorations extends React.Component {
onLinkButtonMoved = (e: PointerEvent): void => {
- console.log("moved");
- let dragData: { [id: string]: any } = {};
- dragData["linkSourceDoc"] = SelectionManager.SelectedDocuments()[0];
if (this._linkButton.current != null) {
- DragManager.StartDrag(this._linkButton.current, dragData, {
+ document.removeEventListener("pointermove", this.onLinkButtonMoved)
+ document.removeEventListener("pointerup", this.onLinkButtonUp)
+ let dragData = new DragManager.LinkDragData(SelectionManager.SelectedDocuments()[0]);
+ DragManager.StartLinkDrag(this._linkButton.current, dragData, {
handlers: {
dragComplete: action(() => { }),
},
hideSource: false
})
}
- document.removeEventListener("pointermove", this.onLinkButtonMoved)
- document.removeEventListener("pointerup", this.onLinkButtonUp)
e.stopPropagation();
}
@@ -217,7 +214,7 @@ export class DocumentDecorations extends React.Component {
<LinkMenu docView={selFirst} changeFlyout={this.changeFlyoutContent}>
</LinkMenu>
}>
- <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} ref={this._linkButton}>{linkCount}</div>
+ <div className={"linkButton-" + (selFirst.props.Document.GetData(KeyStore.LinkedToDocs, ListField, []).length ? "nonempty" : "empty")} onPointerDown={this.onLinkButtonDown} >{linkCount}</div>
</Flyout>);
}
return (
@@ -237,7 +234,7 @@ export class DocumentDecorations extends React.Component {
<div id="documentDecorations-bottomResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
<div id="documentDecorations-bottomRightResizer" className="documentDecorations-resizer" onPointerDown={this.onPointerDown} onContextMenu={(e) => e.preventDefault()}></div>
- <div title="View Links" className="linkFlyout">{linkButton}</div>
+ <div title="View Links" className="linkFlyout" ref={this._linkButton}>{linkButton}</div>
</div >
)
diff --git a/src/client/views/EditableView.tsx b/src/client/views/EditableView.tsx
index 3b54c0dbb..98a6ed1ba 100644
--- a/src/client/views/EditableView.tsx
+++ b/src/client/views/EditableView.tsx
@@ -21,6 +21,7 @@ export interface EditableProps {
*/
contents: any;
height: number
+ display?: string;
}
/**
@@ -47,10 +48,10 @@ export class EditableView extends React.Component<EditableProps> {
render() {
if (this.editing) {
return <input defaultValue={this.props.GetValue()} onKeyDown={this.onKeyDown} autoFocus onBlur={action(() => this.editing = false)}
- style={{ display: "inline" }}></input>
+ style={{ display: this.props.display }}></input>
} else {
return (
- <div className="editableView-container-editing" style={{ display: "inline", height: "auto", maxHeight: `${this.props.height}` }}
+ <div className="editableView-container-editing" style={{ display: this.props.display, height: "auto", maxHeight: `${this.props.height}` }}
onClick={action(() => this.editing = true)}>
{this.props.contents}
</div>
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 6616f68d8..ad6bbd476 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -11,6 +11,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faPen, faHighlighter, faEraser, faBan } from '@fortawesome/free-solid-svg-icons';
import { SelectionManager } from "../util/SelectionManager";
import { KeyStore } from "../../fields/KeyStore";
+import { TextField } from "../../fields/TextField";
library.add(faPen, faHighlighter, faEraser, faBan);
@@ -39,7 +40,7 @@ export class InkingControl extends React.Component {
if (SelectionManager.SelectedDocuments().length == 1) {
var sdoc = SelectionManager.SelectedDocuments()[0];
if (sdoc.props.ContainingCollectionView && sdoc.props.ContainingCollectionView) {
- sdoc.props.Document.SetText(KeyStore.BackgroundColor, color.hex);
+ sdoc.props.Document.SetOnPrototype(KeyStore.BackgroundColor, new TextField(color.hex));
}
}
}
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index f6e19f6c9..26a07fdfe 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -7,17 +7,22 @@ import { KeyStore } from '../../fields/KeyStore';
import "./Main.scss";
import { MessageStore } from '../../server/Message';
import { Utils } from '../../Utils';
+import * as request from 'request'
import { Documents } from '../documents/Documents';
import { Server } from '../Server';
import { setupDrag } from '../util/DragManager';
import { Transform } from '../util/Transform';
import { UndoManager } from '../util/UndoManager';
+import { WorkspacesMenu } from '../../server/authentication/controllers/WorkspacesMenu';
import { CollectionDockingView } from './collections/CollectionDockingView';
import { ContextMenu } from './ContextMenu';
import { DocumentDecorations } from './DocumentDecorations';
import { DocumentView } from './nodes/DocumentView';
import "./Main.scss";
+import { observer } from 'mobx-react';
import { InkingControl } from './InkingControl';
+import { RouteStore } from '../../server/RouteStore';
+import { json } from 'body-parser';
import { library } from '@fortawesome/fontawesome-svg-core';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faFont } from '@fortawesome/free-solid-svg-icons';
@@ -32,152 +37,273 @@ import { faPenNib } from '@fortawesome/free-solid-svg-icons';
import { faFilm } from '@fortawesome/free-solid-svg-icons';
import { faMusic } from '@fortawesome/free-solid-svg-icons';
import Measure from 'react-measure';
+import { DashUserModel } from '../../server/authentication/models/user_model';
+import { ServerUtils } from '../../server/ServerUtil';
+import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { Field, Opt } from '../../fields/Field';
+import { ListField } from '../../fields/ListField';
+@observer
+export class Main extends React.Component {
+ // dummy initializations keep the compiler happy
+ @observable private mainContainer?: Document;
+ @observable private mainfreeform?: Document;
+ @observable private userWorkspaces: Document[] = [];
+ @observable public pwidth: number = 0;
+ @observable public pheight: number = 0;
-configure({ enforceActions: "observed" }); // causes errors to be generated when modifying an observable outside of an action
-window.addEventListener("drop", (e) => e.preventDefault(), false)
-window.addEventListener("dragover", (e) => e.preventDefault(), false)
-document.addEventListener("pointerdown", action(function (e: PointerEvent) {
- if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
- ContextMenu.Instance.clearItems()
+ private mainDocId: string | undefined;
+ private currentUser?: DashUserModel;
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ // causes errors to be generated when modifying an observable outside of an action
+ configure({ enforceActions: "observed" });
+ if (window.location.pathname !== RouteStore.home) {
+ let pathname = window.location.pathname.split("/");
+ this.mainDocId = pathname[pathname.length - 1];
+ }
+
+ CurrentUserUtils.loadCurrentUser();
+
+ library.add(faFont);
+ library.add(faImage);
+ library.add(faFilePdf);
+ library.add(faObjectGroup);
+ library.add(faTable);
+ library.add(faGlobeAsia);
+ library.add(faUndoAlt);
+ library.add(faRedoAlt);
+ library.add(faPenNib);
+ library.add(faFilm);
+ library.add(faMusic);
+
+ this.initEventListeners();
+ Documents.initProtos(() => {
+ this.initAuthenticationRouters();
+ });
+ }
+
+ onHistory = () => {
+ if (window.location.pathname !== RouteStore.home) {
+ let pathname = window.location.pathname.split("/");
+ this.mainDocId = pathname[pathname.length - 1];
+ Server.GetField(this.mainDocId, action((field: Opt<Field>) => {
+ if (field instanceof Document) {
+ this.openWorkspace(field, true);
+ }
+ }));
+ }
+ }
+
+ componentDidMount() {
+ window.onpopstate = this.onHistory;
}
-}), true)
-const pathname = window.location.pathname.split("/");
-const mainDocId = pathname[pathname.length - 1];
-var mainContainer: Document;
-let mainfreeform: Document;
-
-class mainDocFrame {
- @observable public static pwidth: number = 0;
- @observable public static pheight: number = 0;
-}
-library.add(faFont);
-library.add(faImage);
-library.add(faFilePdf);
-library.add(faObjectGroup);
-library.add(faTable);
-library.add(faGlobeAsia);
-library.add(faUndoAlt);
-library.add(faRedoAlt);
-library.add(faPenNib);
-library.add(faFilm);
-library.add(faMusic);
-
-Documents.initProtos(mainDocId, (res?: Document) => {
- if (res instanceof Document) {
- mainContainer = res;
- mainContainer.GetAsync(KeyStore.ActiveFrame, field => mainfreeform = field as Document);
+ componentWillUnmount() {
+ window.onpopstate = null;
}
- else {
- mainContainer = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: "main container" }, mainDocId);
+
+ initEventListeners = () => {
+ // window.addEventListener("pointermove", (e) => this.reportLocation(e))
+ window.addEventListener("drop", (e) => e.preventDefault(), false) // drop event handler
+ window.addEventListener("dragover", (e) => e.preventDefault(), false) // drag event handler
+ // click interactions for the context menu
+ document.addEventListener("pointerdown", action(function (e: PointerEvent) {
+ if (!ContextMenu.Instance.intersects(e.pageX, e.pageY)) {
+ ContextMenu.Instance.clearItems();
+ }
+ }), true);
+ }
+
+ initAuthenticationRouters = () => {
+ // Load the user's active workspace, or create a new one if initial session after signup
+ request.get(ServerUtils.prepend(RouteStore.getActiveWorkspace), (error, response, body) => {
+ if (this.mainDocId || body) {
+ Server.GetField(this.mainDocId || body, field => {
+ if (field instanceof Document) {
+ this.openWorkspace(field);
+ this.populateWorkspaces();
+ } else {
+ this.createNewWorkspace(true, this.mainDocId);
+ }
+ });
+ } else {
+ this.createNewWorkspace(true, this.mainDocId);
+ }
+ });
+ }
+
+ @action
+ createNewWorkspace = (init: boolean, id?: string): void => {
+ let mainDoc = Documents.DockDocument(JSON.stringify({ content: [{ type: 'row', content: [] }] }), { title: `Main Container ${this.userWorkspaces.length + 1}` }, id);
+ let newId = mainDoc.Id;
+ request.post(ServerUtils.prepend(RouteStore.addWorkspace), {
+ body: { target: newId },
+ json: true
+ }, () => { if (init) this.populateWorkspaces(); });
// bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
setTimeout(() => {
- mainfreeform = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" }, undefined, false);
-
- var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(mainfreeform)] }] };
- mainContainer.SetText(KeyStore.Data, JSON.stringify(dockingLayout));
- mainContainer.Set(KeyStore.ActiveFrame, mainfreeform);
+ let freeformDoc = Documents.FreeformDocument([], { x: 0, y: 400, title: "mini collection" });
+ var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(freeformDoc)] }] };
+ mainDoc.SetText(KeyStore.Data, JSON.stringify(dockingLayout));
+ mainDoc.Set(KeyStore.ActiveFrame, freeformDoc);
+ this.openWorkspace(mainDoc);
+ let pendingDocument = Documents.SchemaDocument([], { title: "New Mobile Uploads" })
+ mainDoc.Set(KeyStore.OptionalRightCollection, pendingDocument);
}, 0);
+ this.userWorkspaces.push(mainDoc);
}
- let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
- let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf"
- let weburl = "https://cs.brown.edu/courses/cs166/";
- let audiourl = "http://techslides.com/demos/samples/sample.mp3";
- let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
- let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}))
- let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
- let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
- let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" }));
- let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, title: "video node" }));
- let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, title: "a schema collection" }));
- let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
- let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
- let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }))
- let addClick = (creator: () => Document) => action(() =>
- mainfreeform.GetList<Document>(KeyStore.Data, []).push(creator())
- );
-
- let imgRef = React.createRef<HTMLDivElement>();
- let pdfRef = React.createRef<HTMLDivElement>();
- let webRef = React.createRef<HTMLDivElement>();
- let textRef = React.createRef<HTMLDivElement>();
- let schemaRef = React.createRef<HTMLDivElement>();
- let videoRef = React.createRef<HTMLDivElement>();
- let audioRef = React.createRef<HTMLDivElement>();
- let colRef = React.createRef<HTMLDivElement>();
-
- ReactDOM.render((
- <div style={{ position: "absolute", width: "100%", height: "100%" }}>
- {/* <div id="dash-title">— DASH —</div> */}
- <Measure onResize={(r: any) => runInAction(() => {
- mainDocFrame.pwidth = r.entry.width;
- mainDocFrame.pheight = r.entry.height;
- })}>
- {({ measureRef }) =>
- <div ref={measureRef} style={{ position: "absolute", width: "100%", height: "100%" }}>
- <DocumentView Document={mainContainer}
- AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
- ContentScaling={() => 1}
- PanelWidth={() => mainDocFrame.pwidth}
- PanelHeight={() => mainDocFrame.pheight}
- isTopMost={true}
- SelectOnLoad={false}
- focus={() => { }}
- ContainingCollectionView={undefined} />
- </div>
+ @action
+ populateWorkspaces = () => {
+ // retrieve all workspace documents from the server
+ request.get(ServerUtils.prepend(RouteStore.getAllWorkspaces), (error, res, body) => {
+ let ids = JSON.parse(body) as string[];
+ Server.GetFields(ids, action((fields: { [id: string]: Field }) => this.userWorkspaces = ids.map(id => fields[id] as Document)));
+ });
+ }
+
+ @action
+ openWorkspace = (doc: Document, fromHistory = false): void => {
+ request.post(ServerUtils.prepend(RouteStore.setActiveWorkspace), {
+ body: { target: doc.Id },
+ json: true
+ });
+ this.mainContainer = doc;
+ fromHistory || window.history.pushState(null, doc.Title, "/doc/" + doc.Id);
+ this.mainContainer.GetTAsync(KeyStore.ActiveFrame, Document, field => this.mainfreeform = field);
+ this.mainContainer.GetTAsync(KeyStore.OptionalRightCollection, Document, col => {
+ // if there is a pending doc, and it has new data, show it (syip: we use a timeout to prevent collection docking view from being uninitialized)
+ setTimeout(() => {
+ if (col) {
+ col.GetTAsync<ListField<Document>>(KeyStore.Data, ListField, (f: Opt<ListField<Document>>) => {
+ if (f && f.Data.length > 0) {
+ CollectionDockingView.Instance.AddRightSplit(col);
+ }
+ })
}
- </Measure>
- <DocumentDecorations />
- <ContextMenu />
- <button className="clear-db-button" onClick={clearDatabase}>Clear Database</button>
-
- {/* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */}
- < div id="toolbar" >
- <button className="toolbar-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button>
- <button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
- <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
- </div >
-
- {/* for the expandable add nodes menu. Not included with the above because once it expands it expands the whole div with it, making canvas interactions limited. */}
- < div id="add-nodes-menu" >
- <input type="checkbox" id="add-menu-toggle" />
- <label htmlFor="add-menu-toggle" title="Add Node"><p>+</p></label>
-
- <div id="add-options-content">
- <ul id="add-options-list">
- <li><div ref={textRef}><button className="round-button add-button" title="Add Textbox" onPointerDown={setupDrag(textRef, addTextNode)} onClick={addClick(addTextNode)}>
- <FontAwesomeIcon icon="font" size="sm" />
- </button></div></li>
- <li><div ref={imgRef}><button className="round-button add-button" title="Add Image" onPointerDown={setupDrag(imgRef, addImageNode)} onClick={addClick(addImageNode)}>
- <FontAwesomeIcon icon="image" size="sm" />
- </button></div></li>
- <li><div ref={pdfRef}><button className="round-button add-button" title="Add PDF" onPointerDown={setupDrag(pdfRef, addPDFNode)} onClick={addClick(addPDFNode)}>
- <FontAwesomeIcon icon="file-pdf" size="sm" />
- </button></div></li>
- <li><div ref={videoRef}><button className="round-button add-button" title="Add Video" onPointerDown={setupDrag(videoRef, addVideoNode)} onClick={addClick(addVideoNode)}>
- <FontAwesomeIcon icon="film" size="sm" />
- </button></div></li>
- <li><div ref={audioRef}><button className="round-button add-button" title="Add Audio" onPointerDown={setupDrag(audioRef, addAudioNode)} onClick={addClick(addAudioNode)}>
- <FontAwesomeIcon icon="music" size="sm" />
- </button></div></li>
- <li><div ref={webRef}><button className="round-button add-button" title="Add Web Clipping" onPointerDown={setupDrag(webRef, addWebNode)} onClick={addClick(addWebNode)}>
- <FontAwesomeIcon icon="globe-asia" size="sm" />
- </button></div></li>
- <li><div ref={colRef}><button className="round-button add-button" title="Add Collection" onPointerDown={setupDrag(colRef, addColNode)} onClick={addClick(addColNode)}>
- <FontAwesomeIcon icon="object-group" size="sm" />
- </button></div></li>
- <li><div ref={schemaRef}><button className="round-button add-button" title="Add Schema" onPointerDown={setupDrag(schemaRef, addSchemaNode)} onClick={addClick(addSchemaNode)}>
- <FontAwesomeIcon icon="table" size="sm" />
- </button></div></li>
- </ul>
- </div>
-
- </div >
-
- <InkingControl />
- </div >),
- document.getElementById('root'));
-})
+ }, 100);
+ });
+ }
+
+ toggleWorkspaces = () => {
+ if (WorkspacesMenu.Instance) {
+ WorkspacesMenu.Instance.toggle()
+ }
+ }
+
+ render() {
+ let imgRef = React.createRef<HTMLDivElement>();
+ let pdfRef = React.createRef<HTMLDivElement>();
+ let webRef = React.createRef<HTMLDivElement>();
+ let textRef = React.createRef<HTMLDivElement>();
+ let schemaRef = React.createRef<HTMLDivElement>();
+ let videoRef = React.createRef<HTMLDivElement>();
+ let audioRef = React.createRef<HTMLDivElement>();
+ let colRef = React.createRef<HTMLDivElement>();
+ let workspacesRef = React.createRef<HTMLDivElement>();
+ let logoutRef = React.createRef<HTMLDivElement>();
+
+ let imgurl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3a/Cat03.jpg/1200px-Cat03.jpg";
+ let pdfurl = "http://www.adobe.com/support/products/enterprise/knowledgecenter/media/c4611_sample_explain.pdf"
+ let weburl = "https://cs.brown.edu/courses/cs166/";
+ let audiourl = "http://techslides.com/demos/samples/sample.mp3";
+ let videourl = "http://techslides.com/demos/sample-videos/small.mp4";
+
+ let clearDatabase = action(() => Utils.Emit(Server.Socket, MessageStore.DeleteAll, {}))
+ let addTextNode = action(() => Documents.TextDocument({ width: 200, height: 200, title: "a text note" }))
+ let addColNode = action(() => Documents.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
+ let addSchemaNode = action(() => Documents.SchemaDocument([Documents.TextDocument()], { width: 200, height: 200, title: "a schema collection" }));
+ let addVideoNode = action(() => Documents.VideoDocument(videourl, { width: 200, height: 200, title: "video node" }));
+ let addPDFNode = action(() => Documents.PdfDocument(pdfurl, { width: 200, height: 200, title: "a schema collection" }));
+ let addImageNode = action(() => Documents.ImageDocument(imgurl, { width: 200, height: 200, title: "an image of a cat" }));
+ let addWebNode = action(() => Documents.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
+ let addAudioNode = action(() => Documents.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }))
+
+ let addClick = (creator: () => Document) => action(() => this.mainfreeform!.GetList<Document>(KeyStore.Data, []).push(creator()));
+
+ return (
+ <div style={{ position: "absolute", width: "100%", height: "100%" }}>
+ <Measure onResize={(r: any) => runInAction(() => {
+ this.pwidth = r.entry.width;
+ this.pheight = r.entry.height;
+ })}>
+ {({ measureRef }) => {
+ if (!this.mainContainer) {
+ return <div></div>
+ }
+ return <div ref={measureRef} style={{ position: "absolute", width: "100%", height: "100%" }}>
+ <DocumentView Document={this.mainContainer}
+ AddDocument={undefined} RemoveDocument={undefined} ScreenToLocalTransform={() => Transform.Identity}
+ ContentScaling={() => 1}
+ PanelWidth={() => this.pwidth}
+ PanelHeight={() => this.pheight}
+ isTopMost={true}
+ SelectOnLoad={false}
+ focus={() => { }}
+ ContainingCollectionView={undefined} />
+ </div>
+ }}
+ </Measure>
+ <DocumentDecorations />
+ <ContextMenu />
+
+ <button className="clear-db-button" onClick={clearDatabase}>Clear Database</button>
+
+ {/* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */}
+ < div id="toolbar" >
+ <button className="toolbar-button round-button" title="Undo" onClick={() => UndoManager.Undo()}><FontAwesomeIcon icon="undo-alt" size="sm" /></button>
+ <button className="toolbar-button round-button" title="Redo" onClick={() => UndoManager.Redo()}><FontAwesomeIcon icon="redo-alt" size="sm" /></button>
+ <button className="toolbar-button round-button" title="Ink" onClick={() => InkingControl.Instance.toggleDisplay()}><FontAwesomeIcon icon="pen-nib" size="sm" /></button>
+ </div >
+
+ <div className="main-buttonDiv" style={{ top: '34px', left: '2px', position: 'absolute' }} ref={workspacesRef}>
+ <button onClick={this.toggleWorkspaces}>Workspaces</button></div>
+ <div className="main-buttonDiv" style={{ top: '34px', right: '1px', position: 'absolute' }} ref={logoutRef}>
+ <button onClick={() => request.get(ServerUtils.prepend(RouteStore.logout), () => { })}>Log Out</button></div>
+
+ <WorkspacesMenu active={this.mainContainer} open={this.openWorkspace} new={this.createNewWorkspace} allWorkspaces={this.userWorkspaces} />
+ {/* for the expandable add nodes menu. Not included with the above because once it expands it expands the whole div with it, making canvas interactions limited. */}
+ < div id="add-nodes-menu" >
+ <input type="checkbox" id="add-menu-toggle" />
+ <label htmlFor="add-menu-toggle" title="Add Node"><p>+</p></label>
+
+ <div id="add-options-content">
+ <ul id="add-options-list">
+ <li><div ref={textRef}><button className="round-button add-button" title="Add Textbox" onPointerDown={setupDrag(textRef, addTextNode)} onClick={addClick(addTextNode)}>
+ <FontAwesomeIcon icon="font" size="sm" />
+ </button></div></li>
+ <li><div ref={imgRef}><button className="round-button add-button" title="Add Image" onPointerDown={setupDrag(imgRef, addImageNode)} onClick={addClick(addImageNode)}>
+ <FontAwesomeIcon icon="image" size="sm" />
+ </button></div></li>
+ <li><div ref={pdfRef}><button className="round-button add-button" title="Add PDF" onPointerDown={setupDrag(pdfRef, addPDFNode)} onClick={addClick(addPDFNode)}>
+ <FontAwesomeIcon icon="file-pdf" size="sm" />
+ </button></div></li>
+ <li><div ref={videoRef}><button className="round-button add-button" title="Add Video" onPointerDown={setupDrag(videoRef, addVideoNode)} onClick={addClick(addVideoNode)}>
+ <FontAwesomeIcon icon="film" size="sm" />
+ </button></div></li>
+ <li><div ref={audioRef}><button className="round-button add-button" title="Add Audio" onPointerDown={setupDrag(audioRef, addAudioNode)} onClick={addClick(addAudioNode)}>
+ <FontAwesomeIcon icon="music" size="sm" />
+ </button></div></li>
+ <li><div ref={webRef}><button className="round-button add-button" title="Add Web Clipping" onPointerDown={setupDrag(webRef, addWebNode)} onClick={addClick(addWebNode)}>
+ <FontAwesomeIcon icon="globe-asia" size="sm" />
+ </button></div></li>
+ <li><div ref={colRef}><button className="round-button add-button" title="Add Collection" onPointerDown={setupDrag(colRef, addColNode)} onClick={addClick(addColNode)}>
+ <FontAwesomeIcon icon="object-group" size="sm" />
+ </button></div></li>
+ <li><div ref={schemaRef}><button className="round-button add-button" title="Add Schema" onPointerDown={setupDrag(schemaRef, addSchemaNode)} onClick={addClick(addSchemaNode)}>
+ <FontAwesomeIcon icon="table" size="sm" />
+ </button></div></li>
+ </ul>
+ </div>
+ </div >
+
+ <InkingControl />
+ </div>
+ );
+ }
+}
+
+ReactDOM.render(<Main />, document.getElementById('root'));
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index ba9e8c29f..19788447e 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -144,7 +144,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (this._containerRef.current) {
reaction(
() => this.props.Document.GetText(KeyStore.Data, ""),
- () => this.setupGoldenLayout(), { fireImmediately: true });
+ () => setTimeout(() => this.setupGoldenLayout(), 1), { fireImmediately: true });
window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
}
diff --git a/src/client/views/collections/CollectionFreeFormView.scss b/src/client/views/collections/CollectionFreeFormView.scss
index 9c7a03b52..11addc5a1 100644
--- a/src/client/views/collections/CollectionFreeFormView.scss
+++ b/src/client/views/collections/CollectionFreeFormView.scss
@@ -8,11 +8,11 @@
}
//nested freeform views
- .collectionfreeformview-container {
+ // .collectionfreeformview-container {
// background-image: linear-gradient(to right, $light-color-secondary 1px, transparent 1px),
// linear-gradient(to bottom, $light-color-secondary 1px, transparent 1px);
// background-size: 30px 30px;
- }
+ // }
border: 0px solid $light-color-secondary;
border-radius: $border-radius;
diff --git a/src/client/views/collections/CollectionFreeFormView.tsx b/src/client/views/collections/CollectionFreeFormView.tsx
index 808a22a5d..8bf4a7539 100644
--- a/src/client/views/collections/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/CollectionFreeFormView.tsx
@@ -11,13 +11,14 @@ import { undoBatch } from "../../util/UndoManager";
import { InkingCanvas } from "../InkingCanvas";
import { CollectionFreeFormDocumentView } from "../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../nodes/DocumentContentsView";
-import { DocumentView, DocumentViewProps } from "../nodes/DocumentView";
+import { DocumentViewProps } from "../nodes/DocumentView";
import "./CollectionFreeFormView.scss";
import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
import { CollectionViewBase } from "./CollectionViewBase";
import { MarqueeView } from "./MarqueeView";
import { PreviewCursor } from "./PreviewCursor";
import React = require("react");
+import v5 = require("uuid/v5");
@observer
export class CollectionFreeFormView extends CollectionViewBase {
@@ -28,7 +29,7 @@ export class CollectionFreeFormView extends CollectionViewBase {
// mark this collection so that when the text box is created we can send it the SelectOnLoad prop to focus itself
this._selectOnLoaded = newBox.Id;
//set text to be the typed key and get focus on text box
- this.props.addDocument(newBox);
+ this.props.addDocument(newBox, false);
//remove cursor from screen
this.PreviewCursorVisible = false;
}
@@ -76,14 +77,13 @@ export class CollectionFreeFormView extends CollectionViewBase {
@action
drop = (e: Event, de: DragManager.DropEvent) => {
super.drop(e, de);
- let screenX = de.x - (de.data["xOffset"] as number || 0);
- let screenY = de.y - (de.data["yOffset"] as number || 0);
- const [x, y] = this.getTransform().transformPoint(screenX, screenY);
- let doc: Document = de.data["document"];
- if (doc) {
- doc.SetNumber(KeyStore.X, x);
- doc.SetNumber(KeyStore.Y, y);
- this.bringToFront(doc);
+ if (de.data instanceof DragManager.DocumentDragData) {
+ let screenX = de.x - (de.data.xOffset as number || 0);
+ let screenY = de.y - (de.data.yOffset as number || 0);
+ const [x, y] = this.getTransform().transformPoint(screenX, screenY);
+ de.data.droppedDocument.SetNumber(KeyStore.X, x);
+ de.data.droppedDocument.SetNumber(KeyStore.Y, y);
+ this.bringToFront(de.data.droppedDocument);
}
}
@@ -291,20 +291,58 @@ export class CollectionFreeFormView extends CollectionViewBase {
this.PreviewCursorVisible = false;
}
+ private crosshairs?: HTMLCanvasElement;
+ drawCrosshairs = (backgroundColor: string) => {
+ if (this.crosshairs) {
+ let c = this.crosshairs;
+ let ctx = c.getContext('2d');
+ if (ctx) {
+ ctx.fillStyle = backgroundColor;
+ ctx.fillRect(0, 0, 20, 20);
+
+ ctx.fillStyle = "black";
+ ctx.lineWidth = 0.5;
+
+ ctx.beginPath();
+
+ ctx.moveTo(10, 0);
+ ctx.lineTo(10, 8);
+
+ ctx.moveTo(10, 20);
+ ctx.lineTo(10, 12);
+
+ ctx.moveTo(0, 10);
+ ctx.lineTo(8, 10);
+
+ ctx.moveTo(20, 10);
+ ctx.lineTo(12, 10);
+
+ ctx.stroke();
+
+ // ctx.font = "10px Arial";
+ // ctx.fillText(CurrentUserUtils.email[0].toUpperCase(), 10, 10);
+ }
+ }
+ }
+
render() {
let [dx, dy] = [this.centeringShiftX, this.centeringShiftY];
const panx: number = -this.props.Document.GetNumber(KeyStore.PanX, 0);
const pany: number = -this.props.Document.GetNumber(KeyStore.PanY, 0);
+ // const panx: number = this.props.Document.GetNumber(KeyStore.PanX, 0) + this.centeringShiftX;
+ // const pany: number = this.props.Document.GetNumber(KeyStore.PanY, 0) + this.centeringShiftY;
+ // console.log("center:", this.getLocalTransform().transformPoint(this.centeringShiftX, this.centeringShiftY));
return (
<div className={`collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`}
onPointerDown={this.onPointerDown}
+ onPointerMove={(e) => super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY))}
onWheel={this.onPointerWheel}
onDrop={this.onDrop.bind(this)}
onDragOver={this.onDragOver}
onBlur={this.onBlur}
- style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px`, }}
+ style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}// , zIndex: !this.props.isTopMost ? -1 : undefined }}
tabIndex={0}
ref={this.createDropTarget}>
<div className="collectionfreeformview"
@@ -314,11 +352,54 @@ export class CollectionFreeFormView extends CollectionViewBase {
<InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} />
<PreviewCursor container={this} addLiveTextDocument={this.addLiveTextBox} getTransform={this.getTransform} />
{this.views}
+ {super.getCursors().map(entry => {
+ if (entry.Data.length > 0) {
+ let id = entry.Data[0][0];
+ let email = entry.Data[0][1];
+ let point = entry.Data[1];
+ this.drawCrosshairs("#" + v5(id, v5.URL).substring(0, 6).toUpperCase() + "22")
+ return (
+ <div
+ key={id}
+ style={{
+ position: "absolute",
+ transform: `translate(${point[0] - 10}px, ${point[1] - 10}px)`,
+ zIndex: 10000,
+ transformOrigin: 'center center',
+ }}
+ >
+ <canvas
+ ref={(el) => { if (el) this.crosshairs = el }}
+ width={20}
+ height={20}
+ style={{
+ position: 'absolute',
+ width: "20px",
+ height: "20px",
+ opacity: 0.5,
+ borderRadius: "50%",
+ border: "2px solid black"
+ }}
+ />
+ <p
+ style={{
+ fontSize: 14,
+ color: "black",
+ // fontStyle: "italic",
+ marginLeft: -12,
+ marginTop: 4
+ }}
+ >{email[0].toUpperCase()}</p>
+ </div>
+ );
+ }
+ })}
</div>
<MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments}
addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
getMarqueeTransform={this.getMarqueeTransform} getTransform={this.getTransform} />
{this.overlayView}
+
</div>
);
}
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 124d82c8b..e64b4c945 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -38,7 +38,7 @@ export class CollectionPDFView extends React.Component<CollectionViewProps> {
public SelectedDocs: FieldId[] = []
public active: () => boolean = () => CollectionView.Active(this);
- addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
+ addDocument = (doc: Document, allowDuplicates: boolean): void => { CollectionView.AddDocument(this.props, doc, allowDuplicates); }
removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
specificContextMenu = (e: React.MouseEvent): void => {
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 76ee421d6..1c5ff1b12 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -14,7 +14,7 @@ import { EditableView } from "../EditableView";
import { DocumentView } from "../nodes/DocumentView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./CollectionSchemaView.scss";
-import { COLLECTION_BORDER_WIDTH } from "./CollectionView";
+import { COLLECTION_BORDER_WIDTH, CollectionView } from "./CollectionView";
import { CollectionViewBase } from "./CollectionViewBase";
import { setupDrag } from "../../util/DragManager";
import { Key } from "./../../../fields/Key";
@@ -77,10 +77,12 @@ export class CollectionSchemaView extends CollectionViewBase {
<FieldView {...props} />
)
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = setupDrag(reference, () => props.doc);
+ let onItemDown = setupDrag(reference, () => props.doc, (containingCollection: CollectionView) => this.props.removeDocument(props.doc));
return (
<div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} style={{ height: "36px" }} key={props.doc.Id} ref={reference}>
- <EditableView contents={contents}
+ <EditableView
+ display={"inline"}
+ contents={contents}
height={36} GetValue={() => {
let field = props.doc.Get(props.fieldKey);
if (field && field instanceof Field) {
@@ -89,7 +91,7 @@ export class CollectionSchemaView extends CollectionViewBase {
return field || "";
}}
SetValue={(value: string) => {
- let script = CompileScript(value, undefined, true);
+ let script = CompileScript(value);
if (!script.compiled) {
return false;
}
@@ -235,11 +237,11 @@ export class CollectionSchemaView extends CollectionViewBase {
// e.preventDefault();
// } else
{
- if (e.buttons === 1) {
- if (this.props.isSelected()) {
- e.stopPropagation();
- }
- }
+ // if (e.buttons === 1) {
+ // if (this.props.isSelected()) {
+ // e.stopPropagation();
+ // }
+ // }
}
}
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 80fc89712..f9da759fd 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -77,7 +77,9 @@ class TreeView extends React.Component<TreeViewProps> {
return <div key={this.props.document.Id}></div>;
}
- return <div className="docContainer"> <EditableView contents={title.Data}
+ return <div className="docContainer"> <EditableView
+ display={"inline"}
+ contents={title.Data}
height={36} GetValue={() => {
let title = this.props.document.GetT<TextField>(KeyStore.Title, TextField);
if (title && title !== "<Waiting>")
@@ -167,6 +169,7 @@ export class CollectionTreeView extends CollectionViewBase {
<div id="body" className="collectionTreeView-dropTarget" onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget} style={{ borderWidth: `${COLLECTION_BORDER_WIDTH}px` }}>
<div className="coll-title">
<EditableView contents={titleStr}
+ display={"inline"}
height={72} GetValue={() => {
return this.props.Document.Title;
}} SetValue={(value: string) => {
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index b64ef3c07..05f759967 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -44,7 +44,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
public SelectedDocs: FieldId[] = []
public active: () => boolean = () => CollectionView.Active(this);
- addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
+ addDocument = (doc: Document, allowDuplicates: boolean): void => { CollectionView.AddDocument(this.props, doc, allowDuplicates); }
removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
specificContextMenu = (e: React.MouseEvent): void => {
@@ -112,7 +112,7 @@ export class CollectionVideoView extends React.Component<CollectionViewProps> {
render() {
return (<div className="collectionVideoView-cont" ref={this._mainCont} onContextMenu={this.specificContextMenu}>
{this.subView}
- {this.uIButtons}
+ {this.props.isSelected() ? this.uIButtons : (null)}
</div>)
}
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 40acf466e..303099c16 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -36,7 +36,7 @@ export class CollectionView extends React.Component<CollectionViewProps> {
@observable
public SelectedDocs: FieldId[] = [];
public active: () => boolean = () => CollectionView.Active(this);
- addDocument = (doc: Document): void => { CollectionView.AddDocument(this.props, doc); }
+ addDocument = (doc: Document, allowDuplicates: boolean): void => { CollectionView.AddDocument(this.props, doc, allowDuplicates); }
removeDocument = (doc: Document): boolean => { return CollectionView.RemoveDocument(this.props, doc); }
get subView() { return CollectionView.SubView(this); }
@@ -48,12 +48,13 @@ export class CollectionView extends React.Component<CollectionViewProps> {
}
@action
- public static AddDocument(props: CollectionViewProps, doc: Document) {
+ public static AddDocument(props: CollectionViewProps, doc: Document, allowDuplicates: boolean) {
doc.SetNumber(KeyStore.Page, props.Document.GetNumber(KeyStore.CurPage, -1));
if (props.Document.Get(props.fieldKey) instanceof Field) {
//TODO This won't create the field if it doesn't already exist
const value = props.Document.GetData(props.fieldKey, ListField, new Array<Document>())
- value.push(doc);
+ if (!value.some(v => v.Id == doc.Id) || allowDuplicates)
+ value.push(doc);
} else {
props.Document.SetOnPrototype(props.fieldKey, new ListField([doc]));
}
diff --git a/src/client/views/collections/CollectionViewBase.tsx b/src/client/views/collections/CollectionViewBase.tsx
index 37ec203b5..7175e2846 100644
--- a/src/client/views/collections/CollectionViewBase.tsx
+++ b/src/client/views/collections/CollectionViewBase.tsx
@@ -3,15 +3,18 @@ import { Document } from "../../../fields/Document";
import { ListField } from "../../../fields/ListField";
import React = require("react");
import { KeyStore } from "../../../fields/KeyStore";
-import { FieldWaiting, Field, Opt } from "../../../fields/Field";
+import { FieldWaiting, Opt } from "../../../fields/Field";
import { undoBatch } from "../../util/UndoManager";
import { DragManager } from "../../util/DragManager";
-import { DocumentView } from "../nodes/DocumentView";
import { Documents, DocumentOptions } from "../../documents/Documents";
import { Key } from "../../../fields/Key";
import { Transform } from "../../util/Transform";
import { CollectionView } from "./CollectionView";
+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 { DocumentManager } from "../../util/DocumentManager";
export interface CollectionViewProps {
fieldKey: Key;
@@ -25,13 +28,16 @@ export interface CollectionViewProps {
panelHeight: () => number;
focus: (doc: Document) => void;
}
+
export interface SubCollectionViewProps extends CollectionViewProps {
active: () => boolean;
- addDocument: (doc: Document) => void;
+ addDocument: (doc: Document, allowDuplicates: boolean) => void;
removeDocument: (doc: Document) => boolean;
CollectionView: CollectionView;
}
+export type CursorEntry = TupleField<[string, string], [number, number]>;
+
export class CollectionViewBase extends React.Component<SubCollectionViewProps> {
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
@@ -43,32 +49,49 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
}
}
+ @action
+ protected setCursorPosition(position: [number, number]) {
+ let ind;
+ let doc = this.props.Document;
+ let id = CurrentUserUtils.id;
+ let email = CurrentUserUtils.email;
+ if (id && email) {
+ let textInfo: [string, string] = [id, email];
+ doc.GetOrCreateAsync<ListField<CursorEntry>>(KeyStore.Cursors, ListField, field => {
+ let cursors = field.Data;
+ if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.Data[0][0] === id)) > -1) {
+ cursors[ind].Data[1] = position;
+ } else {
+ let entry = new TupleField<[string, string], [number, number]>([textInfo, position]);
+ cursors.push(entry);
+ }
+ })
+
+
+ }
+ }
+
+ protected getCursors(): CursorEntry[] {
+ let doc = this.props.Document;
+ let id = CurrentUserUtils.id;
+ let cursors = doc.GetList<CursorEntry>(KeyStore.Cursors, []);
+ let notMe = cursors.filter(entry => entry.Data[0][0] !== id);
+ return id ? notMe : [];
+ }
+
@undoBatch
@action
protected drop(e: Event, de: DragManager.DropEvent) {
- let dropDoc: Document = de.data["document"];
- if (de.data["alias"] && dropDoc) {
- let oldDoc = dropDoc;
- de.data["document"] = dropDoc = oldDoc.CreateAlias();
- [KeyStore.Width, KeyStore.Height].map(key =>
- oldDoc.GetTAsync(key, NumberField, (f: Opt<NumberField>) => {
- if (f) {
- dropDoc.SetNumber(key, f.Data)
- }
- })
- );
- } else {
- const docView: DocumentView = de.data["documentView"];
- if (docView && docView.props.RemoveDocument && docView.props.ContainingCollectionView !== this.props.CollectionView) {
- docView.props.RemoveDocument(dropDoc);
- } else if (dropDoc) {
- this.props.removeDocument(dropDoc);
+ if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.data.aliasOnDrop) {
+ [KeyStore.Width, KeyStore.Height, KeyStore.CurPage].map(key =>
+ de.data.draggedDocument.GetTAsync(key, NumberField, (f: Opt<NumberField>) => f ? de.data.droppedDocument.SetNumber(key, f.Data) : null));
+ } else if (de.data.removeDocument) {
+ de.data.removeDocument(this.props.CollectionView);
}
+ this.props.addDocument(de.data.droppedDocument, false);
+ e.stopPropagation();
}
- if (dropDoc) {
- this.props.addDocument(dropDoc);
- }
- e.stopPropagation();
}
@action
@@ -88,22 +111,21 @@ export class CollectionViewBase extends React.Component<SubCollectionViewProps>
console.log("not good");
let htmlDoc = Documents.HtmlDocument(html, { ...options, width: 300, height: 300 });
htmlDoc.SetText(KeyStore.DocumentText, text);
- this.props.addDocument(htmlDoc);
+ this.props.addDocument(htmlDoc, false);
return;
}
console.log(e.dataTransfer.items.length);
for (let i = 0; i < e.dataTransfer.items.length; i++) {
- const upload = window.location.origin + "/upload";
+ const upload = window.location.origin + RouteStore.upload;
let item = e.dataTransfer.items[i];
if (item.kind === "string" && item.type.indexOf("uri") != -1) {
- e.dataTransfer.items[i].getAsString(action((s: string) => this.props.addDocument(Documents.WebDocument(s, options))))
+ e.dataTransfer.items[i].getAsString(action((s: string) => this.props.addDocument(Documents.WebDocument(s, options), false)))
}
let type = item.type
console.log(type)
if (item.kind == "file") {
- let fReader = new FileReader()
let file = item.getAsFile();
let formData = new FormData()
diff --git a/src/client/views/collections/MarqueeView.tsx b/src/client/views/collections/MarqueeView.tsx
index f5c83a934..8c2f3443c 100644
--- a/src/client/views/collections/MarqueeView.tsx
+++ b/src/client/views/collections/MarqueeView.tsx
@@ -17,7 +17,7 @@ interface MarqueeViewProps {
getMarqueeTransform: () => Transform;
getTransform: () => Transform;
container: CollectionFreeFormView;
- addDocument: (doc: Document) => void;
+ addDocument: (doc: Document, allowDuplicates: false) => void;
activeDocuments: () => Document[];
selectDocuments: (docs: Document[]) => void;
removeDocument: (doc: Document) => boolean;
@@ -111,7 +111,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let liftedInk = this.marqueeInkSelect(true);
this.props.container.props.Document.SetData(KeyStore.Ink, this.marqueeInkSelect(false), InkField);
//setTimeout(() => {
- this.props.addDocument(Documents.FreeformDocument(selected, { x: bounds.left, y: bounds.top, panx: 0, pany: 0, width: bounds.width, backgroundColor: "Transparent", height: bounds.height, ink: liftedInk, title: "a nested collection" }));
+ let newCollection = Documents.FreeformDocument(selected, {
+ x: bounds.left,
+ y: bounds.top,
+ panx: 0,
+ pany: 0,
+ width: bounds.width,
+ height: bounds.height,
+ backgroundColor: "Transparent",
+ ink: liftedInk,
+ title: "a nested collection"
+ });
+ this.props.addDocument(newCollection, false);
// }, 100);
this.cleanupInteractions();
}
diff --git a/src/client/views/collections/PreviewCursor.tsx b/src/client/views/collections/PreviewCursor.tsx
index cbcfa568d..cbf36cf9e 100644
--- a/src/client/views/collections/PreviewCursor.tsx
+++ b/src/client/views/collections/PreviewCursor.tsx
@@ -59,7 +59,6 @@ export class PreviewCursor extends React.Component<PreviewCursorProps> {
let newBox = Documents.TextDocument({ width: 200, height: 100, x: x, y: y, title: "new" });
this.props.addLiveTextDocument(newBox);
e.stopPropagation();
- e.preventDefault();
}
}
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 981cabe71..9e34b2b60 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -23,7 +23,7 @@ import React = require("react");
export interface DocumentViewProps {
ContainingCollectionView: Opt<CollectionView>;
Document: Document;
- AddDocument?: (doc: Document) => void;
+ AddDocument?: (doc: Document, allowDuplicates: boolean) => void;
RemoveDocument?: (doc: Document) => boolean;
ScreenToLocalTransform: () => Transform;
isTopMost: boolean;
@@ -163,13 +163,16 @@ export class DocumentView extends React.Component<DocumentViewProps> {
startDragging(x: number, y: number, dropAliasOfDraggedDoc: boolean) {
if (this._mainCont.current) {
const [left, top] = this.props.ScreenToLocalTransform().inverse().transformPoint(0, 0);
- let dragData: { [id: string]: any } = {};
- dragData["documentView"] = this;
- dragData["document"] = this.props.Document;
- dragData["xOffset"] = x - left;
- dragData["yOffset"] = y - top;
- dragData["alias"] = dropAliasOfDraggedDoc;
- DragManager.StartDrag(this._mainCont.current, dragData, {
+ let dragData = new DragManager.DocumentDragData(this.props.Document);
+ dragData.aliasOnDrop = dropAliasOfDraggedDoc;
+ dragData.xOffset = x - left;
+ dragData.yOffset = y - top;
+ dragData.removeDocument = (dropCollectionView: CollectionView) => {
+ if (this.props.RemoveDocument && this.props.ContainingCollectionView !== dropCollectionView) {
+ this.props.RemoveDocument(this.props.Document);
+ }
+ }
+ DragManager.StartDocumentDrag(this._mainCont.current, dragData, {
handlers: {
dragComplete: action(() => { }),
},
@@ -212,7 +215,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
fieldsClicked = (e: React.MouseEvent): void => {
if (this.props.AddDocument) {
- this.props.AddDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }));
+ this.props.AddDocument(Documents.KVPDocument(this.props.Document, { width: 300, height: 300 }), false);
}
}
fullScreenClicked = (e: React.MouseEvent): void => {
@@ -231,28 +234,25 @@ export class DocumentView extends React.Component<DocumentViewProps> {
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- console.log("drop");
- const sourceDocView: DocumentView = de.data["linkSourceDoc"];
- if (!sourceDocView) {
- return;
- }
- let sourceDoc: Document = sourceDocView.props.Document;
- let destDoc: Document = this.props.Document;
- if (this.props.isTopMost) {
- return;
- }
- let linkDoc: Document = new Document();
+ if (de.data instanceof DragManager.LinkDragData) {
+ let sourceDoc: Document = de.data.linkSourceDocumentView.props.Document;
+ let destDoc: Document = this.props.Document;
+ if (this.props.isTopMost) {
+ return;
+ }
+ let linkDoc: Document = new Document();
- linkDoc.Set(KeyStore.Title, new TextField("New Link"));
- linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
- linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
+ linkDoc.Set(KeyStore.Title, new TextField("New Link"));
+ linkDoc.Set(KeyStore.LinkDescription, new TextField(""));
+ linkDoc.Set(KeyStore.LinkTags, new TextField("Default"));
- sourceDoc.GetOrCreateAsync(KeyStore.LinkedToDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
- linkDoc.Set(KeyStore.LinkedToDocs, destDoc);
- destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
- linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc);
+ sourceDoc.GetOrCreateAsync(KeyStore.LinkedToDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
+ linkDoc.Set(KeyStore.LinkedToDocs, destDoc);
+ destDoc.GetOrCreateAsync(KeyStore.LinkedFromDocs, ListField, field => { (field as ListField<Document>).Data.push(linkDoc) });
+ linkDoc.Set(KeyStore.LinkedFromDocs, sourceDoc);
- e.stopPropagation();
+ e.stopPropagation();
+ }
}
onDrop = (e: React.DragEvent) => {
@@ -335,7 +335,7 @@ export class DocumentView extends React.Component<DocumentViewProps> {
onContextMenu={this.onContextMenu}
onPointerDown={this.onPointerDown} >
<DocumentContentsView {...this.props} isSelected={this.isSelected} select={this.select} layoutKey={KeyStore.Layout} />
- </div>
+ </div >
)
}
} \ No newline at end of file
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index 3442e21aa..2db0cc4e2 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -104,7 +104,6 @@ export class ImageBox extends React.Component<FieldViewProps> {
render() {
let field = this.props.doc.Get(this.props.fieldKey);
- console.log(field)
let path = field == FieldWaiting ? "https://image.flaticon.com/icons/svg/66/66163.svg" :
field instanceof ImageField ? field.Data.href : "http://www.cs.brown.edu/~bcz/face.gif";
let nativeWidth = this.props.doc.GetNumber(KeyStore.NativeWidth, 1);
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 430c1b694..dd2f71b59 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -16,6 +16,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { faEye } from '@fortawesome/free-solid-svg-icons';
import { faEdit } from '@fortawesome/free-solid-svg-icons';
import { faTimes } from '@fortawesome/free-solid-svg-icons';
+import { undoBatch } from "../../util/UndoManager";
library.add(faEye);
@@ -33,8 +34,8 @@ interface Props {
@observer
export class LinkBox extends React.Component<Props> {
+ @undoBatch
onViewButtonPressed = (e: React.PointerEvent): void => {
- console.log("view down");
e.stopPropagation();
let docView = DocumentManager.Instance.getDocumentView(this.props.pairedDoc);
if (docView) {
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index b0b5a63a4..3a0ef2d32 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -17,6 +17,7 @@ import "./ImageBox.scss";
import "./PDFBox.scss";
import { Sticky } from './Sticky'; //you should look at sticky and annotation, because they are used here
import React = require("react")
+import { RouteStore } from "../../../server/RouteStore";
/** ALSO LOOK AT: Annotation.tsx, Sticky.tsx
* This method renders PDF and puts all kinds of functionalities such as annotation, highlighting,
@@ -96,7 +97,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
this.saveThumbnail();
this._interactive = true;
} else {
- if (this.curPage)
+ if (this.curPage > 0)
this.initPage = true;
}
},
@@ -441,7 +442,7 @@ export class PDFBox extends React.Component<FieldViewProps> {
let pdfUrl = this.props.doc.GetT(this.props.fieldKey, PDFField);
let xf = this.props.doc.GetNumber(KeyStore.NativeHeight, 0) / renderHeight;
return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
- <Document file={window.origin + "/corsProxy/" + `${pdfUrl}`}>
+ <Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl}`}>
<Measure onResize={this.setScaling}>
{({ measureRef }) =>
<div className="pdfBox-page" ref={measureRef}>