aboutsummaryrefslogtreecommitdiff
path: root/src/client/views
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views')
-rw-r--r--src/client/views/ContextMenu.scss83
-rw-r--r--src/client/views/ContextMenu.tsx20
-rw-r--r--src/client/views/ContextMenuItem.tsx52
-rw-r--r--src/client/views/DocumentDecorations.tsx58
-rw-r--r--src/client/views/InkingControl.tsx9
-rw-r--r--src/client/views/Main.scss19
-rw-r--r--src/client/views/Main.tsx329
-rw-r--r--src/client/views/MainView.tsx288
-rw-r--r--src/client/views/PresentationView.scss71
-rw-r--r--src/client/views/PresentationView.tsx171
-rw-r--r--src/client/views/PreviewCursor.tsx8
-rw-r--r--src/client/views/SearchItem.tsx12
-rw-r--r--src/client/views/TemplateMenu.tsx7
-rw-r--r--src/client/views/Templates.tsx24
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx17
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx108
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx2
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss89
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx97
-rw-r--r--src/client/views/collections/CollectionSubView.tsx8
-rw-r--r--src/client/views/collections/CollectionTreeView.scss17
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx28
-rw-r--r--src/client/views/collections/CollectionVideoView.scss2
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx52
-rw-r--r--src/client/views/collections/CollectionView.tsx37
-rw-r--r--src/client/views/collections/ParentDocumentSelector.scss8
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx66
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx92
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx119
-rw-r--r--src/client/views/nodes/CollectionFreeFormDocumentView.tsx171
-rw-r--r--src/client/views/nodes/DocumentContentsView.tsx25
-rw-r--r--src/client/views/nodes/DocumentView.tsx117
-rw-r--r--src/client/views/nodes/FieldView.tsx12
-rw-r--r--src/client/views/nodes/FormattedTextBox.tsx35
-rw-r--r--src/client/views/nodes/IconBox.scss8
-rw-r--r--src/client/views/nodes/IconBox.tsx27
-rw-r--r--src/client/views/nodes/ImageBox.scss6
-rw-r--r--src/client/views/nodes/ImageBox.tsx16
-rw-r--r--src/client/views/nodes/KeyValueBox.tsx6
-rw-r--r--src/client/views/nodes/KeyValuePair.scss8
-rw-r--r--src/client/views/nodes/KeyValuePair.tsx3
-rw-r--r--src/client/views/nodes/LinkBox.tsx2
-rw-r--r--src/client/views/nodes/LinkMenu.tsx9
-rw-r--r--src/client/views/nodes/PDFBox.tsx74
-rw-r--r--src/client/views/nodes/VideoBox.scss6
-rw-r--r--src/client/views/nodes/VideoBox.tsx97
46 files changed, 1468 insertions, 1047 deletions
diff --git a/src/client/views/ContextMenu.scss b/src/client/views/ContextMenu.scss
index fe884ca85..0a14c8ce7 100644
--- a/src/client/views/ContextMenu.scss
+++ b/src/client/views/ContextMenu.scss
@@ -1,55 +1,70 @@
@import "globalCssVariables";
+
.contextMenu-cont {
- position: absolute;
- display: flex;
- z-index: $contextMenu-zindex;
- box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
- flex-direction: column;
+ position: absolute;
+ display: flex;
+ z-index: $contextMenu-zindex;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.4vw;
+ flex-direction: column;
}
.contextMenu-item:first-child {
- background: $intermediate-color;
- color: $light-color;
+ background: $intermediate-color;
+ color: $light-color;
}
.contextMenu-item:first-child::placeholder {
- color: $light-color;
+ color: $light-color;
}
.contextMenu-item:first-child:hover {
- background: $intermediate-color;
- color: $light-color;
+ background: $intermediate-color;
+ color: $light-color;
+}
+
+.subMenu-cont {
+ position: absolute;
+ display: flex;
+ z-index: 1000;
+ box-shadow: #AAAAAA .2vw .2vw .4vw;
+ flex-direction: column;
}
.contextMenu-item {
- width: auto;
- height: auto;
- background: $light-color-secondary;
- display: flex;
- justify-content: left;
- align-items: center;
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
- transition: all 0.1s;
- border-width: 0.11px;
- border-style: none;
- border-color: $intermediate-color;
- border-bottom-style: solid;
- padding: 10px;
- white-space: nowrap;
- font-size: 13px;
+ width: 11vw; //10vw
+ height: 2.5vh; //2vh
+ background: #DDDDDD;
+ display: flex; //comment out to allow search icon to be inline with search text
+ justify-content: left;
+ align-items: center;
+ -webkit-touch-callout: none;
+ -webkit-user-select: none;
+ -khtml-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ transition: all .1s;
+ border-width: .11px;
+ border-style: none;
+ border-color: $intermediate-color; // rgb(187, 186, 186);
+ border-bottom-style: solid;
+ padding: 10px;
+ white-space: nowrap;
+ font-size: 1vw;
}
.contextMenu-item:hover {
- transition: all 0.1s;
- background: $lighter-alt-accent;
+ transition: all 0.1s;
+ background: $lighter-alt-accent;
}
.contextMenu-description {
- text-align: left;
- width: 8vw;
+ font-size: 1vw;
+ text-align: left;
+ width: 8vw;
+ display: inline; //need this?
}
+
+.icon-background {
+ background-color: #DDDDDD;
+} \ No newline at end of file
diff --git a/src/client/views/ContextMenu.tsx b/src/client/views/ContextMenu.tsx
index 615a928ad..9143c012e 100644
--- a/src/client/views/ContextMenu.tsx
+++ b/src/client/views/ContextMenu.tsx
@@ -1,14 +1,20 @@
import React = require("react");
import { ContextMenuItem, ContextMenuProps } from "./ContextMenuItem";
import { observable, action } from "mobx";
-import { observer } from "mobx-react";
-import "./ContextMenu.scss";
+import { observer } from "mobx-react"
+import "./ContextMenu.scss"
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { faSearch, faCircle } from '@fortawesome/free-solid-svg-icons';
+
+library.add(faSearch);
+library.add(faCircle);
@observer
export class ContextMenu extends React.Component {
static Instance: ContextMenu;
- @observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault() }];
+ @observable private _items: Array<ContextMenuProps> = [{ description: "test", event: (e: React.MouseEvent) => e.preventDefault(), icon: "smile" }];
@observable private _pageX: number = 0;
@observable private _pageY: number = 0;
@observable private _display: string = "none";
@@ -82,7 +88,13 @@ export class ContextMenu extends React.Component {
return (
<div className="contextMenu-cont" style={style} ref={this.ref}>
- <input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange}></input>
+ <span>
+ <span className="icon-background">
+ <FontAwesomeIcon icon="circle" size="lg" />
+ <FontAwesomeIcon icon="search" size="lg" />
+ </span>
+ <input className="contextMenu-item" type="text" placeholder="Search . . ." value={this._searchString} onChange={this.onChange} />
+ </span>
{this._items.filter(prop => prop.description.toLowerCase().indexOf(this._searchString.toLowerCase()) !== -1).
map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
</div>
diff --git a/src/client/views/ContextMenuItem.tsx b/src/client/views/ContextMenuItem.tsx
index 70813f0dd..ed583942a 100644
--- a/src/client/views/ContextMenuItem.tsx
+++ b/src/client/views/ContextMenuItem.tsx
@@ -1,8 +1,13 @@
import React = require("react");
+import { observable, action } from "mobx";
+import { observer } from "mobx-react";
+import { IconProp } from '@fortawesome/fontawesome-svg-core';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-export interface ContextMenuProps {
+export interface OriginalMenuProps {
description: string;
event: (e: React.MouseEvent<HTMLDivElement>) => void;
+ icon?: IconProp; //maybe should be optional (icon?)
}
export interface SubmenuProps {
@@ -13,13 +18,48 @@ export interface SubmenuProps {
export interface ContextMenuItemProps {
type: ContextMenuProps | SubmenuProps;
}
+export type ContextMenuProps = OriginalMenuProps | SubmenuProps;
+@observer
export class ContextMenuItem extends React.Component<ContextMenuProps> {
+ @observable private _items: Array<ContextMenuProps> = [];
+ @observable private overItem = false;
+
+ constructor(props: ContextMenuProps | SubmenuProps) {
+ super(props);
+ if ("subitems" in this.props) {
+ this.props.subitems.forEach(i => this._items.push(i));
+ }
+ }
+
render() {
- return (
- <div className="contextMenu-item" onClick={this.props.event}>
- <div className="contextMenu-description">{this.props.description}</div>
- </div>
- );
+ if ("event" in this.props) {
+ return (
+ <div className="contextMenu-item" onClick={this.props.event}>
+ <span className="icon-background">
+ <FontAwesomeIcon icon="circle" size="sm" />
+ {this.props.icon ? <FontAwesomeIcon icon={this.props.icon} size="sm" /> : null}
+ </span>
+ <div className="contextMenu-description"> {this.props.description}</div>
+ </div>
+ );
+ }
+ else {
+ let submenu = null;
+ if (this.overItem) {
+ submenu = (
+ <div className="subMenu-cont" style={{ marginLeft: "100.5%", left: "0px" }}>
+ {this._items.map(prop => <ContextMenuItem {...prop} key={prop.description} />)}
+ </div>
+ );
+ }
+ return (
+ <div className="contextMenu-item" onMouseEnter={action(() => { this.overItem = true; })}
+ onMouseLeave={action(() => this.overItem = false)}>
+ <div className="contextMenu-description"> {this.props.description}</div>
+ {submenu}
+ </div>
+ );
+ }
}
} \ No newline at end of file
diff --git a/src/client/views/DocumentDecorations.tsx b/src/client/views/DocumentDecorations.tsx
index 4786b4de6..5ec090f05 100644
--- a/src/client/views/DocumentDecorations.tsx
+++ b/src/client/views/DocumentDecorations.tsx
@@ -96,7 +96,6 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
this._downX = e.clientX;
this._downY = e.clientY;
e.stopPropagation();
- this.onBackgroundDown(e);
document.removeEventListener("pointermove", this.onTitleMove);
document.removeEventListener("pointerup", this.onTitleUp);
document.addEventListener("pointermove", this.onTitleMove);
@@ -239,6 +238,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
this._removeIcon = snapped;
}
}
+ @undoBatch
@action
onMinimizeUp = (e: PointerEvent): void => {
e.stopPropagation();
@@ -250,9 +250,10 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
selectedDocs[0].props.removeDocument && selectedDocs[0].props.removeDocument(this._iconDoc);
}
if (!this._removeIcon) {
- if (selectedDocs.length === 1)
+ if (selectedDocs.length === 1) {
this.getIconDoc(selectedDocs[0]).then(icon => selectedDocs[0].props.toggleMinimized());
- else {
+ } else if (Math.abs(e.pageX - this._downX) < Utils.DRAG_THRESHOLD &&
+ Math.abs(e.pageY - this._downY) < Utils.DRAG_THRESHOLD) {
let docViews = SelectionManager.ViewsSortedVertically();
let topDocView = docViews[0];
let ind = topDocView.templates.indexOf(Templates.Bullet.Layout);
@@ -261,7 +262,7 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
topDocView.props.Document.subBulletDocs = undefined;
} else {
topDocView.addTemplate(Templates.Bullet);
- topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document));
+ topDocView.props.Document.subBulletDocs = new List<Doc>(docViews.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
}
}
}
@@ -270,19 +271,20 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
runInAction(() => this._minimizedX = this._minimizedY = 0);
}
+ @undoBatch
@action createIcon = (selected: DocumentView[], layoutString: string): Doc => {
let doc = selected[0].props.Document;
let iconDoc = Docs.IconDocument(layoutString);
iconDoc.isButton = true;
- iconDoc.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
- iconDoc.labelField = this._fieldKey;
- iconDoc[this._fieldKey] = selected.length > 1 ? "collection" : undefined;
- iconDoc.isMinimized = false;
+ iconDoc.proto!.title = selected.length > 1 ? "ICONset" : "ICON" + StrCast(doc.title);
+ iconDoc.labelField = selected.length > 1 ? undefined : this._fieldKey;
+ iconDoc.proto![this._fieldKey] = selected.length > 1 ? "collection" : undefined;
+ iconDoc.proto!.isMinimized = false;
iconDoc.width = Number(MINIMIZED_ICON_SIZE);
iconDoc.height = Number(MINIMIZED_ICON_SIZE);
iconDoc.x = NumCast(doc.x);
iconDoc.y = NumCast(doc.y) - 24;
- iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document));
+ iconDoc.maximizedDocs = new List<Doc>(selected.map(s => s.props.Document.proto!));
doc.minimizedDoc = iconDoc;
selected[0].props.addDocument && selected[0].props.addDocument(iconDoc, false);
return iconDoc;
@@ -429,31 +431,29 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
SelectionManager.SelectedDocuments().forEach(element => {
const rect = element.ContentDiv ? element.ContentDiv.getBoundingClientRect() : new DOMRect();
- if (rect.width !== 0) {
+ if (rect.width !== 0 && (dX != 0 || dY != 0 || dW != 0 || dH != 0)) {
let doc = PositionDocument(element.props.Document);
- let width = FieldValue(doc.width, 0);
- let nwidth = FieldValue(doc.nativeWidth, 0);
- let nheight = FieldValue(doc.nativeHeight, 0);
- let height = FieldValue(doc.height, nwidth ? nheight / nwidth * width : 0);
- let x = FieldValue(doc.x, 0);
- let y = FieldValue(doc.y, 0);
+ let nwidth = doc.nativeWidth || 0;
+ let nheight = doc.nativeHeight || 0;
+ let zoomBasis = NumCast(doc.zoomBasis, 1);
+ let width = (doc.width || 0) / zoomBasis;
+ let height = (doc.height || (nheight / nwidth * width)) / zoomBasis;
let scale = width / rect.width;
let actualdW = Math.max(width + (dW * scale), 20);
let actualdH = Math.max(height + (dH * scale), 20);
- x += dX * (actualdW - width);
- y += dY * (actualdH - height);
- doc.x = x;
- doc.y = y;
- var nativeWidth = FieldValue(doc.nativeWidth, 0);
- var nativeHeight = FieldValue(doc.nativeHeight, 0);
- if (nativeWidth > 0 && nativeHeight > 0) {
+ doc.x = (doc.x || 0) + dX * (actualdW - width);
+ doc.y = (doc.y || 0) + dY * (actualdH - height);
+ if (nwidth > 0 && nheight > 0) {
if (Math.abs(dW) > Math.abs(dH)) {
- actualdH = nativeHeight / nativeWidth * actualdW;
+ doc.zoomBasis = zoomBasis * width / actualdW;
+ }
+ else {
+ doc.zoomBasis = zoomBasis * height / actualdH;
}
- else actualdW = nativeWidth / nativeHeight * actualdH;
+ } else {
+ doc.width = zoomBasis * actualdW;
+ doc.height = zoomBasis * actualdH;
}
- doc.width = actualdW;
- doc.height = actualdH;
}
});
}
@@ -534,9 +534,9 @@ export class DocumentDecorations extends React.Component<{}, { value: string }>
if (temp !== Templates.Bullet.Layout || i === 0) {
res.push(temp);
}
- })
+ });
}
- return res
+ return res;
}, [] as string[]);
let checked = false;
docTemps.forEach(temp => {
diff --git a/src/client/views/InkingControl.tsx b/src/client/views/InkingControl.tsx
index 4b3dbd4e0..17d4a1e49 100644
--- a/src/client/views/InkingControl.tsx
+++ b/src/client/views/InkingControl.tsx
@@ -35,12 +35,9 @@ export class InkingControl extends React.Component {
@action
switchColor = (color: ColorResult): void => {
this._selectedColor = color.hex;
- if (SelectionManager.SelectedDocuments().length === 1) {
- var sdoc = SelectionManager.SelectedDocuments()[0];
- if (sdoc.props.ContainingCollectionView) {
- Doc.SetOnPrototype(sdoc.props.Document, "backgroundColor", color.hex);
- }
- }
+ SelectionManager.SelectedDocuments().forEach(doc =>
+ doc.props.ContainingCollectionView && Doc.SetOnPrototype(doc.props.Document, "backgroundColor", color.hex)
+ );
}
@action
diff --git a/src/client/views/Main.scss b/src/client/views/Main.scss
index 2430e8f6c..d63b0147b 100644
--- a/src/client/views/Main.scss
+++ b/src/client/views/Main.scss
@@ -27,25 +27,6 @@ div {
z-index: 9999;
}
-h1 {
- font-size: 50px;
- position: fixed;
- top: 30px;
- left: 50%;
- transform: translateX(-50%);
- color: $dark-color;
- text-shadow: -1px -1px 0 #fff, 1px -1px 0 #fff, -1px 1px 0 #fff, 1px 1px 0 #fff;
- z-index: 9999;
- font-family: $sans-serif;
- font-weight: 700;
- -webkit-touch-callout: none;
- -webkit-user-select: none;
- -khtml-user-select: none;
- -moz-user-select: none;
- -ms-user-select: none;
- user-select: none;
-}
-
.jsx-parser {
width: 100%;
pointer-events: none;
diff --git a/src/client/views/Main.tsx b/src/client/views/Main.tsx
index 158de31f5..3d9750a85 100644
--- a/src/client/views/Main.tsx
+++ b/src/client/views/Main.tsx
@@ -1,330 +1,11 @@
-import { IconName, library } from '@fortawesome/fontawesome-svg-core';
-import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
-import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, configure, observable, runInAction } from 'mobx';
-import { observer } from 'mobx-react';
-import "normalize.css";
-import * as React from 'react';
+import { MainView } from "./MainView";
+import { Docs } from "../documents/Documents";
+import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
import * as ReactDOM from 'react-dom';
-import Measure from 'react-measure';
-import * as request from 'request';
-import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
-import { RouteStore } from '../../server/RouteStore';
-import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
-import { Docs } from '../documents/Documents';
-import { ColumnAttributeModel } from '../northstar/core/attribute/AttributeModel';
-import { AttributeTransformationModel } from '../northstar/core/attribute/AttributeTransformationModel';
-import { Gateway, NorthstarSettings } from '../northstar/manager/Gateway';
-import { AggregateFunction, Catalog } from '../northstar/model/idea/idea';
-import '../northstar/model/ModelExtensions';
-import { HistogramOperation } from '../northstar/operations/HistogramOperation';
-import '../northstar/utils/Extensions';
-import { SetupDrag, DragManager } from '../util/DragManager';
-import { Transform } from '../util/Transform';
-import { UndoManager } from '../util/UndoManager';
-import { PresentationView } from './PresentationView';
-import { CollectionDockingView } from './collections/CollectionDockingView';
-import { ContextMenu } from './ContextMenu';
-import { DocumentDecorations } from './DocumentDecorations';
-import { InkingControl } from './InkingControl';
-import "./Main.scss";
-import { MainOverlayTextBox } from './MainOverlayTextBox';
-import { DocumentView } from './nodes/DocumentView';
-import { PreviewCursor } from './PreviewCursor';
-import { SearchBox } from './SearchBox';
-import { SelectionManager } from '../util/SelectionManager';
-import { FieldResult, Field, Doc, Opt } from '../../new_fields/Doc';
-import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
-import { DocServer } from '../DocServer';
-import { listSpec } from '../../new_fields/Schema';
-import { Id } from '../../new_fields/RefField';
-
-@observer
-export class Main extends React.Component {
- public static Instance: Main;
- @observable private _workspacesShown: boolean = false;
- @observable public pwidth: number = 0;
- @observable public pheight: number = 0;
-
- @computed private get mainContainer(): Opt<Doc> {
- return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
- }
- private set mainContainer(doc: Opt<Doc>) {
- if (doc) {
- CurrentUserUtils.UserDocument.activeWorkspace = doc;
- }
- }
-
- constructor(props: Readonly<{}>) {
- super(props);
- Main.Instance = this;
- // 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("/");
- if (pathname.length > 1 && pathname[pathname.length - 2] === 'doc') {
- CurrentUserUtils.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);
- library.add(faTree);
-
- this.initEventListeners();
- this.initAuthenticationRouters();
-
- // try {
- // this.initializeNorthstar();
- // } catch (e) {
-
- // }
- }
-
- componentDidMount() { window.onpopstate = this.onHistory; }
-
- componentWillUnmount() { window.onpopstate = null; }
-
- onHistory = () => {
- if (window.location.pathname !== RouteStore.home) {
- let pathname = window.location.pathname.split("/");
- DocServer.GetRefField(pathname[pathname.length - 1]).then(action((field: Opt<Field>) => {
- if (field instanceof Doc) {
- this.openWorkspace(field, true);
- }
- }));
- }
- }
-
- 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
- window.addEventListener("keydown", (e) => {
- if (e.key === "Escape") {
- DragManager.AbortDrag();
- SelectionManager.DeselectAll();
- }
- }, 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 = async () => {
- // Load the user's active workspace, or create a new one if initial session after signup
- if (!CurrentUserUtils.MainDocId) {
- const doc = await Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc);
- if (doc) {
- this.openWorkspace(doc);
- } else {
- this.createNewWorkspace();
- }
- } else {
- DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field =>
- field instanceof Doc ? this.openWorkspace(field) :
- this.createNewWorkspace(CurrentUserUtils.MainDocId));
- }
- }
-
- @action
- createNewWorkspace = async (id?: string) => {
- const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
- if (list) {
- let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
- var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
- let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` });
- list.push(mainDoc);
- // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
- setTimeout(() => {
- this.openWorkspace(mainDoc);
- let pendingDocument = Docs.SchemaDocument([], { title: "New Mobile Uploads" });
- mainDoc.optionalRightCollection = pendingDocument;
- }, 0);
- }
- }
-
- @action
- openWorkspace = async (doc: Doc, fromHistory = false) => {
- CurrentUserUtils.MainDocId = doc[Id];
- this.mainContainer = doc;
- fromHistory || window.history.pushState(null, StrCast(doc.title), "/doc/" + doc[Id]);
- const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc);
- // 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(async () => {
- if (col) {
- const l = Cast(col.data, listSpec(Doc));
- if (l && l.length > 0) {
- CollectionDockingView.Instance.AddRightSplit(col);
- }
- }
- }, 100);
- }
- @computed
- get mainContent() {
- let pwidthFunc = () => this.pwidth;
- let pheightFunc = () => this.pheight;
- let noScaling = () => 1;
- let mainCont = this.mainContainer;
- return <Measure offset onResize={action((r: any) => { this.pwidth = r.offset.width; this.pheight = r.offset.height; })}>
- {({ measureRef }) =>
- <div ref={measureRef} id="mainContent-div">
- {!mainCont ? (null) :
- <DocumentView Document={mainCont}
- toggleMinimized={emptyFunction}
- addDocument={undefined}
- removeDocument={undefined}
- ScreenToLocalTransform={Transform.Identity}
- ContentScaling={noScaling}
- PanelWidth={pwidthFunc}
- PanelHeight={pheightFunc}
- isTopMost={true}
- selectOnLoad={false}
- focus={emptyFunction}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- bringToFront={emptyFunction}
- ContainingCollectionView={undefined} />}
- <PresentationView key="presentation" />
- </div>
- }
- </Measure>;
- }
-
- /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
- nodesMenu() {
-
- 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/c27211_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 addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
- let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
- let addSchemaNode = action(() => Docs.SchemaDocument([], { width: 200, height: 200, title: "a schema collection" }));
- let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
- // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" }));
- let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" }));
- let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
- let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
- let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
- let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
-
- let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
- [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
- [React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
- [React.createRef<HTMLDivElement>(), "file-pdf", "Add PDF", addPDFNode],
- [React.createRef<HTMLDivElement>(), "film", "Add Video", addVideoNode],
- [React.createRef<HTMLDivElement>(), "music", "Add Audio", addAudioNode],
- [React.createRef<HTMLDivElement>(), "globe-asia", "Add Web Clipping", addWebNode],
- [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
- [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
- [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
- ];
-
- return < 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">
- {btns.map(btn =>
- <li key={btn[1]} ><div ref={btn[0]}>
- <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}>
- <FontAwesomeIcon icon={btn[1]} size="sm" />
- </button>
- </div></li>)}
- </ul>
- </div>
- </div >;
- }
-
- /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
- @computed
- get miscButtons() {
- let logoutRef = React.createRef<HTMLDivElement>();
-
- return [
- <button className="clear-db-button" key="clear-db" onClick={DocServer.DeleteDatabase}>Clear Database</button>,
- <div id="toolbar" key="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-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div>,
- <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
- <button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
- ];
-
- }
-
- render() {
- return (
- <div id="main-div">
- <DocumentDecorations />
- {this.mainContent}
- <PreviewCursor />
- <ContextMenu />
- {this.nodesMenu()}
- {this.miscButtons}
- <InkingControl />
- <MainOverlayTextBox />
- </div>
- );
- }
-
- // --------------- Northstar hooks ------------- /
- private _northstarSchemas: Doc[] = [];
-
- @action SetNorthstarCatalog(ctlog: Catalog) {
- CurrentUserUtils.NorthstarDBCatalog = ctlog;
- if (ctlog && ctlog.schemas) {
- ctlog.schemas.map(schema => {
- let schemaDocuments: Doc[] = [];
- let attributesToBecomeDocs = CurrentUserUtils.GetAllNorthstarColumnAttributes(schema);
- Promise.all(attributesToBecomeDocs.reduce((promises, attr) => {
- promises.push(DocServer.GetRefField(attr.displayName! + ".alias").then(action((field: Opt<Field>) => {
- if (field instanceof Doc) {
- schemaDocuments.push(field);
- } else {
- var atmod = new ColumnAttributeModel(attr);
- let histoOp = new HistogramOperation(schema.displayName!,
- new AttributeTransformationModel(atmod, AggregateFunction.None),
- new AttributeTransformationModel(atmod, AggregateFunction.Count),
- new AttributeTransformationModel(atmod, AggregateFunction.Count));
- schemaDocuments.push(Docs.HistogramDocument(histoOp, { width: 200, height: 200, title: attr.displayName! }));
- }
- })));
- return promises;
- }, [] as Promise<void>[])).finally(() =>
- this._northstarSchemas.push(Docs.TreeDocument(schemaDocuments, { width: 50, height: 100, title: schema.displayName! })));
- });
- }
- }
- async initializeNorthstar(): Promise<void> {
- const getEnvironment = await fetch("/assets/env.json", { redirect: "follow", method: "GET", credentials: "include" });
- NorthstarSettings.Instance.UpdateEnvironment(await getEnvironment.json());
- Gateway.Instance.ClearCatalog().then(async () => this.SetNorthstarCatalog(await Gateway.Instance.GetCatalog()));
- }
-}
+import * as React from 'react';
(async () => {
await Docs.initProtos();
await CurrentUserUtils.loadCurrentUser();
- ReactDOM.render(<Main />, document.getElementById('root'));
+ ReactDOM.render(<MainView />, document.getElementById('root'));
})();
diff --git a/src/client/views/MainView.tsx b/src/client/views/MainView.tsx
new file mode 100644
index 000000000..cdb105e21
--- /dev/null
+++ b/src/client/views/MainView.tsx
@@ -0,0 +1,288 @@
+import { IconName, library } from '@fortawesome/fontawesome-svg-core';
+import { faFilePdf, faFilm, faFont, faGlobeAsia, faImage, faMusic, faObjectGroup, faPenNib, faRedoAlt, faTable, faTree, faUndoAlt } from '@fortawesome/free-solid-svg-icons';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { action, computed, configure, observable, runInAction, trace } from 'mobx';
+import { observer } from 'mobx-react';
+import "normalize.css";
+import * as React from 'react';
+import Measure from 'react-measure';
+import * as request from 'request';
+import { CurrentUserUtils } from '../../server/authentication/models/current_user_utils';
+import { RouteStore } from '../../server/RouteStore';
+import { emptyFunction, returnTrue, Utils, returnOne, returnZero } from '../../Utils';
+import { Docs } from '../documents/Documents';
+import { SetupDrag, DragManager } from '../util/DragManager';
+import { Transform } from '../util/Transform';
+import { UndoManager } from '../util/UndoManager';
+import { PresentationView } from './PresentationView';
+import { CollectionDockingView } from './collections/CollectionDockingView';
+import { ContextMenu } from './ContextMenu';
+import { DocumentDecorations } from './DocumentDecorations';
+import { InkingControl } from './InkingControl';
+import "./Main.scss";
+import { MainOverlayTextBox } from './MainOverlayTextBox';
+import { DocumentView } from './nodes/DocumentView';
+import { PreviewCursor } from './PreviewCursor';
+import { SearchBox } from './SearchBox';
+import { SelectionManager } from '../util/SelectionManager';
+import { FieldResult, Field, Doc, Opt } from '../../new_fields/Doc';
+import { Cast, FieldValue, StrCast } from '../../new_fields/Types';
+import { DocServer } from '../DocServer';
+import { listSpec } from '../../new_fields/Schema';
+import { Id } from '../../new_fields/RefField';
+import { HistoryUtil } from '../util/History';
+import { CollectionBaseView } from './collections/CollectionBaseView';
+
+
+@observer
+export class MainView extends React.Component {
+ public static Instance: MainView;
+ @observable private _workspacesShown: boolean = false;
+ @observable public pwidth: number = 0;
+ @observable public pheight: number = 0;
+
+ @computed private get mainContainer(): Opt<Doc> {
+ return FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc));
+ }
+ private set mainContainer(doc: Opt<Doc>) {
+ if (doc) {
+ if (!("presentationView" in doc)) {
+ doc.presentationView = new Doc();
+ }
+ CurrentUserUtils.UserDocument.activeWorkspace = doc;
+ }
+ }
+
+ constructor(props: Readonly<{}>) {
+ super(props);
+ MainView.Instance = this;
+ // causes errors to be generated when modifying an observable outside of an action
+ configure({ enforceActions: "observed" });
+ if (window.location.search.includes("readonly")) {
+ DocServer.makeReadOnly();
+ }
+ if (window.location.search.includes("safe")) {
+ if (!window.location.search.includes("nro")) {
+ DocServer.makeReadOnly();
+ }
+ CollectionBaseView.SetSafeMode(true);
+ }
+ if (window.location.pathname !== RouteStore.home) {
+ let pathname = window.location.pathname.substr(1).split("/");
+ if (pathname.length > 1) {
+ let type = pathname[0];
+ if (type === "doc") {
+ CurrentUserUtils.MainDocId = pathname[1];
+ }
+ }
+ }
+
+ 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);
+ library.add(faTree);
+ this.initEventListeners();
+ this.initAuthenticationRouters();
+ }
+
+ 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
+ window.addEventListener("keydown", (e) => {
+ if (e.key === "Escape") {
+ DragManager.AbortDrag();
+ SelectionManager.DeselectAll();
+ }
+ }, 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 = async () => {
+ // Load the user's active workspace, or create a new one if initial session after signup
+ if (!CurrentUserUtils.MainDocId) {
+ const doc = await Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc);
+ if (doc) {
+ this.openWorkspace(doc);
+ } else {
+ this.createNewWorkspace();
+ }
+ } else {
+ DocServer.GetRefField(CurrentUserUtils.MainDocId).then(field =>
+ field instanceof Doc ? this.openWorkspace(field) :
+ this.createNewWorkspace(CurrentUserUtils.MainDocId));
+ }
+ }
+
+ @action
+ createNewWorkspace = async (id?: string) => {
+ const list = Cast(CurrentUserUtils.UserDocument.data, listSpec(Doc));
+ if (list) {
+ let freeformDoc = Docs.FreeformDocument([], { x: 0, y: 400, title: `WS collection ${list.length + 1}` });
+ var dockingLayout = { content: [{ type: 'row', content: [CollectionDockingView.makeDocumentConfig(CurrentUserUtils.UserDocument, 150), CollectionDockingView.makeDocumentConfig(freeformDoc, 600)] }] };
+ let mainDoc = Docs.DockDocument([CurrentUserUtils.UserDocument, freeformDoc], JSON.stringify(dockingLayout), { title: `Workspace ${list.length + 1}` }, id);
+ list.push(mainDoc);
+ // bcz: strangely, we need a timeout to prevent exceptions/issues initializing GoldenLayout (the rendering engine for Main Container)
+ setTimeout(() => {
+ this.openWorkspace(mainDoc);
+ let pendingDocument = Docs.SchemaDocument(["title"], [], { title: "New Mobile Uploads" });
+ mainDoc.optionalRightCollection = pendingDocument;
+ }, 0);
+ }
+ }
+
+ @action
+ openWorkspace = async (doc: Doc, fromHistory = false) => {
+ CurrentUserUtils.MainDocId = doc[Id];
+ this.mainContainer = doc;
+ fromHistory || HistoryUtil.pushState({ type: "doc", docId: doc[Id], initializers: {} });
+ const col = await Cast(CurrentUserUtils.UserDocument.optionalRightCollection, Doc);
+ // 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(async () => {
+ if (col) {
+ const l = Cast(col.data, listSpec(Doc));
+ if (l && l.length > 0) {
+ CollectionDockingView.Instance.AddRightSplit(col);
+ }
+ }
+ }, 100);
+ }
+ @action
+ onResize = (r: any) => {
+ this.pwidth = r.offset.width;
+ this.pheight = r.offset.height;
+ }
+ getPWidth = () => {
+ return this.pwidth;
+ }
+ getPHeight = () => {
+ return this.pheight;
+ }
+ @computed
+ get mainContent() {
+ let mainCont = this.mainContainer;
+ let content = !mainCont ? (null) :
+ <DocumentView Document={mainCont}
+ toggleMinimized={emptyFunction}
+ addDocument={undefined}
+ addDocTab={emptyFunction}
+ removeDocument={undefined}
+ ScreenToLocalTransform={Transform.Identity}
+ ContentScaling={returnOne}
+ PanelWidth={this.getPWidth}
+ PanelHeight={this.getPHeight}
+ isTopMost={true}
+ selectOnLoad={false}
+ focus={emptyFunction}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ bringToFront={emptyFunction}
+ ContainingCollectionView={undefined} />;
+ const pres = mainCont ? FieldValue(Cast(mainCont.presentationView, Doc)) : undefined;
+ return <Measure offset onResize={this.onResize}>
+ {({ measureRef }) =>
+ <div ref={measureRef} id="mainContent-div">
+ {content}
+ {pres ? <PresentationView Document={pres} key="presentation" /> : null}
+ </div>
+ }
+ </Measure>;
+ }
+
+ /* for the expandable add nodes menu. Not included with the miscbuttons because once it expands it expands the whole div with it, making canvas interactions limited. */
+ nodesMenu() {
+
+ 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/c27211_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 addTextNode = action(() => Docs.TextDocument({ borderRounding: -1, width: 200, height: 200, title: "a text note" }));
+ let addColNode = action(() => Docs.FreeformDocument([], { width: 200, height: 200, title: "a freeform collection" }));
+ let addSchemaNode = action(() => Docs.SchemaDocument(["title"], [], { width: 200, height: 200, title: "a schema collection" }));
+ let addTreeNode = action(() => CurrentUserUtils.UserDocument);
+ //let addTreeNode = action(() => Docs.TreeDocument([CurrentUserUtils.UserDocument], { width: 250, height: 400, title: "Library:" + CurrentUserUtils.email, dropAction: "alias" }));
+ // let addTreeNode = action(() => Docs.TreeDocument(this._northstarSchemas, { width: 250, height: 400, title: "northstar schemas", dropAction: "copy" }));
+ let addVideoNode = action(() => Docs.VideoDocument(videourl, { width: 200, title: "video node" }));
+ let addPDFNode = action(() => Docs.PdfDocument(pdfurl, { width: 200, height: 200, title: "a pdf doc" }));
+ let addImageNode = action(() => Docs.ImageDocument(imgurl, { width: 200, title: "an image of a cat" }));
+ let addWebNode = action(() => Docs.WebDocument(weburl, { width: 200, height: 200, title: "a sample web page" }));
+ let addAudioNode = action(() => Docs.AudioDocument(audiourl, { width: 200, height: 200, title: "audio node" }));
+
+ let btns: [React.RefObject<HTMLDivElement>, IconName, string, () => Doc][] = [
+ [React.createRef<HTMLDivElement>(), "font", "Add Textbox", addTextNode],
+ [React.createRef<HTMLDivElement>(), "image", "Add Image", addImageNode],
+ [React.createRef<HTMLDivElement>(), "file-pdf", "Add PDF", addPDFNode],
+ [React.createRef<HTMLDivElement>(), "film", "Add Video", addVideoNode],
+ [React.createRef<HTMLDivElement>(), "music", "Add Audio", addAudioNode],
+ [React.createRef<HTMLDivElement>(), "globe-asia", "Add Web Clipping", addWebNode],
+ [React.createRef<HTMLDivElement>(), "object-group", "Add Collection", addColNode],
+ [React.createRef<HTMLDivElement>(), "tree", "Add Tree", addTreeNode],
+ [React.createRef<HTMLDivElement>(), "table", "Add Schema", addSchemaNode],
+ ];
+
+ return < 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">
+ {btns.map(btn =>
+ <li key={btn[1]} ><div ref={btn[0]}>
+ <button className="round-button add-button" title={btn[2]} onPointerDown={SetupDrag(btn[0], btn[3])}>
+ <FontAwesomeIcon icon={btn[1]} size="sm" />
+ </button>
+ </div></li>)}
+ </ul>
+ </div>
+ </div >;
+ }
+
+ /* @TODO this should really be moved into a moveable toolbar component, but for now let's put it here to meet the deadline */
+ @computed
+ get miscButtons() {
+ let logoutRef = React.createRef<HTMLDivElement>();
+
+ return [
+ <button className="clear-db-button" key="clear-db" onClick={e => e.shiftKey && e.altKey && DocServer.DeleteDatabase()}>Clear Database</button>,
+ <div id="toolbar" key="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-searchDiv" key="search" style={{ top: '34px', right: '1px', position: 'absolute' }} > <SearchBox /> </div>,
+ <div className="main-buttonDiv" key="logout" style={{ bottom: '0px', right: '1px', position: 'absolute' }} ref={logoutRef}>
+ <button onClick={() => request.get(DocServer.prepend(RouteStore.logout), emptyFunction)}>Log Out</button></div>
+ ];
+
+ }
+
+ render() {
+ return (
+ <div id="main-div">
+ <DocumentDecorations />
+ {this.mainContent}
+ <PreviewCursor />
+ <ContextMenu />
+ {this.nodesMenu()}
+ {this.miscButtons}
+ <InkingControl />
+ <MainOverlayTextBox />
+ </div>
+ );
+ }
+}
diff --git a/src/client/views/PresentationView.scss b/src/client/views/PresentationView.scss
index 7c5677f0d..fb4a851c4 100644
--- a/src/client/views/PresentationView.scss
+++ b/src/client/views/PresentationView.scss
@@ -4,15 +4,14 @@
z-index: 1;
box-shadow: #AAAAAA .2vw .2vw .4vw;
right: 0;
- top:0;
- bottom:0;
+ top: 0;
+ bottom: 0;
}
.presentationView-item {
- width: 220px;
- height: 40px;
- vertical-align: center;
- padding-top: 15px;
+ padding: 10px;
+ display: inline-block;
+ width: 100%;
-webkit-touch-callout: none;
-webkit-user-select: none;
-khtml-user-select: none;
@@ -22,47 +21,59 @@
transition: all .1s;
}
+.presentationView-listCont {
+ padding-left: 10px;
+ padding-right: 10px;
+}
+
.presentationView-item:hover {
transition: all .1s;
background: #AAAAAA
}
+.presentationView-selected {
+ background: gray;
+}
+
.presentationView-heading {
- margin-top: 0px;
- height: 40px;
background: lightseagreen;
- padding: 30px;
+ padding: 10px;
+ display: inline-block;
+ width: 100%;
}
+
.presentationView-title {
- padding-top: 3px;
- padding-bottom: 3px;
- font-size: 25px;
- float:left;
+ padding-top: 3px;
+ padding-bottom: 3px;
+ font-size: 25px;
+ display: inline-block;
}
-.presentation-icon{
+
+.presentation-icon {
float: right;
- display: inline;
- width: 10px;
- margin-top: 7px;
}
-.presentationView-header {
- padding-top: 1px;
- padding-bottom: 1px;
+
+.presentationView-name {
font-size: 15px;
- float:left;
- }
+ display: inline-block;
+}
+
+.presentation-button {
+ margin-right: 12.5%;
+ margin-left: 12.5%;
+ width: 25%;
+}
- .presentation-next{
- float: right;
- }
- .presentation-back{
- float: left;
- }
- .presentation-next:hover{
+.presentation-buttons {
+ padding: 10px;
+}
+
+.presentation-next:hover {
transition: all .1s;
background: #AAAAAA
}
-.presentation-back:hover{
+
+.presentation-back:hover {
transition: all .1s;
background: #AAAAAA
} \ No newline at end of file
diff --git a/src/client/views/PresentationView.tsx b/src/client/views/PresentationView.tsx
index 3fb24a339..9c37e9000 100644
--- a/src/client/views/PresentationView.tsx
+++ b/src/client/views/PresentationView.tsx
@@ -1,19 +1,23 @@
import { observer } from "mobx-react";
-import React = require("react")
+import React = require("react");
import { observable, action, runInAction, reaction } from "mobx";
-import "./PresentationView.scss"
-import "./Main.tsx";
+import "./PresentationView.scss";
import { DocumentManager } from "../util/DocumentManager";
import { Utils } from "../../Utils";
-import { Doc } from "../../new_fields/Doc";
+import { Doc, DocListCast, DocListCastAsync } from "../../new_fields/Doc";
import { listSpec } from "../../new_fields/Schema";
-import { Cast, NumCast, FieldValue, PromiseValue } from "../../new_fields/Types";
+import { Cast, NumCast, FieldValue, PromiseValue, StrCast } from "../../new_fields/Types";
import { Id } from "../../new_fields/RefField";
import { List } from "../../new_fields/List";
import { CurrentUserUtils } from "../../server/authentication/models/current_user_utils";
export interface PresViewProps {
- //Document: Doc;
+ Document: Doc;
+}
+
+interface PresListProps extends PresViewProps {
+ deleteDocument(index: number): void;
+ gotoDocument(index: number): void;
}
@@ -21,72 +25,40 @@ export interface PresViewProps {
/**
* Component that takes in a document prop and a boolean whether it's collapsed or not.
*/
-class PresentationViewItem extends React.Component<PresViewProps> {
-
- @observable Document: Doc;
- constructor(props: PresViewProps) {
- super(props);
- this.Document = FieldValue(Cast(FieldValue(Cast(CurrentUserUtils.UserDocument.activeWorkspace, Doc))!.presentationView, Doc))!;
- }
- //look at CollectionFreeformView.focusDocument(d)
- @action
- openDoc = (doc: Doc) => {
- let docView = DocumentManager.Instance.getDocumentView(doc);
- if (docView) {
- docView.props.focus(docView.props.Document);
- }
- }
-
- /**
- * Removes a document from the presentation view
- **/
- @action
- public RemoveDoc(doc: Doc) {
- const value = Cast(this.Document.data, listSpec(Doc), []);
- let index = -1;
- for (let i = 0; i < value.length; i++) {
- if (value[i][Id] === doc[Id]) {
- index = i;
- break;
- }
- }
- if (index !== -1) {
- value.splice(index, 1);
- }
- }
+class PresentationViewList extends React.Component<PresListProps> {
/**
* Renders a single child document. It will just append a list element.
* @param document The document to render.
*/
- renderChild(document: Doc) {
+ renderChild = (document: Doc, index: number) => {
let title = document.title;
//to get currently selected presentation doc
- let selected = NumCast(this.Document.selectedDoc, 0);
+ let selected = NumCast(this.props.Document.selectedDoc, 0);
- // finally, if it's a normal document, then render it as such.
- const children = Cast(this.Document.data, listSpec(Doc));
- const styles: any = {};
- if (children && children[selected] === document) {
+ let className = "presentationView-item";
+ if (selected === index) {
//this doc is selected
- styles.background = "gray";
+ className += " presentationView-selected";
}
return (
- <li className="presentationView-item" style={styles} key={Utils.GenerateGuid()}>
- <div className="presentationView-header" onClick={() => this.openDoc(document)}>{title}</div>
- <div className="presentation-icon" onClick={() => this.RemoveDoc(document)}>X</div>
- </li>
+ <div className={className} key={document[Id] + index} onClick={e => { this.props.gotoDocument(index); e.stopPropagation(); }}>
+ <strong className="presentationView-name">
+ {`${index + 1}. ${title}`}
+ </strong>
+ <button className="presentation-icon" onClick={e => { this.props.deleteDocument(index); e.stopPropagation(); }}>X</button>
+ </div>
);
}
render() {
- const children = Cast(this.Document.data, listSpec(Doc), []);
+ const children = DocListCast(this.props.Document.data);
return (
- <div>
- {children.map(value => this.renderChild(value))}
+ <div className="presentationView-listCont">
+ {children.map(this.renderChild)}
</div>
);
}
@@ -100,59 +72,42 @@ export class PresentationView extends React.Component<PresViewProps> {
//observable means render is re-called every time variable is changed
@observable
collapsed: boolean = false;
- closePresentation = action(() => this.Document!.width = 0);
+ closePresentation = action(() => this.props.Document.width = 0);
next = () => {
- const current = NumCast(this.Document!.selectedDoc);
- const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc)));
- if (allDocs && current < allDocs.length + 1) {
- //can move forwards
- this.Document!.selectedDoc = current + 1;
- const doc = allDocs[current + 1];
- let docView = DocumentManager.Instance.getDocumentView(doc);
- if (docView) {
- docView.props.focus(docView.props.Document);
- }
- }
+ const current = NumCast(this.props.Document.selectedDoc);
+ this.gotoDocument(current + 1);
}
back = () => {
- const current = NumCast(this.Document!.selectedDoc);
- const allDocs = FieldValue(Cast(this.Document!.data, listSpec(Doc)));
- if (allDocs && current - 1 >= 0) {
- //can move forwards
- this.Document!.selectedDoc = current - 1;
- const doc = allDocs[current - 1];
- let docView = DocumentManager.Instance.getDocumentView(doc);
- if (docView) {
- docView.props.focus(docView.props.Document);
- }
+ const current = NumCast(this.props.Document.selectedDoc);
+ this.gotoDocument(current - 1);
+ }
+
+ @action
+ public RemoveDoc = (index: number) => {
+ const value = FieldValue(Cast(this.props.Document.data, listSpec(Doc)));
+ if (value) {
+ value.splice(index, 1);
}
}
- private ref = React.createRef<HTMLDivElement>();
+ public gotoDocument = async (index: number) => {
+ const list = FieldValue(Cast(this.props.Document.data, listSpec(Doc)));
+ if (!list) {
+ return;
+ }
+ if (index < 0 || index >= list.length) {
+ return;
+ }
+
+ this.props.Document.selectedDoc = index;
+ const doc = await list[index];
+ DocumentManager.Instance.jumpToDocument(doc);
+ }
- @observable Document?: Doc;
//initilize class variables
constructor(props: PresViewProps) {
super(props);
- let self = this;
- reaction(() =>
- CurrentUserUtils.UserDocument.activeWorkspace,
- (activeW) => {
- if (activeW && activeW instanceof Doc) {
- PromiseValue(Cast(activeW.presentationView, Doc)).
- then(pv => runInAction(() => {
- if (pv) self.Document = pv;
- else {
- pv = new Doc();
- pv.title = "Presentation Doc";
- activeW.presentationView = pv;
- self.Document = pv;
- }
- }))
- }
- },
- { fireImmediately: true });
PresentationView.Instance = this;
}
@@ -162,36 +117,32 @@ export class PresentationView extends React.Component<PresViewProps> {
@action
public PinDoc(doc: Doc) {
//add this new doc to props.Document
- const data = Cast(this.Document!.data, listSpec(Doc));
+ const data = Cast(this.props.Document.data, listSpec(Doc));
if (data) {
data.push(doc);
} else {
- this.Document!.data = new List([doc]);
+ this.props.Document.data = new List([doc]);
}
- this.Document!.width = 300;
+ this.props.Document.width = 300;
}
render() {
- if (!this.Document)
- return (null);
- let titleStr = this.Document.Title;
- let width = NumCast(this.Document.width);
+ let titleStr = StrCast(this.props.Document.title);
+ let width = NumCast(this.props.Document.width);
//TODO: next and back should be icons
return (
<div className="presentationView-cont" style={{ width: width, overflow: "hidden" }}>
<div className="presentationView-heading">
<div className="presentationView-title">{titleStr}</div>
- <div className='presentation-icon' onClick={this.closePresentation}>X</div></div>
- <div>
- <div className="presentation-back" onClick={this.back}>back</div>
- <div className="presentation-next" onClick={this.next}>next</div>
-
+ <button className='presentation-icon' onClick={this.closePresentation}>X</button>
+ </div>
+ <div className="presentation-buttons">
+ <button className="presentation-button" onClick={this.back}>back</button>
+ <button className="presentation-button" onClick={this.next}>next</button>
</div>
- <ul>
- <PresentationViewItem />
- </ul>
+ <PresentationViewList Document={this.props.Document} deleteDocument={this.RemoveDoc} gotoDocument={this.gotoDocument} />
</div>
);
}
diff --git a/src/client/views/PreviewCursor.tsx b/src/client/views/PreviewCursor.tsx
index 4218ea7c9..7c1d00eb0 100644
--- a/src/client/views/PreviewCursor.tsx
+++ b/src/client/views/PreviewCursor.tsx
@@ -22,9 +22,11 @@ export class PreviewCursor extends React.Component<{}> {
}
paste = (e: ClipboardEvent) => {
console.log(e.clipboardData);
- console.log(e.clipboardData.getData("text/html"));
- console.log(e.clipboardData.getData("text/csv"));
- console.log(e.clipboardData.getData("text/plain"));
+ if (e.clipboardData) {
+ console.log(e.clipboardData.getData("text/html"));
+ console.log(e.clipboardData.getData("text/csv"));
+ console.log(e.clipboardData.getData("text/plain"));
+ }
}
@action
diff --git a/src/client/views/SearchItem.tsx b/src/client/views/SearchItem.tsx
index 0da0bdae8..01c7316d6 100644
--- a/src/client/views/SearchItem.tsx
+++ b/src/client/views/SearchItem.tsx
@@ -40,6 +40,14 @@ export class SearchItem extends React.Component<SearchProps> {
faCaretUp;
return <FontAwesomeIcon icon={button} className="documentView-minimizedIcon" />;
}
+ onPointerEnter = (e: React.PointerEvent) => {
+ this.props.doc.libraryBrush = true;
+ Doc.SetOnPrototype(this.props.doc, "protoBrush", true);
+ }
+ onPointerLeave = (e: React.PointerEvent) => {
+ this.props.doc.libraryBrush = false;
+ Doc.SetOnPrototype(this.props.doc, "protoBrush", false);
+ }
collectionRef = React.createRef<HTMLDivElement>();
startDocDrag = () => {
@@ -53,7 +61,9 @@ export class SearchItem extends React.Component<SearchProps> {
}
render() {
return (
- <div className="search-item" ref={this.collectionRef} id="result" onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
+ <div className="search-item" ref={this.collectionRef} id="result"
+ onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}
+ onClick={this.onClick} onPointerDown={SetupDrag(this.collectionRef, this.startDocDrag)} >
<div className="search-title" id="result" >title: {this.props.doc.title}</div>
{/* <div className="search-type" id="result" >Type: {this.props.doc.layout}</div> */}
{/* <div className="search-type" >{SearchItem.DocumentIcon(this.layout)}</div> */}
diff --git a/src/client/views/TemplateMenu.tsx b/src/client/views/TemplateMenu.tsx
index e2b3bd07a..e5b679e24 100644
--- a/src/client/views/TemplateMenu.tsx
+++ b/src/client/views/TemplateMenu.tsx
@@ -38,22 +38,21 @@ export class TemplateMenu extends React.Component<TemplateMenuProps> {
constructor(props: TemplateMenuProps) {
super(props);
- console.log("");
}
@action
toggleTemplate = (event: React.ChangeEvent<HTMLInputElement>, template: Template): void => {
if (event.target.checked) {
- if (template.Name == "Bullet") {
+ if (template.Name === "Bullet") {
let topDocView = this.props.docs[0];
topDocView.addTemplate(template);
- topDocView.props.Document.subBulletDocs = new List<Doc>(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document));
+ topDocView.props.Document.subBulletDocs = new List<Doc>(this.props.docs.filter(v => v !== topDocView).map(v => v.props.Document.proto!));
} else {
this.props.docs.map(d => d.addTemplate(template));
}
this.props.templates.set(template, true);
} else {
- if (template.Name == "Bullet") {
+ if (template.Name === "Bullet") {
let topDocView = this.props.docs[0];
topDocView.removeTemplate(template);
topDocView.props.Document.subBulletDocs = undefined;
diff --git a/src/client/views/Templates.tsx b/src/client/views/Templates.tsx
index a98870b04..5a99b3d90 100644
--- a/src/client/views/Templates.tsx
+++ b/src/client/views/Templates.tsx
@@ -40,8 +40,11 @@ export namespace Templates {
// export const BasicLayout = new Template("Basic layout", "{layout}");
export const Caption = new Template("Caption", TemplatePosition.OutterBottom,
- `<div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute">
- <FormattedTextBox {...props} fieldKey={"caption"} />
+ `<div>
+ <div style="height:100%; width:100%;position:absolute;">{layout}</div>
+ <div id="screenSpace" style="top: 100%; font-size:14px; background:yellow; width:100%; position:absolute">
+ <FormattedTextBox {...props} fieldKey={"caption"} />
+ </div>
</div>` );
export const TitleOverlay = new Template("TitleOverlay", TemplatePosition.InnerTop,
@@ -59,19 +62,20 @@ export namespace Templates {
</div></div>` );
export const Bullet = new Template("Bullet", TemplatePosition.InnerTop,
- `<div><div style="height:100%; width:100%;position:absolute;">{layout}</div>
- <div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
- <img id="isExpander" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg=="
- width="15px" height="15px"/>
- </div>
+ `<div>
+ <div style="height:100%; width:100%;position:absolute;">{layout}</div>
+ <div id="isExpander" style="height:15px; width:15px; margin-left:-16px; pointer-events:all; position:absolute; top: 0; background-color: rgba(0, 0, 0, .4); color: white;">
+ <img id="isExpander" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOEAAADhCAMAAAAJbSJIAAAAZlBMVEX///8AAABmZmb7+/tYWFhgYGBFRUVSUlL4+Pg/Pz9jY2N5eXmcnJyioqKBgYFzc3NtbW1LS0s3NzfW1taWlpaOjo6IiIgvLy9WVlampqZcXFw5OTlvb28mJiYxMTHe3t7l5eUjIyMY8kIZAAAD2UlEQVR4nO2d61biMBRGW1FBEVHxfp15/5ecOVa5lHxtArmck/Xtn1BotjtNoXQtm4YQQgghhBBCCCGEEEIIIYQQQgghhBBCCCGEEFIrX6UHEA1gsmrneceRjHm7cj28attKFOf/TRyKIliH4vzbZE+xE2zbZYkxRWX5Y9JT/BW0X3G+NtlR3Ahar7jcMtlS3Ba0XXG+Y7JW3BW0XHHZM/lR7AvaVewL/ijuC1pV3Bf8VnQJ2lR0CYriq/Nxg4puwfa1aZ7dz9yUHnEgN26NZ3luWkPFd7fEtHsWVDwpO+YgTgYKCuYn6tAU7TBecaygcGpZEQie7m5luKJPQQFUvCwx5iAuvQoK4KShvSIoOHVtCz7dnOUecxBn7kG/urc2eCz6T9EOcxXDCgpAUetyAwoOCBqrGF5QMKR4mCA8L+pTBIJwkRl95eifJjPHTDYTFQ8vePyrs3BsBfXLzfFHkvKKMY4j1ctNnCmmuGKslfCQT0RZiPdFVmnFmOcy36sDWYn7DU9hxdifRkKuEGQh/pWW0K/QiUlxtUxVxTTXyhQtN6kuI6mpmO5qpxJFIBjl1yMVimmvV4PfrnIq3iYsKICTRj7F9L84gIq38fYwCCj4HnMfRY/FPL8ZFayYo6BQbLlJeZrYpVDFXAUFcMtKWkUgmOhmnwKKOQsK4NaxdIp5CwqZj8X8gv27jNecJ9nZuXtnie/SzjhRQcHkt6Fnq1imoAAUY1csVVDIUrFcQSGDIhC8jriLQZIrli0oXKdVLF1QSFqxfEEBVLyI8NYXCgoKySaqhinakajimxrBRBX1FBQSVNRyDP4SXVGbYHRFfYJN8xhTESwyj5HHHEjEihoLCqDiXfAb3aksKESqCAoqEIxUUW9BAS03E+93mOhcZDYcXVF3QeHBPcI3v4qo4EPiUQcBKr75vHaiv6AAKt6NV0SCqgoKqOKYovpFZgOo+DmsOHkyUlA4ZKKamaIdQPEJK5oqKKCKM7D9zFZBIayiuYICWm5cFWef7o3vs486CP8VdQIEVRcU7sFE7VecgSmqvKDgVxEJqi8ogIof2xVnH2YLCuMT1fAU7RirOPtrXHCsovmCwlDFCgoKWNH4IrMBTdQ/NUzRjiu3CeCq9HAPAVSspaDgX9FkQcG3ollB34qGBf0UTQv6KBoXHFc0LzimWIFg0ywGBBelBxcHXLGKggKqWElBwV2xIkF3xaoEXYqVCe4rVifYV3wpPZwULOouKLzUXVBY1F1QeKm7oLCoXVAqVi7YNM7/F0YIIYQQQgghhBBCCCGEEEIIIYQQQgghhBBCCCG+/ANh4i1CHdc63QAAAABJRU5ErkJggg=="
+ width="15px" height="15px"/>
+ </div>
</div>`
);
export function ImageOverlay(width: number, height: number, field: string = "thumbnail") {
return (`<div>
- <div style="height:100%; width:100%;position:absolute;">{layout}</div>
- <div style="width:${width}px; height:${height}px; top:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
- <ImageBox id="isExpander" {...props} PanelWidth={${width}} fieldKey={"${field}"} />
+ <div style="height:100%; width:100%; position:absolute;">{layout}</div>
+ <div style="height:auto; width:${width}px; bottom:0; right:0; background:rgba(0,0,0,0.25); position:absolute;overflow:hidden;">
+ <ImageBox id="isExpander" {...props} style="width:100%; height=auto;" PanelWidth={${width}} fieldKey={"${field}"} />
</div>
</div>`);
}
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 645296d27..84ffbac36 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -1,4 +1,4 @@
-import { action, computed } from 'mobx';
+import { action, computed, observable } from 'mobx';
import { observer } from 'mobx-react';
import * as React from 'react';
import { ContextMenu } from '../ContextMenu';
@@ -36,9 +36,20 @@ export interface CollectionViewProps extends FieldViewProps {
@observer
export class CollectionBaseView extends React.Component<CollectionViewProps> {
+ @observable private static _safeMode = false;
+ static InSafeMode() { return this._safeMode; }
+ static SetSafeMode(safeMode: boolean) { this._safeMode = safeMode; }
get collectionViewType(): CollectionViewType | undefined {
let Document = this.props.Document;
let viewField = Cast(Document.viewType, "number");
+ if (CollectionBaseView._safeMode) {
+ if (viewField === CollectionViewType.Freeform) {
+ return CollectionViewType.Tree;
+ }
+ if (viewField === CollectionViewType.Invalid) {
+ return CollectionViewType.Freeform;
+ }
+ }
if (viewField !== undefined) {
return viewField;
} else {
@@ -82,12 +93,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
}
return false;
}
- @computed get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
+ @computed get isAnnotationOverlay() { return this.props.fieldKey === "annotations"; }
@action.bound
addDocument(doc: Doc, allowDuplicates: boolean = false): boolean {
let props = this.props;
- var curPage = Cast(props.Document.curPage, "number", -1);
+ var curPage = NumCast(props.Document.curPage, -1);
Doc.SetOnPrototype(doc, "page", curPage);
if (curPage >= 0) {
Doc.SetOnPrototype(doc, "annotationOn", props.Document);
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index efcee9c02..06b262d79 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,11 +1,11 @@
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction } from "mobx";
+import { action, observable, reaction, Lambda } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
import Measure from "react-measure";
import * as GoldenLayout from "../../../client/goldenLayout";
-import { Doc, Field, Opt } from "../../../new_fields/Doc";
+import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
import { FieldId, Id } from "../../../new_fields/RefField";
import { listSpec } from "../../../new_fields/Schema";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
@@ -18,6 +18,9 @@ import { DocumentView } from "../nodes/DocumentView";
import "./CollectionDockingView.scss";
import { SubCollectionViewProps } from "./CollectionSubView";
import React = require("react");
+import { ParentDocSelector } from './ParentDocumentSelector';
+import { DocumentManager } from '../../util/DocumentManager';
+import { CollectionViewType } from './CollectionBaseView';
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -72,32 +75,40 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@undoBatch
@action
- public CloseRightSplit(document: Doc) {
+ public CloseRightSplit(document: Doc): boolean {
+ let retVal = false;
if (this._goldenLayout.root.contentItems[0].isRow) {
- this._goldenLayout.root.contentItems[0].contentItems.map((child: any, i: number) => {
+ retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
- child.contentItems[0].config.props.documentId == document[Id]) {
+ Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
child.contentItems[0].remove();
this.layoutChanged(document);
- this.stateChanged();
- } else
- child.contentItems.map((tab: any, j: number) => {
- if (tab.config.component === "DocumentFrameRenderer" && tab.config.props.documentId === document[Id]) {
+ return true;
+ } else {
+ Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
+ if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
child.contentItems[j].remove();
child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
let docs = Cast(this.props.Document.data, listSpec(Doc));
docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
- this.stateChanged();
+ return true;
}
+ return false;
});
- })
+ }
+ return false;
+ });
+ }
+ if (retVal) {
+ this.stateChanged();
}
+ return retVal;
}
@action
layoutChanged(removed?: Doc) {
this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
- this._goldenLayout.emit('sbcreteChanged');
+ this._goldenLayout.emit('stateChanged');
this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
this.stateChanged();
@@ -143,6 +154,17 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
return newContentItem;
}
+ @action
+ public AddTab(stack: any, document: Doc) {
+ let docs = Cast(this.props.Document.data, listSpec(Doc));
+ if (docs) {
+ docs.push(document);
+ }
+ let docContentConfig = CollectionDockingView.makeDocumentConfig(document);
+ var newContentItem = stack.layoutManager.createContentItem(docContentConfig, this._goldenLayout);
+ stack.addChild(newContentItem.contentItems[0], undefined);
+ this.layoutChanged();
+ }
setupGoldenLayout() {
var config = StrCast(this.props.Document.dockingConfig);
@@ -157,6 +179,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
try {
this._goldenLayout.unbind('itemDropped', this.itemDropped);
this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
this._goldenLayout.unbind('stackCreated', this.stackCreated);
} catch (e) { }
this._goldenLayout.destroy();
@@ -164,6 +187,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
this._goldenLayout.on('itemDropped', this.itemDropped);
this._goldenLayout.on('tabCreated', this.tabCreated);
+ this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
this._goldenLayout.on('stackCreated', this.stackCreated);
this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
this._goldenLayout.container = this._containerRef.current;
@@ -177,12 +201,15 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this._goldenLayout.init();
}
}
+ reactionDisposer?: Lambda;
componentDidMount: () => void = () => {
if (this._containerRef.current) {
- reaction(
+ this.reactionDisposer = reaction(
() => StrCast(this.props.Document.dockingConfig),
() => {
if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
+ // Because this is in a set timeout, if this component unmounts right after mounting,
+ // we will leak a GoldenLayout, because we try to destroy it before we ever create it
setTimeout(() => this.setupGoldenLayout(), 1);
}
this._ignoreStateChange = "";
@@ -196,12 +223,17 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this._goldenLayout.unbind('itemDropped', this.itemDropped);
this._goldenLayout.unbind('tabCreated', this.tabCreated);
this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
} catch (e) {
}
if (this._goldenLayout) this._goldenLayout.destroy();
this._goldenLayout = null;
window.removeEventListener('resize', this.onResize);
+
+ if (this.reactionDisposer) {
+ this.reactionDisposer();
+ }
}
@action
onResize = (event: any) => {
@@ -238,6 +270,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
let y = e.clientY;
let docid = (e.target as any).DashDocId;
let tab = (e.target as any).parentElement as HTMLElement;
+ let glTab = (e.target as any).Tab;
+ if (glTab && glTab.contentItem && glTab.contentItem.parent) {
+ glTab.contentItem.parent.setActiveContentItem(glTab.contentItem);
+ }
DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
if (f instanceof Doc) {
DragManager.StartDocumentDrag([tab], new DragManager.DocumentDragData([f]), x, y,
@@ -292,21 +328,23 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
DocServer.GetRefField(tab.contentItem.config.props.documentId).then(async doc => {
if (doc instanceof Doc) {
- let counter: any = this.htmlToElement(`<div class="messageCounter">0</div>`);
+ let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`);
tab.element.append(counter);
+ let upDiv = document.createElement("span");
+ ReactDOM.render(<ParentDocSelector Document={doc} />, upDiv);
+ tab.reactComponents = [upDiv];
+ tab.element.append(upDiv);
counter.DashDocId = tab.contentItem.config.props.documentId;
tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
() => {
- const lf = Cast(doc.linkedFromDocs, listSpec(Doc), []);
- const lt = Cast(doc.linkedToDocs, listSpec(Doc), []);
- let count = (lf ? lf.length : 0) + (lt ? lt.length : 0);
- counter.innerHTML = count;
+ counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length;
tab.titleElement[0].textContent = doc.title;
}, { fireImmediately: true });
tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
}
});
}
+ tab.titleElement[0].Tab = tab;
tab.closeElement.off('click') //unbind the current click handler
.click(async function () {
if (tab.reactionDisposer) {
@@ -320,6 +358,14 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
tab.contentItem.remove();
});
}
+
+ tabDestroyed = (tab: any) => {
+ if (tab.reactComponents) {
+ for (const ele of tab.reactComponents) {
+ ReactDOM.unmountComponentAtNode(ele);
+ }
+ }
+ }
_removedDocs: Doc[] = [];
stackCreated = (stack: any) => {
@@ -366,9 +412,10 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
@observable private _panelWidth = 0;
@observable private _panelHeight = 0;
@observable private _document: Opt<Doc>;
-
+ _stack: any;
constructor(props: any) {
super(props);
+ this._stack = (this.props as any).glContainer.parent.parent;
DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
}
@@ -385,19 +432,37 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
if (this._mainCont.current && this._mainCont.current.children) {
let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement);
scale = Utils.GetScreenTransform(this._mainCont.current).scale;
- return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(scale / this.contentScaling());
+ return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / scale);
}
return Transform.Identity();
}
+ get scaleToFitMultiplier() {
+ let docWidth = NumCast(this._document!.width);
+ let docHeight = NumCast(this._document!.height);
+ if (NumCast(this._document!.nativeWidth) || !docWidth || !this._panelWidth || !this._panelHeight) return 1;
+ if (StrCast(this._document!.layout).indexOf("Collection") === -1 ||
+ NumCast(this._document!.viewType) !== CollectionViewType.Freeform) return 1;
+ let scaling = Math.max(1, this._panelWidth / docWidth * docHeight > this._panelHeight ?
+ this._panelHeight / docHeight : this._panelWidth / docWidth);
+ console.log("Scaling = " + scaling);
+ return scaling;
+ }
get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
+ addDocTab = (doc: Doc, location: string) => {
+ if (location === "onRight") {
+ CollectionDockingView.Instance.AddRightSplit(doc);
+ } else {
+ CollectionDockingView.Instance.AddTab(this._stack, doc);
+ }
+ }
get content() {
if (!this._document) {
return (null);
}
return (
<div className="collectionDockingView-content" ref={this._mainCont}
- style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
+ style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
<DocumentView key={this._document[Id]} Document={this._document}
toggleMinimized={emptyFunction}
bringToFront={emptyFunction}
@@ -412,6 +477,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
parentActive={returnTrue}
whenActiveChanged={emptyFunction}
focus={emptyFunction}
+ addDocTab={this.addDocTab}
ContainingCollectionView={undefined} />
</div >);
}
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index b3762206a..a6614da21 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -61,7 +61,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "PDFOptions", event: emptyFunction });
+ ContextMenu.Instance.addItem({ description: "PDFOptions", event: emptyFunction, icon: "file-pdf" });
}
}
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index cfdb3ab22..df5c52d01 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -4,7 +4,7 @@
.collectionSchemaView-container {
border-width: $COLLECTION_BORDER_WIDTH;
- border-color : $intermediate-color;
+ border-color: $intermediate-color;
border-style: solid;
border-radius: $border-radius;
box-sizing: border-box;
@@ -14,42 +14,52 @@
.collectionSchemaView-cellContents {
height: $MAX_ROW_HEIGHT;
+
+ img {
+ width: auto;
+ max-height: $MAX_ROW_HEIGHT;
+ }
}
-
+
.collectionSchemaView-previewRegion {
position: relative;
background: $light-color;
float: left;
height: 100%;
+
.collectionSchemaView-previewDoc {
height: 100%;
- width: 100%;
+ width: 100%;
position: absolute;
}
+
.collectionSchemaView-input {
position: absolute;
max-width: 150px;
width: 100%;
bottom: 0px;
}
+
.documentView-node:first-child {
position: relative;
background: $light-color;
}
}
+
.collectionSchemaView-previewHandle {
position: absolute;
height: 15px;
- width: 15px;
- z-index: 20;
- right: 0;
- top: 20px;
- background: Black ;
+ width: 15px;
+ z-index: 20;
+ right: 0;
+ top: 20px;
+ background: Black;
}
- .collectionSchemaView-dividerDragger{
- position: relative;
- background: black;
- float: left;
+
+ .collectionSchemaView-dividerDragger {
+ position: relative;
+ background: black;
+ float: left;
height: 37px;
width: 20px;
z-index: 20;
@@ -57,6 +67,7 @@
top: 0;
background: $main-accent;
}
+
.collectionSchemaView-columnsHandle {
position: absolute;
height: 37px;
@@ -66,6 +77,7 @@
bottom: 0;
background: $main-accent;
}
+
.collectionSchemaView-colDividerDragger {
position: relative;
box-sizing: border-box;
@@ -74,6 +86,7 @@
float: top;
width: 100%;
}
+
.collectionSchemaView-dividerDragger {
position: relative;
box-sizing: border-box;
@@ -82,11 +95,13 @@
float: left;
height: 100%;
}
+
.collectionSchemaView-tableContainer {
position: relative;
float: left;
height: 100%;
}
+
.ReactTable {
// position: absolute; // display: inline-block;
// overflow: auto;
@@ -95,6 +110,7 @@
background: $light-color;
box-sizing: border-box;
border: none !important;
+
.rt-table {
overflow-y: auto;
overflow-x: auto;
@@ -103,42 +119,50 @@
direction: ltr; // direction:rtl;
// display:block;
}
+
.rt-tbody {
//direction: ltr;
direction: rtl;
}
+
.rt-tr-group {
direction: ltr;
max-height: $MAX_ROW_HEIGHT;
}
+
.rt-td {
border-width: 1px;
border-right-color: $intermediate-color;
+
.imageBox-cont {
position: relative;
max-height: 100%;
}
+
.imageBox-cont img {
object-fit: contain;
max-width: 100%;
height: 100%;
}
- .videobox-cont {
+
+ .videoBox-cont {
object-fit: contain;
width: auto;
height: 100%;
}
}
}
+
.ReactTable .rt-thead.-header {
background: $intermediate-color;
color: $light-color;
- text-transform: uppercase;
+ // text-transform: uppercase;
letter-spacing: 2px;
font-size: 12px;
height: 30px;
padding-top: 4px;
}
+
.ReactTable .rt-th,
.ReactTable .rt-td {
max-height: $MAX_ROW_HEIGHT;
@@ -146,32 +170,36 @@
font-size: 13px;
text-align: center;
}
+
.ReactTable .rt-tbody .rt-tr-group:last-child {
border-bottom: $intermediate-color;
border-bottom-style: solid;
border-bottom-width: 1;
}
+
.documentView-node-topmost {
- text-align:left;
+ text-align: left;
transform-origin: center top;
display: inline-block;
}
+
.documentView-node:first-child {
background: $light-color;
}
}
+
//options menu styling
#schemaOptionsMenuBtn {
position: absolute;
height: 20px;
width: 20px;
border-radius: 50%;
- z-index: 21;
+ z-index: 21;
right: 4px;
- top: 4px;
+ top: 4px;
pointer-events: auto;
- background-color:black;
- display:inline-block;
+ background-color: black;
+ display: inline-block;
padding: 0px;
font-size: 100%;
}
@@ -185,10 +213,12 @@ ul {
padding: 0px;
margin: 0px;
}
+
.schema-options-subHeader {
color: $intermediate-color;
margin-bottom: 5px;
}
+
#schemaOptionsMenuBtn:hover {
transform: scale(1.15);
}
@@ -198,15 +228,15 @@ ul {
font-size: 12px;
}
- #options-flyout-div {
+#options-flyout-div {
text-align: left;
- padding:0px;
+ padding: 0px;
z-index: 100;
font-family: $sans-serif;
padding-left: 5px;
- }
+}
- #schema-col-checklist {
+#schema-col-checklist {
overflow: scroll;
text-align: left;
//background-color: $light-color-secondary;
@@ -214,8 +244,8 @@ ul {
max-height: 175px;
font-family: $sans-serif;
font-size: 12px;
- }
-
+}
+
.Resizer {
box-sizing: border-box;
@@ -223,6 +253,7 @@ ul {
opacity: 0.5;
z-index: 1;
background-clip: padding-box;
+
&.horizontal {
height: 11px;
margin: -5px 0;
@@ -230,22 +261,26 @@ ul {
border-bottom: 5px solid rgba(255, 255, 255, 0);
cursor: row-resize;
width: 100%;
+
&:hover {
border-top: 5px solid rgba(0, 0, 0, 0.5);
border-bottom: 5px solid rgba(0, 0, 0, 0.5);
}
}
+
&.vertical {
width: 11px;
margin: 0 -5px;
border-left: 5px solid rgba(255, 255, 255, 0);
border-right: 5px solid rgba(255, 255, 255, 0);
cursor: col-resize;
+
&:hover {
border-left: 5px solid rgba(0, 0, 0, 0.5);
border-right: 5px solid rgba(0, 0, 0, 0.5);
}
}
+
&:hover {
-webkit-transition: all 2s ease;
transition: all 2s ease;
@@ -266,10 +301,12 @@ ul {
-ms-flex-direction: column;
flex-direction: column;
}
+
header {
padding: 1rem;
background: #eee;
}
+
footer {
padding: 1rem;
background: #eee;
@@ -283,10 +320,12 @@ ul {
display: flex;
flex-direction: column;
}
+
header {
padding: 1rem;
background: #eee;
}
+
footer {
padding: 1rem;
background: #eee;
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 18319dc77..f15da41ff 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -20,10 +20,13 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
import { Opt, Field, Doc, DocListCastAsync, DocListCast } from "../../../new_fields/Doc";
-import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
import { listSpec } from "../../../new_fields/Schema";
import { List } from "../../../new_fields/List";
import { Id } from "../../../new_fields/RefField";
+import { Gateway } from "../../northstar/manager/Gateway";
+import { Docs } from "../../documents/Documents";
+import { ContextMenu } from "../ContextMenu";
// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
@@ -61,10 +64,25 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@computed get columns() { return Cast(this.props.Document.schemaColumns, listSpec("string"), []); }
@computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); }
+ @computed get tableColumns() {
+ return this.columns.map(col => {
+ const ref = React.createRef<HTMLParagraphElement>();
+ return {
+ Header: <p ref={ref} onPointerDown={SetupDrag(ref, () => this.onHeaderDrag(col))}>{col}</p>,
+ accessor: (doc: Doc) => doc ? doc[col] : 0,
+ id: col
+ };
+ });
+ }
+
+ onHeaderDrag = (columnName: string) => {
+ return this.props.Document;
+ }
+
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
- Document: rowProps.value[0],
- fieldKey: rowProps.value[1],
+ Document: rowProps.original,
+ fieldKey: rowProps.column.id as string,
ContainingCollectionView: this.props.CollectionView,
isSelected: returnFalse,
select: emptyFunction,
@@ -76,24 +94,24 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
whenActiveChanged: emptyFunction,
PanelHeight: returnZero,
PanelWidth: returnZero,
+ addDocTab: this.props.addDocTab,
};
- let contents = (
- <FieldView {...props} />
- );
+ let fieldContentView = <FieldView {...props} />;
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = SetupDrag(reference, () => props.Document, this.props.moveDocument);
+ let onItemDown = (e: React.PointerEvent) =>
+ (this.props.CollectionView.props.isSelected() ?
+ SetupDrag(reference, () => props.Document, this.props.moveDocument)(e) : undefined);
let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
const res = run({ this: doc });
if (!res.success) return false;
- const field = res.result;
- doc[props.fieldKey] = field;
+ doc[props.fieldKey] = res.result;
return true;
};
return (
<div className="collectionSchemaView-cellContents" onPointerDown={onItemDown} key={props.Document[Id]} ref={reference}>
<EditableView
display={"inline"}
- contents={contents}
+ contents={fieldContentView}
height={Number(MAX_ROW_HEIGHT)}
GetValue={() => {
let field = props.Document[props.fieldKey];
@@ -118,7 +136,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
const run = script.run;
//TODO This should be able to be refactored to compile the script once
- const val = await DocListCastAsync(this.props.Document[this.props.fieldKey])
+ const val = await DocListCastAsync(this.props.Document[this.props.fieldKey]);
val && val.forEach(doc => applyToDoc(doc, run));
}}>
</EditableView>
@@ -207,6 +225,32 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
}
+ onContextMenu = (e: React.MouseEvent): void => {
+ if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
+ ContextMenu.Instance.addItem({ description: "Make DB", event: this.makeDB });
+ }
+ }
+
+ @action
+ makeDB = async () => {
+ let csv: string = this.columns.reduce((val, col) => val + col + ",", "");
+ csv = csv.substr(0, csv.length - 1) + "\n";
+ let self = this;
+ DocListCast(this.props.Document.data).map(doc => {
+ csv += self.columns.reduce((val, col) => val + (doc[col] ? doc[col]!.toString() : "") + ",", "");
+ csv = csv.substr(0, csv.length - 1) + "\n";
+ });
+ csv.substring(0, csv.length - 1);
+ let dbName = StrCast(this.props.Document.title);
+ let res = await Gateway.Instance.PostSchema(csv, dbName);
+ if (self.props.CollectionView.props.addDocument) {
+ let schemaDoc = await Docs.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName });
+ if (schemaDoc) {
+ self.props.CollectionView.props.addDocument(schemaDoc, false);
+ }
+ }
+ }
+
@action
addColumn = () => {
this.columns.push(this._newKeyName);
@@ -224,10 +268,11 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
this.previewScript = e.currentTarget.value;
}
+ @computed
get previewDocument(): Doc | undefined {
- const children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
+ const children = DocListCast(this.props.Document[this.props.fieldKey]);
const selected = children.length > this._selectedIndex ? FieldValue(children[this._selectedIndex]) : undefined;
- return selected ? (this.previewScript ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
+ return selected ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
}
get tableWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * (1 - this.splitPercentage / 100); }
get previewRegionHeight() { return this.props.PanelHeight() - 2 * this.borderWidth; }
@@ -253,8 +298,8 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
get previewPanel() {
// let doc = CompileScript(this.previewScript, { this: selected }, true)();
const previewDoc = this.previewDocument;
- return !previewDoc || !this.previewRegionWidth ? (null) : (
- <div className="collectionSchemaView-previewRegion" style={{ width: `${this.previewRegionWidth}px` }}>
+ return (<div className="collectionSchemaView-previewRegion" style={{ width: `${Math.max(0, this.previewRegionWidth - 1)}px` }}>
+ {!previewDoc || !this.previewRegionWidth ? (null) : (
<div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}>
<DocumentView Document={previewDoc} isTopMost={false} selectOnLoad={false}
toggleMinimized={emptyFunction}
@@ -267,12 +312,12 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
parentActive={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
bringToFront={emptyFunction}
+ addDocTab={this.props.addDocTab}
/>
- </div>
- <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange}
- style={{ left: `calc(50% - ${Math.min(75, this.previewPanelWidth() / 2)}px)` }} />
- </div>
- );
+ </div>)}
+ <input className="collectionSchemaView-input" value={this.previewScript} onChange={this.onPreviewScriptChange}
+ style={{ left: `calc(50% - ${Math.min(75, (previewDoc ? this.previewPanelWidth() / 2 : 75))}px)` }} />
+ </div>);
}
get documentKeysCheckList() {
@@ -319,20 +364,18 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
}
+
+
render() {
library.add(faCog);
library.add(faPlus);
- const children = this.children;
+ const children = this.childDocs;
return (
<div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
- onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createTarget}>
+ onDrop={(e: React.DragEvent) => this.onDrop(e, {})} onContextMenu={this.onContextMenu} ref={this.createTarget}>
<div className="collectionSchemaView-tableContainer" style={{ width: `${this.tableWidth}px` }}>
<ReactTable data={children} page={0} pageSize={children.length} showPagination={false}
- columns={this.columns.map(col => ({
- Header: col,
- accessor: (doc: Doc) => [doc, col],
- id: col
- }))}
+ columns={this.tableColumns}
column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
getTrProps={this.getTrProps}
/>
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index a86d250bd..864fdfa4b 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -18,6 +18,7 @@ import { List } from "../../../new_fields/List";
import { DocServer } from "../../DocServer";
import { ObjectField } from "../../../new_fields/ObjectField";
import CursorField, { CursorPosition, CursorMetadata } from "../../../new_fields/CursorField";
+import { url } from "inspector";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -46,7 +47,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.createDropTarget(ele);
}
- get children() {
+ get childDocs() {
//TODO tfs: This might not be what we want?
//This linter error can't be fixed because of how js arguments work, so don't switch this to filter(FieldValue)
return DocListCast(this.props.Document[this.props.fieldKey]);
@@ -168,6 +169,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.props.addDocument(htmlDoc, false);
return;
}
+ if (text && text.indexOf("www.youtube.com/watch") !== -1) {
+ const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
+ this.props.addDocument(Docs.WebDocument(url, { ...options, width: 300, height: 300 }));
+ return;
+ }
let batch = UndoManager.StartBatch("collection view drop");
let promises: Promise<void>[] = [];
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 411d67ff7..5f82137c6 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -29,23 +29,24 @@
}
.bullet {
- float:left;
+ float: left;
position: relative;
width: 15px;
display: block;
color: $intermediate-color;
margin-top: 3px;
- transform: scale(1.3,1.3);
+ transform: scale(1.3, 1.3);
}
.docContainer {
margin-left: 10px;
display: block;
- // width:100%;//width: max-content;
+ // width:100%;//width: max-content;
}
+
.docContainer:hover {
.treeViewItem-openRight {
- display:inline;
+ display: inline;
}
}
@@ -61,10 +62,12 @@
// margin-top: 3px;
display: inline;
}
+
.treeViewItem-openRight {
margin-left: 5px;
- display:none;
+ display: none;
}
+
.docContainer:hover {
.delete-button {
display: inline;
@@ -73,14 +76,16 @@
}
.coll-title {
- width:max-content;
+ width: max-content;
display: block;
font-size: 24px;
}
+
.collection-child {
margin-top: 10px;
margin-bottom: 10px;
}
+
.collectionTreeView-keyHeader {
font-style: italic;
font-size: 8pt;
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index d22418b2c..72fa69cb1 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -14,12 +14,11 @@ import { Doc, DocListCast } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/RefField';
import { ContextMenu } from '../ContextMenu';
import { undoBatch } from '../../util/UndoManager';
-import { Main } from '../Main';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
import { CollectionDockingView } from './CollectionDockingView';
import { DocumentManager } from '../../util/DocumentManager';
-import { List } from '../../../new_fields/List';
import { Docs } from '../../documents/Documents';
+import { MainView } from '../MainView';
export interface TreeViewProps {
@@ -52,11 +51,11 @@ class TreeView extends React.Component<TreeViewProps> {
@undoBatch openRight = async () => {
if (this.props.document.dockingConfig) {
- Main.Instance.openWorkspace(this.props.document);
+ MainView.Instance.openWorkspace(this.props.document);
} else {
CollectionDockingView.Instance.AddRightSplit(this.props.document);
}
- };
+ }
get children() {
return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed? .filter(doc => FieldValue(doc));
@@ -112,7 +111,7 @@ class TreeView extends React.Component<TreeViewProps> {
let editableView = (titleString: string) =>
(<EditableView
oneLine={!this._isOver ? true : false}
- display={"block"}
+ display={"inline-block"}
contents={titleString}
height={36}
GetValue={() => StrCast(this.props.document.title)}
@@ -130,7 +129,7 @@ class TreeView extends React.Component<TreeViewProps> {
</div>);
return (
<div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
- style={{ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
+ style={{ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
{editableView(StrCast(this.props.document.title))}
{openRight}
@@ -140,7 +139,7 @@ class TreeView extends React.Component<TreeViewProps> {
onWorkspaceContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => Main.Instance.openWorkspace(this.props.document)) });
+ ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.props.document)) });
ContextMenu.Instance.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) });
ContextMenu.Instance.addItem({
description: "Open Fields", event: () => CollectionDockingView.Instance.AddRightSplit(Docs.KVPDocument(this.props.document,
@@ -184,8 +183,9 @@ class TreeView extends React.Component<TreeViewProps> {
{TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
</div>
</ul >);
- } else
+ } else {
bulletType = BulletType.Collapsed;
+ }
}
});
return <div className="treeViewItem-container"
@@ -198,8 +198,8 @@ class TreeView extends React.Component<TreeViewProps> {
</div>;
}
public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType) {
- return docs.filter(child => child instanceof Doc && !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).filter(doc => FieldValue(doc)).map(child =>
- <TreeView document={child as Doc} key={(child as Doc)[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
+ return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child =>
+ <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} />);
}
}
@@ -214,7 +214,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
}
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => Main.Instance.createNewWorkspace()) });
+ ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()) });
}
if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) });
@@ -222,16 +222,16 @@ export class CollectionTreeView extends CollectionSubView(Document) {
}
render() {
let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
- if (!this.children) {
+ if (!this.childDocs) {
return (null);
}
- let childElements = TreeView.GetChildElements(this.children, false, this.remove, this.props.moveDocument, dropAction);
+ let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction);
return (
<div id="body" className="collectionTreeView-dropTarget"
style={{ borderRadius: "inherit" }}
onContextMenu={this.onContextMenu}
- onWheel={(e: React.WheelEvent) => e.stopPropagation()}
+ onWheel={(e: React.WheelEvent) => this.props.isSelected() && e.stopPropagation()}
onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
<div className="coll-title">
<EditableView
diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss
index ed56ad268..db8b84832 100644
--- a/src/client/views/collections/CollectionVideoView.scss
+++ b/src/client/views/collections/CollectionVideoView.scss
@@ -2,7 +2,7 @@
.collectionVideoView-cont{
width: 100%;
height: 100%;
- position: absolute;
+ position: inherit;
top: 0;
left:0;
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index cb3fd1ba4..9ab959f3c 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -9,27 +9,26 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
import { emptyFunction } from "../../../Utils";
import { Id } from "../../../new_fields/RefField";
import { VideoBox } from "../nodes/VideoBox";
+import { NumCast } from "../../../new_fields/Types";
@observer
export class CollectionVideoView extends React.Component<FieldViewProps> {
- private _videoBox: VideoBox | undefined = undefined;
- @observable _playTimer?: NodeJS.Timeout = undefined;
-
- @observable _currentTimecode: number = 0;
+ private _videoBox?: VideoBox;
public static LayoutString(fieldKey: string = "data") {
return FieldView.LayoutString(CollectionVideoView, fieldKey);
}
private get uIButtons() {
let scaling = Math.min(1.8, this.props.ScreenToLocalTransform().Scale);
+ let curTime = NumCast(this.props.Document.curPage);
return ([
<div className="collectionVideoView-time" key="time" onPointerDown={this.onResetDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
- <span>{"" + Math.round(this._currentTimecode)}</span>
- <span style={{ fontSize: 8 }}>{" " + Math.round((this._currentTimecode - Math.trunc(this._currentTimecode)) * 100)}</span>
+ <span>{"" + Math.round(curTime)}</span>
+ <span style={{ fontSize: 8 }}>{" " + Math.round((curTime - Math.trunc(curTime)) * 100)}</span>
</div>,
<div className="collectionVideoView-play" key="play" onPointerDown={this.onPlayDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
- {this._playTimer ? "\"" : ">"}
+ {this._videoBox && this._videoBox.Playing ? "\"" : ">"}
</div>,
<div className="collectionVideoView-full" key="full" onPointerDown={this.onFullDown} style={{ transform: `scale(${scaling}, ${scaling})` }}>
F
@@ -38,36 +37,20 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
}
@action
- updateTimecode = () => {
- if (this._videoBox && this._videoBox.player) {
- this._currentTimecode = this._videoBox.player.currentTime;
- this.props.Document.curPage = Math.round(this._currentTimecode);
- }
- }
-
- componentDidMount() { this.updateTimecode(); }
-
- componentWillUnmount() { if (this._playTimer) clearInterval(this._playTimer); }
-
- @action
onPlayDown = () => {
if (this._videoBox && this._videoBox.player) {
- if (this._videoBox.player.paused) {
- this._videoBox.player.play();
- if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 1000);
+ if (this._videoBox.Playing) {
+ this._videoBox.Pause();
} else {
- this._videoBox.player.pause();
- if (this._playTimer) clearInterval(this._playTimer);
- this._playTimer = undefined;
-
+ this._videoBox.Play();
}
}
}
@action
onFullDown = (e: React.PointerEvent) => {
- if (this._videoBox && this._videoBox.player) {
- this._videoBox.player.requestFullscreen();
+ if (this._videoBox) {
+ this._videoBox.FullScreen();
e.stopPropagation();
e.preventDefault();
}
@@ -75,22 +58,18 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
@action
onResetDown = () => {
- if (this._videoBox && this._videoBox.player) {
- this._videoBox.player.pause();
- this._videoBox.player.currentTime = 0;
- if (this._playTimer) clearInterval(this._playTimer);
- this._playTimer = undefined;
- this.updateTimecode();
+ if (this._videoBox) {
+ this._videoBox.Pause();
+ this.props.Document.curPage = 0;
}
}
onContextMenu = (e: React.MouseEvent): void => {
if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "VideoOptions", event: emptyFunction });
}
}
- setVideoBox = (player: VideoBox) => { this._videoBox = player; };
+ setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
let props = { ...this.props, ...renderProps };
@@ -101,6 +80,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
}
render() {
+ trace();
return (
<CollectionBaseView {...this.props} className="collectionVideoView-cont" onContextMenu={this.onContextMenu}>
{this.subView}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 8c1442d38..b9ffc11a2 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,17 +1,23 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faProjectDiagram, faSquare, faTh, faTree } from '@fortawesome/free-solid-svg-icons';
+import { observer } from "mobx-react";
import * as React from 'react';
-import { FieldViewProps, FieldView } from '../nodes/FieldView';
-import { CollectionBaseView, CollectionViewType, CollectionRenderProps } from './CollectionBaseView';
-import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
-import { CollectionSchemaView } from './CollectionSchemaView';
-import { CollectionDockingView } from './CollectionDockingView';
-import { CollectionTreeView } from './CollectionTreeView';
-import { ContextMenu } from '../ContextMenu';
+import { Id } from '../../../new_fields/RefField';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { observer } from 'mobx-react';
import { undoBatch } from '../../util/UndoManager';
-import { trace } from 'mobx';
-import { Id } from '../../../new_fields/RefField';
-import { Main } from '../Main';
+import { ContextMenu } from "../ContextMenu";
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView';
+import { CollectionDockingView } from "./CollectionDockingView";
+import { CollectionSchemaView } from "./CollectionSchemaView";
+import { CollectionTreeView } from "./CollectionTreeView";
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+export const COLLECTION_BORDER_WIDTH = 2;
+
+library.add(faTh);
+library.add(faTree);
+library.add(faSquare);
+library.add(faProjectDiagram);
@observer
export class CollectionView extends React.Component<FieldViewProps> {
@@ -34,9 +40,12 @@ export class CollectionView extends React.Component<FieldViewProps> {
onContextMenu = (e: React.MouseEvent): void => {
if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7
- ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform) });
- ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema) });
- ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree) });
+ ContextMenu.Instance.addItem({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "project-diagram" });
+ if (CollectionBaseView.InSafeMode()) {
+ ContextMenu.Instance.addItem({ description: "Test Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Invalid), icon: "project-diagram" });
+ }
+ ContextMenu.Instance.addItem({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "project-diagram" });
+ ContextMenu.Instance.addItem({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
}
}
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
new file mode 100644
index 000000000..f3c605f3e
--- /dev/null
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -0,0 +1,8 @@
+.PDS-flyout {
+ position: absolute;
+ z-index: 9999;
+ background-color: #d3d3d3;
+ box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
+ min-width: 150px;
+ color: black;
+} \ No newline at end of file
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
new file mode 100644
index 000000000..52f7914f3
--- /dev/null
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -0,0 +1,66 @@
+import * as React from "react";
+import './ParentDocumentSelector.scss';
+import { Doc } from "../../../new_fields/Doc";
+import { observer } from "mobx-react";
+import { observable, action, runInAction } from "mobx";
+import { Id } from "../../../new_fields/RefField";
+import { SearchUtil } from "../../util/SearchUtil";
+import { CollectionDockingView } from "./CollectionDockingView";
+
+@observer
+export class SelectorContextMenu extends React.Component<{ Document: Doc }> {
+ @observable private _docs: Doc[] = [];
+
+ constructor(props: { Document: Doc }) {
+ super(props);
+
+ this.fetchDocuments();
+ }
+
+ async fetchDocuments() {
+ const docs = await SearchUtil.Search(`data_l:"${this.props.Document[Id]}"`, true);
+ runInAction(() => this._docs = docs);
+ }
+
+ render() {
+ return (
+ <>
+ {this._docs.map(doc => <p><a onClick={() => CollectionDockingView.Instance.AddRightSplit(doc)}>{doc.title}</a></p>)}
+ </>
+ );
+ }
+}
+
+@observer
+export class ParentDocSelector extends React.Component<{ Document: Doc }> {
+ @observable hover = false;
+
+ @action
+ onMouseLeave = () => {
+ this.hover = false;
+ }
+
+ @action
+ onMouseEnter = () => {
+ this.hover = true;
+ }
+
+ render() {
+ let flyout;
+ if (this.hover) {
+ flyout = (
+ <div className="PDS-flyout">
+ <SelectorContextMenu Document={this.props.Document} />
+ </div>
+ );
+ }
+ return (
+ <span style={{ position: "relative", display: "inline-block" }}
+ onMouseEnter={this.onMouseEnter}
+ onMouseLeave={this.onMouseLeave}>
+ <p>^</p>
+ {flyout}
+ </span>
+ );
+ }
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index dc86f38b6..9cb8443f4 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -20,10 +20,11 @@ import React = require("react");
import v5 = require("uuid/v5");
import { createSchema, makeInterface, listSpec } from "../../../../new_fields/Schema";
import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
-import { FieldValue, Cast, NumCast } from "../../../../new_fields/Types";
+import { FieldValue, Cast, NumCast, BoolCast } from "../../../../new_fields/Types";
import { pageSchema } from "../../nodes/ImageBox";
import { Id } from "../../../../new_fields/RefField";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { HistoryUtil } from "../../../util/History";
export const panZoomSchema = createSchema({
panX: "number",
@@ -36,23 +37,22 @@ 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;
private get _pwidth() { return this.props.PanelWidth(); }
private get _pheight() { return this.props.PanelHeight(); }
- @computed get nativeWidth() { return NumCast(this.Document.nativeWidth, 0); }
- @computed get nativeHeight() { return NumCast(this.Document.nativeHeight, 0); }
+ @computed get nativeWidth() { return this.Document.nativeWidth || 0; }
+ @computed get nativeHeight() { return this.Document.nativeHeight || 0; }
+ public get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
- private get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; }
- private panX = () => FieldValue(this.Document.panX, 0);
- private panY = () => FieldValue(this.Document.panY, 0);
- private zoomScaling = () => FieldValue(this.Document.scale, 1);
+ private panX = () => this.Document.panX || 0;
+ private panY = () => this.Document.panY || 0;
+ private zoomScaling = () => this.Document.scale || 1;
private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0; // shift so pan position is at center of window for non-overlay collections
private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections
- private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
+ private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth);
private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY());
private addLiveTextBox = (newBox: Doc) => {
@@ -65,13 +65,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
return true;
}
private selectDocuments = (docs: Doc[]) => {
- SelectionManager.DeselectAll;
+ SelectionManager.DeselectAll();
docs.map(doc => DocumentManager.Instance.getDocumentView(doc)).filter(dv => dv).map(dv =>
SelectionManager.SelectDoc(dv!, true));
}
public getActiveDocuments = () => {
const curPage = FieldValue(this.Document.curPage, -1);
- return this.children.filter(doc => {
+ return this.childDocs.filter(doc => {
var page = NumCast(doc.page, -1);
return page === curPage || page === -1;
});
@@ -89,7 +89,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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 => {
+ de.data.droppedDocuments.forEach(d => {
d.x = x + NumCast(d.x) - dropX;
d.y = y + NumCast(d.y) - dropY;
if (!NumCast(d.width)) {
@@ -111,11 +111,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerDown = (e: React.PointerEvent): void => {
- if ((CollectionFreeFormView.RIGHT_BTN_DRAG &&
- (((e.button === 2 && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) ||
- (e.button === 0 && e.altKey)) && this.props.active())) ||
- (!CollectionFreeFormView.RIGHT_BTN_DRAG &&
- ((e.button === 0 && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1)) && this.props.active()))) {
+ if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) {
document.removeEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointermove", this.onPointerMove);
@@ -133,20 +129,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble) {
- let x = NumCast(this.props.Document.panX);
- let y = NumCast(this.props.Document.panY);
- let docs = this.children || [];
+ let x = this.Document.panX || 0;
+ let y = this.Document.panY || 0;
+ let docs = this.childDocs || [];
let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
if (!this.isAnnotationOverlay) {
let minx = docs.length ? NumCast(docs[0].x) : 0;
- let maxx = docs.length ? NumCast(docs[0].width) + minx : minx;
+ let maxx = docs.length ? NumCast(docs[0].width) / NumCast(docs[0].zoomBasis) + minx : minx;
let miny = docs.length ? NumCast(docs[0].y) : 0;
- let maxy = docs.length ? NumCast(docs[0].height) + miny : miny;
+ let maxy = docs.length ? NumCast(docs[0].height) / NumCast(docs[0].zoomBasis) + miny : miny;
let ranges = docs.filter(doc => doc).reduce((range, doc) => {
let x = NumCast(doc.x);
- let xe = x + NumCast(doc.width);
+ let xe = x + NumCast(doc.width) / NumCast(doc.zoomBasis);
let y = NumCast(doc.y);
- let ye = y + NumCast(doc.height);
+ let ye = y + NumCast(doc.height) / NumCast(doc.zoomBasis);
return [[range[0][0] > x ? x : range[0][0], range[0][1] < xe ? xe : range[0][1]],
[range[1][0] > y ? y : range[1][0], range[1][1] < ye ? ye : range[1][1]]];
}, [[minx, maxx], [miny, maxy]]);
@@ -179,7 +175,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
// if (!this.props.active()) {
// return;
// }
- let childSelected = this.children.some(doc => {
+ let childSelected = this.childDocs.some(doc => {
var dv = DocumentManager.Instance.getDocumentView(doc);
return dv && SelectionManager.IsSelected(dv) ? true : false;
});
@@ -210,7 +206,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let [x, y] = this.getTransform().transformPoint(e.clientX, e.clientY);
let localTransform = this.getLocalTransform().inverse().scaleAbout(deltaScale, x, y);
- let safeScale = Math.abs(localTransform.Scale);
+ let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40);
this.props.Document.scale = Math.abs(safeScale);
this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale);
e.stopPropagation();
@@ -219,6 +215,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
setPan(panX: number, panY: number) {
+ this.panDisposer && clearTimeout(this.panDisposer);
+ this.props.Document.panTransformType = "None";
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));
@@ -236,7 +234,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
bringToFront = (doc: Doc) => {
- const docs = this.children;
+ const docs = this.childDocs;
docs.slice().sort((doc1, doc2) => {
if (doc1 === doc) return 1;
if (doc2 === doc) return -1;
@@ -245,16 +243,37 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
doc.zIndex = docs.length + 1;
}
+ panDisposer?: NodeJS.Timeout;
focusDocument = (doc: Doc) => {
+ const panX = this.Document.panX;
+ const panY = this.Document.panY;
+ const id = this.Document[Id];
+ const state = HistoryUtil.getState();
+ // TODO This technically isn't correct if type !== "doc", as
+ // currently nothing is done, but we should probably push a new state
+ if (state.type === "doc" && panX !== undefined && panY !== undefined) {
+ const init = state.initializers[id];
+ if (!init) {
+ state.initializers[id] = {
+ panX, panY
+ };
+ HistoryUtil.pushState(state);
+ } else if (init.panX !== panX || init.panY !== panY) {
+ init.panX = panX;
+ init.panY = panY;
+ HistoryUtil.pushState(state);
+ }
+ }
SelectionManager.DeselectAll();
+ const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
+ const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
+ const newState = HistoryUtil.getState();
+ newState.initializers[id] = { panX: newPanX, panY: newPanY };
+ HistoryUtil.pushState(newState);
+ this.setPan(newPanX, newPanY);
this.props.Document.panTransformType = "Ease";
- this.setPan(
- NumCast(doc.x) + NumCast(doc.width) / 2,
- NumCast(doc.y) + NumCast(doc.height) / 2);
this.props.focus(this.props.Document);
- if (this.props.Document.panTransformType === "Ease") {
- setTimeout(() => this.props.Document.panTransformType = "None", 2000); // wait 3 seconds, then reset to false
- }
+ this.panDisposer = setTimeout(() => this.props.Document.panTransformType = "None", 2000); // wait 3 seconds, then reset to false
}
@@ -276,17 +295,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
parentActive: this.props.active,
whenActiveChanged: this.props.whenActiveChanged,
bringToFront: this.bringToFront,
+ addDocTab: this.props.addDocTab,
};
}
@computed.struct
get views() {
let curPage = FieldValue(this.Document.curPage, -1);
- let docviews = this.children.reduce((prev, doc) => {
+ let docviews = this.childDocs.reduce((prev, doc) => {
if (!(doc instanceof Doc)) return prev;
var page = NumCast(doc.page, -1);
if (page === curPage || page === -1) {
- let minim = Cast(doc.isMinimized, "boolean");
+ let minim = BoolCast(doc.isMinimized, false);
if (minim === undefined || !minim) {
prev.push(<CollectionFreeFormDocumentView key={doc[Id]} {...this.getDocumentViewProps(doc)} />);
}
@@ -350,7 +370,7 @@ class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps
isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
}
render() {
- return this.backgroundView;
+ return this.props.Document.backgroundLayout ? this.backgroundView : (null);
}
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 080c484f4..4587c2227 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -17,6 +17,9 @@ import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { List } from "../../../../new_fields/List";
import { ImageField } from "../../../../new_fields/URLField";
import { Template, Templates } from "../../Templates";
+import { Gateway } from "../../../northstar/manager/Gateway";
+import { DocServer } from "../../../DocServer";
+import { Id } from "../../../../new_fields/RefField";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -60,7 +63,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
e.preventDefault();
(async () => {
let text: string = await navigator.clipboard.readText();
- let ns = text.split("\n").filter(t => t.trim() != "\r" && t.trim() != "");
+ let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
for (let i = 0; i < ns.length - 1; i++) {
while (!(ns[i].trim() === "" || ns[i].endsWith("-\r") || ns[i].endsWith("-") ||
ns[i].endsWith(";\r") || ns[i].endsWith(";") ||
@@ -77,9 +80,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let newBox = Docs.TextDocument({ width: 200, height: 35, x: x + indent / 3 * 10, y: y, documentText: "@@@" + line, title: line });
this.props.addDocument(newBox, false);
y += 40 * this.props.getTransform().Scale;
- })
+ });
})();
- } else if (e.key === "t" && e.ctrlKey) {
+ } else if (e.key === "b" && e.ctrlKey) {
//heuristically converts pasted text into a table.
// assumes each entry is separated by a tab
// skips all rows until it gets to a row with more than one entry
@@ -90,9 +93,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
e.preventDefault();
(async () => {
let text: string = await navigator.clipboard.readText();
- let ns = text.split("\n").filter(t => t.trim() != "\r" && t.trim() != "");
- while (ns.length > 0 && ns[0].split("\t").length < 2)
+ let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
+ while (ns.length > 0 && ns[0].split("\t").length < 2) {
ns.splice(0, 1);
+ }
if (ns.length > 0) {
let columns = ns[0].split("\t");
let docList: Doc[] = [];
@@ -104,18 +108,15 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
continue;
}
let doc = new Doc();
- columns.forEach((col, i) => {
- console.log(values[i] + " " + Number(values[i]).toString());
- doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined);
- });
+ columns.forEach((col, i) => doc[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined));
if (groupAttr) {
- doc["_group"] = groupAttr;
+ doc._group = groupAttr;
}
doc.title = i.toString();
docList.push(doc);
}
- let newCol = Docs.SchemaDocument(docList, { x: x, y: y, title: "-dropped table-", width: 300, height: 100 });
- newCol.proto!.schemaColumns = new List<string>([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)]);
+ let newCol = Docs.SchemaDocument([...(groupAttr ? ["_group"] : []), ...columns.filter(c => c)], docList, { x: x, y: y, title: "droppedTable", width: 300, height: 100 });
+
this.props.addDocument(newCol, false);
}
})();
@@ -131,17 +132,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this._downY = this._lastY = e.pageY;
this._commandExecuted = false;
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())) {
+ if (e.button === 2 || (e.button === 0 && e.altKey)) {
+ if (!this.props.container.props.active()) this.props.selectDocuments([this.props.container.props.Document]);
document.addEventListener("pointermove", this.onPointerMove, true);
document.addEventListener("pointerup", this.onPointerUp, true);
document.addEventListener("keydown", this.marqueeCommand, true);
- // bcz: do we need this? it kills the context menu on the main collection
+ if (e.altKey) {
+ e.stopPropagation();
+ e.preventDefault();
+ }
+ // bcz: do we need this? it kills the context menu on the main collection if !altKey
// e.stopPropagation();
}
- if (e.altKey) {
- e.preventDefault();
- }
}
@action
@@ -207,11 +209,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@undoBatch
@action
marqueeCommand = async (e: KeyboardEvent) => {
- if (this._commandExecuted) {
+ if (this._commandExecuted || (e as any).propagationIsStopped) {
return;
}
if (e.key === "Backspace" || e.key === "Delete" || e.key === "d") {
this._commandExecuted = true;
+ e.stopPropagation();
+ (e as any).propagationIsStopped = true;
this.marqueeSelect().map(d => this.props.removeDocument(d));
let ink = Cast(this.props.container.props.Document.ink, InkField);
if (ink) {
@@ -221,26 +225,21 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this.cleanupInteractions(false);
e.stopPropagation();
}
- if (e.key === "c" || e.key === "r" || e.key === "s" || e.key === "e" || e.key === "p") {
+ if (e.key === "c" || e.key === "s" || e.key === "e" || e.key === "p") {
this._commandExecuted = true;
e.stopPropagation();
+ (e as any).propagationIsStopped = true;
let bounds = this.Bounds;
- let selected = this.marqueeSelect().map(d => {
- if (e.key === "s") {
- let dCopy = Doc.MakeCopy(d);
- dCopy.x = NumCast(d.x) - bounds.left - bounds.width / 2;
- dCopy.y = NumCast(d.y) - bounds.top - bounds.height / 2;
- dCopy.page = -1;
- return dCopy;
- }
- else if (e.key !== "r") {
+ let selected = this.marqueeSelect();
+ if (e.key === "c") {
+ selected.map(d => {
this.props.removeDocument(d);
d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
d.page = -1;
- }
- return d;
- });
+ return d;
+ });
+ }
let ink = Cast(this.props.container.props.Document.ink, InkField);
let inkData = ink ? ink.inkData : undefined;
let zoomBasis = NumCast(this.props.container.props.Document.scale, 1);
@@ -250,39 +249,36 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
panX: 0,
panY: 0,
borderRounding: e.key === "e" ? -1 : undefined,
+ backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
scale: zoomBasis,
width: bounds.width * zoomBasis,
height: bounds.height * zoomBasis,
ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined,
- title: "a nested collection",
+ title: e.key === "s" ? "-summary-" : e.key === "p" ? "-summary-" : "a nested collection",
});
-
this.marqueeInkDelete(inkData);
- // SelectionManager.DeselectAll();
- if (e.key === "s" || e.key === "r" || e.key === "p") {
- e.preventDefault();
- let scrpt = this.props.getTransform().inverse().transformPoint(bounds.left, bounds.top);
- let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
- let dataUrl = await htmlToImage.toPng(this._mainCont.current!, { width: bounds.width, height: bounds.height, quality: 1 });
- summary.proto!.thumbnail = new ImageField(new URL(dataUrl));
+ if (e.key === "s" || e.key === "p") {
- summary.proto!.templates = new List<string>([Templates.ImageOverlay(Math.min(50, bounds.width), bounds.height * Math.min(50, bounds.width) / bounds.width, "thumbnail")]);
- if (e.key === "s" || e.key === "p") {
- summary.proto!.maximizeOnRight = true;
+ htmlToImage.toPng(this._mainCont.current!, { width: bounds.width * zoomBasis, height: bounds.height * zoomBasis, quality: 1 }).then((dataUrl) => {
+ selected.map(d => {
+ this.props.removeDocument(d);
+ d.x = NumCast(d.x) - bounds.left - bounds.width / 2;
+ d.y = NumCast(d.y) - bounds.top - bounds.height / 2;
+ d.page = -1;
+ return d;
+ });
+ let summary = Docs.TextDocument({ x: bounds.left, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
+ summary.proto!.thumbnail = new ImageField(new URL(dataUrl));
+ summary.proto!.templates = new List<string>([Templates.ImageOverlay(Math.min(50, bounds.width), bounds.height * Math.min(50, bounds.width) / bounds.width, "thumbnail")]);
newCollection.proto!.summaryDoc = summary;
selected = [newCollection];
- }
- summary.proto!.summarizedDocs = new List<Doc>(selected);
- //summary.proto!.isButton = true;
- selected.map(summarizedDoc => {
- let maxx = NumCast(summarizedDoc.x, undefined);
- let maxy = NumCast(summarizedDoc.y, undefined);
- let maxw = NumCast(summarizedDoc.width, undefined);
- let maxh = NumCast(summarizedDoc.height, undefined);
- summarizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), 0])
+ newCollection.x = bounds.left + bounds.width;
+ //this.props.addDocument(newCollection, false);
+ summary.proto!.summarizedDocs = new List<Doc>(selected);
+ summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight"
+ this.props.addLiveTextDocument(summary);
});
- this.props.addLiveTextDocument(summary);
}
else {
this.props.addDocument(newCollection, false);
@@ -290,20 +286,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this.props.selectDocuments([newCollection]);
}
this.cleanupInteractions(false);
- } else
- if (e.key === "s") {
- // this._commandExecuted = true;
- // e.stopPropagation();
- // e.preventDefault();
- // let bounds = this.Bounds;
- // let selected = this.marqueeSelect();
- // SelectionManager.DeselectAll();
- // let summary = Docs.TextDocument({ x: bounds.left + bounds.width + 25, y: bounds.top, width: 300, height: 100, backgroundColor: "yellow", title: "-summary-" });
- // this.props.addLiveTextDocument(summary);
- // selected.forEach(select => Doc.MakeLink(summary.proto!, select.proto!));
-
- // this.cleanupInteractions(false);
- }
+ }
}
@action
marqueeInkSelect(ink: Map<any, any>) {
diff --git a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
index 817a23ce8..fa44ec9f3 100644
--- a/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
+++ b/src/client/views/nodes/CollectionFreeFormDocumentView.tsx
@@ -1,25 +1,26 @@
-import { computed, trace, action, reaction, IReactionDisposer } from "mobx";
+import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
+import { Doc, DocListCast, DocListCastAsync } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { createSchema, listSpec, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../new_fields/Types";
+import { OmitKeys, Utils } from "../../../Utils";
+import { DocumentManager } from "../../util/DocumentManager";
+import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
+import { UndoManager } from "../../util/UndoManager";
+import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { DocComponent } from "../DocComponent";
import { DocumentView, DocumentViewProps, positionSchema } from "./DocumentView";
import "./DocumentView.scss";
import React = require("react");
-import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface, listSpec } from "../../../new_fields/Schema";
-import { FieldValue, Cast, NumCast, BoolCast, StrCast } from "../../../new_fields/Types";
-import { OmitKeys, Utils } from "../../../Utils";
-import { SelectionManager } from "../../util/SelectionManager";
-import { Doc, DocListCastAsync, DocListCast, } from "../../../new_fields/Doc";
-import { List } from "../../../new_fields/List";
-import { CollectionDockingView } from "../collections/CollectionDockingView";
-import { UndoManager } from "../../util/UndoManager";
export interface CollectionFreeFormDocumentViewProps extends DocumentViewProps {
}
const schema = createSchema({
zoomBasis: "number",
- zIndex: "number"
+ zIndex: "number",
});
//TODO Types: The import order is wrong, so positionSchema is undefined
@@ -31,6 +32,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
private _mainCont = React.createRef<HTMLDivElement>();
private _downX: number = 0;
private _downY: number = 0;
+ private _doubleTap = false;
+ private _lastTap: number = 0;
_bringToFrontDisposer?: IReactionDisposer;
@computed get transform() {
@@ -42,9 +45,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
@computed get zoom(): number { return 1 / FieldValue(this.Document.zoomBasis, 1); }
@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); }
+ @computed get width(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.width, 0); }
+ @computed get height(): number { return BoolCast(this.props.Document.willMaximize) ? 0 : FieldValue(this.Document.height, 0); }
set width(w: number) {
this.Document.width = w;
@@ -58,10 +60,6 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
this.Document.width = this.nativeWidth / this.nativeHeight * h;
}
}
- set zIndex(h: number) {
- this.Document.zIndex = h;
- }
-
contentScaling = () => this.nativeWidth > 0 ? this.width / this.nativeWidth : 1;
panelWidth = () => this.props.PanelWidth();
panelHeight = () => this.props.PanelHeight();
@@ -86,7 +84,8 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
this.props.bringToFront(this.props.Document);
if (values instanceof List) {
let scrpt = this.props.ScreenToLocalTransform().transformPoint(values[0], values[1]);
- this.animateBetweenIcon(true, scrpt, [values[2], values[3]], values[4], values[5], values[6], this.props.Document, values[7] ? true : false);
+ this.animateBetweenIcon(true, scrpt, [this.Document.x || 0, this.Document.y || 0],
+ this.Document.width || 0, this.Document.height || 0, values[2], values[3] ? true : false);
}
}, { fireImmediately: true });
}
@@ -95,32 +94,33 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
if (this._bringToFrontDisposer) this._bringToFrontDisposer();
}
- animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, target: Doc, maximizing: boolean) {
- if (first) {
- if (maximizing) target.width = target.height = 1;
- }
+ animateBetweenIcon(first: boolean, icon: number[], targ: number[], width: number, height: number, stime: number, 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];
+ this.props.Document.width = maximizing ? 25 + (width - 25) * progress : width + (25 - width) * progress;
+ this.props.Document.height = maximizing ? 25 + (height - 25) * progress : height + (25 - height) * progress;
+ this.props.Document.x = pval[0];
+ this.props.Document.y = pval[1];
+ if (first) {
+ this.props.Document.proto!.willMaximize = false;
+ }
if (now < stime + 200) {
- this.animateBetweenIcon(false, icon, targ, width, height, stime, target, maximizing);
+ this.animateBetweenIcon(false, icon, targ, width, height, stime, maximizing);
}
else {
if (!maximizing) {
- target.isMinimized = true;
- target.x = targ[0];
- target.y = targ[1];
- target.width = width;
- target.height = height;
+ this.props.Document.proto!.isMinimized = true;
+ this.props.Document.x = targ[0];
+ this.props.Document.y = targ[1];
+ this.props.Document.width = width;
+ this.props.Document.height = height;
}
- target.isIconAnimating = undefined;
+ this.props.Document.proto!.isIconAnimating = undefined;
}
},
2);
@@ -139,23 +139,19 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
if (!CollectionFreeFormDocumentView._undoBatch) {
CollectionFreeFormDocumentView._undoBatch = UndoManager.StartBatch("iconAnimating");
}
- maximizedDocs.forEach(maximizedDoc => {
+ maximizedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
let iconAnimating = Cast(maximizedDoc.isIconAnimating, List);
- if (!iconAnimating || (Date.now() - iconAnimating[6] > 1000)) {
+ if (!iconAnimating || (Date.now() - iconAnimating[2] > 1000)) {
if (isMinimized === undefined) {
isMinimized = BoolCast(maximizedDoc.isMinimized, false);
}
- let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) * this.getTransform().Scale * this.contentScaling() / 2;
- let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) * this.getTransform().Scale * this.contentScaling() / 2;
- 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) {
+ let minx = NumCast(minimizedTarget.x, undefined) + NumCast(minimizedTarget.width, undefined) / 2;
+ let miny = NumCast(minimizedTarget.y, undefined) + NumCast(minimizedTarget.height, undefined) / 2;
+ if (minx !== undefined && miny !== undefined) {
let scrpt = this.props.ScreenToLocalTransform().inverse().transformPoint(minx, miny);
- if (isMinimized) maximizedDoc.isMinimized = false;
- maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], maxx, maxy, maxw, maxh, Date.now(), isMinimized ? 1 : 0])
+ maximizedDoc.willMaximize = isMinimized;
+ maximizedDoc.isMinimized = false;
+ maximizedDoc.isIconAnimating = new List<number>([scrpt[0], scrpt[1], Date.now(), isMinimized ? 1 : 0]);
}
}
});
@@ -169,36 +165,81 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
onPointerDown = (e: React.PointerEvent): void => {
this._downX = e.clientX;
this._downY = e.clientY;
- // e.stopPropagation();
+ this._doubleTap = false;
+ if (e.button === 0 && e.altKey) {
+ e.stopPropagation(); // prevents panning from happening on collection if shift is pressed after a document drag has started
+ } // allow pointer down to go through otherwise so that marquees can be drawn starting over a document
+ if (Date.now() - this._lastTap < 300) {
+ if (e.buttons === 1) {
+ document.removeEventListener("pointerup", this.onPointerUp);
+ document.addEventListener("pointerup", this.onPointerUp);
+ }
+ } else {
+ this._lastTap = Date.now();
+ }
+ }
+ onPointerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointerup", this.onPointerUp);
+ if (Math.abs(e.clientX - this._downX) < 2 && Math.abs(e.clientY - this._downY) < 2) {
+ this._doubleTap = true;
+ }
}
onClick = async (e: React.MouseEvent) => {
e.stopPropagation();
+ if (this._doubleTap) {
+ this.props.addDocTab(this.props.Document, "inTab");
+ SelectionManager.DeselectAll();
+ }
let altKey = e.altKey;
if (Math.abs(e.clientX - this._downX) < Utils.DRAG_THRESHOLD &&
Math.abs(e.clientY - this._downY) < Utils.DRAG_THRESHOLD) {
let isExpander = (e.target as any).id === "isExpander";
if (BoolCast(this.props.Document.isButton, false) || isExpander) {
+ SelectionManager.DeselectAll();
let subBulletDocs = await DocListCastAsync(this.props.Document.subBulletDocs);
let maximizedDocs = await DocListCastAsync(this.props.Document.maximizedDocs);
let summarizedDocs = await DocListCastAsync(this.props.Document.summarizedDocs);
- let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : [])];
- if (expandedDocs) { // bcz: need a better way to associate behaviors with click events on widget-documents
- if ((altKey && !this.props.Document.maximizeOnRight) || (!altKey && this.props.Document.maximizeOnRight)) {
+ let linkedToDocs = await DocListCastAsync(this.props.Document.linkedToDocs, []);
+ let linkedFromDocs = await DocListCastAsync(this.props.Document.linkedFromDocs, []);
+ let expandedDocs: Doc[] = [];
+ expandedDocs = subBulletDocs ? [...subBulletDocs, ...expandedDocs] : expandedDocs;
+ expandedDocs = maximizedDocs ? [...maximizedDocs, ...expandedDocs] : expandedDocs;
+ expandedDocs = summarizedDocs ? [...summarizedDocs, ...expandedDocs] : expandedDocs;
+ // let expandedDocs = [...(subBulletDocs ? subBulletDocs : []), ...(maximizedDocs ? maximizedDocs : []), ...(summarizedDocs ? summarizedDocs : []),];
+ if (expandedDocs.length) { // bcz: need a better way to associate behaviors with click events on widget-documents
+ let expandedProtoDocs = expandedDocs.map(doc => Doc.GetProto(doc))
+ let maxLocation = StrCast(this.props.Document.maximizeLocation, "inPlace");
+ let getDispDoc = (target: Doc) => Object.getOwnPropertyNames(target).indexOf("isPrototype") === -1 ? target : Doc.MakeDelegate(target);
+ if (altKey) {
+ maxLocation = this.props.Document.maximizeLocation = (maxLocation === "inPlace" || !maxLocation ? "inTab" : "inPlace");
+ if (!maxLocation || maxLocation === "inPlace") {
+ let hadView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
+ let wasMinimized = !hadView && expandedDocs.reduce((min, d) => !min && !BoolCast(d.IsMinimized, false), false);
+ expandedDocs.forEach(maxDoc => Doc.GetProto(maxDoc).isMinimized = false);
+ let hasView = expandedDocs.length === 1 && DocumentManager.Instance.getDocumentView(expandedProtoDocs[0], this.props.ContainingCollectionView);
+ if (!hasView) {
+ this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(getDispDoc(maxDoc), false));
+ }
+ expandedProtoDocs.forEach(maxDoc => maxDoc.isMinimized = wasMinimized);
+ }
+ }
+ if (maxLocation && maxLocation !== "inPlace") {
let dataDocs = DocListCast(CollectionDockingView.Instance.props.Document.data);
if (dataDocs) {
- SelectionManager.DeselectAll();
- expandedDocs.forEach(maxDoc => {
- maxDoc.isMinimized = false;
- if (!dataDocs || dataDocs.indexOf(maxDoc) == -1) {
- CollectionDockingView.Instance.AddRightSplit(maxDoc);
- } else {
- CollectionDockingView.Instance.CloseRightSplit(maxDoc);
- }
- });
+ expandedDocs.forEach(maxDoc =>
+ (!CollectionDockingView.Instance.CloseRightSplit(Doc.GetProto(maxDoc)) &&
+ this.props.addDocTab(getDispDoc(maxDoc), maxLocation)));
}
} else {
- this.props.addDocument && expandedDocs.forEach(async maxDoc => this.props.addDocument!(await maxDoc, false));
- this.toggleIcon(expandedDocs);
+ this.toggleIcon(expandedProtoDocs);
+ }
+ }
+ else if (linkedToDocs.length || linkedFromDocs.length) {
+ let linkedFwdDocs = [
+ linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : expandedDocs[0],
+ linkedFromDocs.length ? linkedFromDocs[0].linkedFrom as Doc : linkedToDocs.length ? linkedToDocs[0].linkedTo as Doc : expandedDocs[0]];
+ if (linkedFwdDocs) {
+ DocumentManager.Instance.jumpToDocument(linkedFwdDocs[altKey ? 1 : 0], altKey);
}
}
}
@@ -236,9 +277,11 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave} onPointerOver={this.onPointerEnter}
onClick={this.onClick}
style={{
- outlineColor: "black",
+ outlineColor: "maroon",
outlineStyle: "dashed",
- outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ? `${0.5 / this.contentScaling()}px` : "0px",
+ outlineWidth: BoolCast(this.props.Document.libraryBrush, false) ||
+ BoolCast(this.props.Document.protoBrush, false) ?
+ `${1 * this.getTransform().Scale}px` : "0px",
opacity: zoomFade,
borderRadius: `${this.borderRounding()}px`,
transformOrigin: "left top",
@@ -247,7 +290,7 @@ export class CollectionFreeFormDocumentView extends DocComponent<CollectionFreeF
width: this.width,
height: this.height,
position: "absolute",
- zIndex: this.zIndex,
+ zIndex: this.Document.zIndex || 0,
backgroundColor: "transparent"
}} >
{this.docView}
diff --git a/src/client/views/nodes/DocumentContentsView.tsx b/src/client/views/nodes/DocumentContentsView.tsx
index 11df9162a..8e08385a4 100644
--- a/src/client/views/nodes/DocumentContentsView.tsx
+++ b/src/client/views/nodes/DocumentContentsView.tsx
@@ -21,7 +21,7 @@ import { HistogramBox } from "../../northstar/dash-nodes/HistogramBox";
import React = require("react");
import { FieldViewProps } from "./FieldView";
import { Without, OmitKeys } from "../../../Utils";
-import { Cast, StrCast } from "../../../new_fields/Types";
+import { Cast, StrCast, NumCast } from "../../../new_fields/Types";
import { List } from "../../../new_fields/List";
import { PDFBox2 } from "../pdf/PDFBox2";
const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
@@ -44,9 +44,20 @@ const ObserverJsxParser: typeof JsxParser = ObserverJsxParser1 as any;
export class DocumentContentsView extends React.Component<DocumentViewProps & {
isSelected: () => boolean,
select: (ctrl: boolean) => void,
- layoutKey: string
+ layoutKey: string,
}> {
- @computed get layout(): string { return Cast(this.props.Document[this.props.layoutKey], "string", this.props.layoutKey === "layout" ? "<p>Error loading layout data</p>" : ""); }
+ @computed get layout(): string {
+ const layout = Cast(this.props.Document[this.props.layoutKey], "string");
+ if (layout === undefined) {
+ return this.props.Document.data ?
+ "<FieldView {...props} fieldKey='data' />" :
+ KeyValueBox.LayoutString(this.props.Document.proto ? "proto" : "");
+ } else if (typeof layout === "string") {
+ return layout;
+ } else {
+ return "<p>Loading layout</p>";
+ }
+ }
CreateBindings(): JsxBindings {
return { props: OmitKeys(this.props, ['parentActive'], (obj: any) => obj.active = this.props.parentActive).omit };
@@ -60,7 +71,7 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
return new List<string>();
}
@computed get finalLayout() {
- const baseLayout = this.layout;
+ const baseLayout = this.props.layoutKey === "overlayLayout" ? "<div/>" : this.layout;
let base = baseLayout;
let layout = baseLayout;
@@ -72,9 +83,13 @@ export class DocumentContentsView extends React.Component<DocumentViewProps & {
(this.props.layoutKey === "layout" && StrCast(this.props.Document.layout).indexOf("CollectionView") === -1)) {
this.templates.forEach(template => {
let self = this;
+ // this scales constants in the markup by the scaling applied to the document, but caps the constants to be smaller
+ // than the width/height of the containing document
function convertConstantsToNative(match: string, offset: number, x: string) {
let px = Number(match.replace("px", ""));
- return `${px * self.props.ScreenToLocalTransform().Scale}px`;
+ return `${Math.min(NumCast(self.props.Document.height, 0),
+ Math.min(NumCast(self.props.Document.width, 0),
+ px * self.props.ScreenToLocalTransform().Scale))}px`;
}
let nativizedTemplate = template.replace(/([0-9]+)px/g, convertConstantsToNative);
layout = nativizedTemplate.replace("{layout}", base);
diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx
index 90f67db7c..38f3db19f 100644
--- a/src/client/views/nodes/DocumentView.tsx
+++ b/src/client/views/nodes/DocumentView.tsx
@@ -1,31 +1,44 @@
-import { action, computed, runInAction, reaction, IReactionDisposer } from "mobx";
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faAlignCenter, faCaretSquareRight, faCompressArrowsAlt, faExpandArrowsAlt, faLayerGroup, faSquare, faTrash } from '@fortawesome/free-solid-svg-icons';
+import { action, computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
+import { Doc, DocListCast, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { Copy, ObjectField } from "../../../new_fields/ObjectField";
+import { Id } from "../../../new_fields/RefField";
+import { createSchema, makeInterface } from "../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, StrCast } from "../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
import { emptyFunction, Utils } from "../../../Utils";
-import { Docs } from "../../documents/Documents";
+import { DocServer } from "../../DocServer";
+import { Docs, DocUtils } from "../../documents/Documents";
import { DocumentManager } from "../../util/DocumentManager";
import { DragManager, dropActionType } from "../../util/DragManager";
+import { SearchUtil } from "../../util/SearchUtil";
import { SelectionManager } from "../../util/SelectionManager";
import { Transform } from "../../util/Transform";
-import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { undoBatch } from "../../util/UndoManager";
import { CollectionDockingView } from "../collections/CollectionDockingView";
+import { CollectionFreeFormView } from "../collections/collectionFreeForm/CollectionFreeFormView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
import { CollectionView } from "../collections/CollectionView";
import { ContextMenu } from "../ContextMenu";
+import { DocComponent } from "../DocComponent";
+import { PresentationView } from "../PresentationView";
import { Template } from "./../Templates";
import { DocumentContentsView } from "./DocumentContentsView";
import "./DocumentView.scss";
import React = require("react");
-import { Opt, Doc, WidthSym, HeightSym, DocListCastAsync, DocListCast } from "../../../new_fields/Doc";
-import { DocComponent } from "../DocComponent";
-import { createSchema, makeInterface } from "../../../new_fields/Schema";
-import { FieldValue, StrCast, BoolCast, Cast } 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 { DocServer } from "../../DocServer";
-import { Id } from "../../../new_fields/RefField";
-import { PresentationView } from "../PresentationView";
+const JsxParser = require('react-jsx-parser').default; //TODO Why does this need to be imported like this?
+
+library.add(faTrash);
+library.add(faExpandArrowsAlt);
+library.add(faCompressArrowsAlt);
+library.add(faLayerGroup);
+library.add(faAlignCenter);
+library.add(faCaretSquareRight);
+library.add(faSquare);
const linkSchema = createSchema({
title: "string",
@@ -55,6 +68,7 @@ export interface DocumentViewProps {
whenActiveChanged: (isActive: boolean) => void;
toggleMinimized: () => void;
bringToFront: (doc: Doc) => void;
+ addDocTab: (doc: Doc, where: string) => void;
}
const schema = createSchema({
@@ -111,12 +125,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
this._reactionDisposer = reaction(() => [this.props.Document.maximizedDocs, this.props.Document.summaryDoc, this.props.Document.summaryDoc instanceof Doc ? this.props.Document.summaryDoc.title : ""],
() => {
let maxDoc = DocListCast(this.props.Document.maximizedDocs);
- if (maxDoc && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
- this.props.Document.title = (maxDoc && maxDoc.length === 1 ? maxDoc[0].title + ".icon" : "");
+ if (maxDoc.length === 1 && StrCast(this.props.Document.title).startsWith("-") && StrCast(this.props.Document.layout).indexOf("IconBox") !== -1) {
+ this.props.Document.proto!.title = "-" + maxDoc[0].title + ".icon";
}
let sumDoc = Cast(this.props.Document.summaryDoc, Doc);
- if (sumDoc instanceof Doc) {
- this.props.Document.title = sumDoc.title + ".expanded";
+ if (sumDoc instanceof Doc && StrCast(this.props.Document.title).startsWith("-")) {
+ this.props.Document.proto!.title = "-" + sumDoc.title + ".expanded";
}
}, { fireImmediately: true });
DocumentManager.Instance.DocumentViews.push(this);
@@ -173,18 +187,12 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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;
- }
this._hitExpander = DocListCast(this.props.Document.subBulletDocs).length > 0;
if (e.shiftKey && e.buttons === 1) {
- if (this.props.isTopMost) {
- this.startDragging(e.pageX, e.pageY, e.altKey || e.ctrlKey ? "alias" : undefined, this._hitExpander);
- } else if (this.props.Document) {
- CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
- }
+ CollectionDockingView.Instance.StartOtherDrag([Doc.MakeAlias(this.props.Document)], e);
e.stopPropagation();
} else if (this.active) {
+ //e.stopPropagation(); // bcz: doing this will block click events from CollectionFreeFormDocumentView which are needed for iconifying,etc
document.removeEventListener("pointermove", this.onPointerMove);
document.addEventListener("pointermove", this.onPointerMove);
document.removeEventListener("pointerup", this.onPointerUp);
@@ -196,7 +204,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
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)) {
+ if (!e.altKey && !this.topMost && e.buttons === 1) {
this.startDragging(this._downX, this._downY, e.ctrlKey || e.altKey ? "alias" : undefined, this._hitExpander);
}
}
@@ -220,12 +228,15 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let doc = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
doc.isButton = !BoolCast(doc.isButton, false);
if (doc.isButton && !doc.nativeWidth) {
- doc.nativeWidth = doc[WidthSym]();
- doc.nativeHeight = doc[HeightSym]();
+ doc.nativeWidth = this.props.Document[WidthSym]();
+ doc.nativeHeight = this.props.Document[HeightSym]();
+ } else {
+
+ doc.nativeWidth = doc.nativeHeight = undefined;
}
}
fullScreenClicked = (e: React.MouseEvent): void => {
- const doc = Doc.MakeDelegate(FieldValue(this.Document.proto));
+ const doc = Doc.MakeCopy(this.props.Document, false);
if (doc) {
CollectionDockingView.Instance.OpenFullScreen(doc);
}
@@ -240,10 +251,19 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
let sourceDoc = de.data.linkSourceDocument;
let destDoc = this.props.Document;
- const protoDest = destDoc.proto;
- const protoSrc = sourceDoc.proto;
- Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
- de.data.droppedDocuments.push(destDoc);
+ if (de.mods === "AltKey") {
+ const protoDest = destDoc.proto;
+ const protoSrc = sourceDoc.proto;
+ let src = protoSrc ? protoSrc : sourceDoc;
+ let dst = protoDest ? protoDest : destDoc;
+ dst.data = (src.data! as ObjectField)[Copy]();
+ dst.nativeWidth = src.nativeWidth;
+ dst.nativeHeight = src.nativeHeight;
+ }
+ else {
+ DocUtils.MakeLink(sourceDoc, destDoc);
+ de.data.droppedDocuments.push(destDoc);
+ }
e.stopPropagation();
}
}
@@ -287,16 +307,23 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
}
e.preventDefault();
- ContextMenu.Instance.addItem({ description: "Full Screen", event: this.fullScreenClicked });
- ContextMenu.Instance.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton });
- 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(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: "Pin to Presentation", event: () => PresentationView.Instance.PinDoc(this.props.Document) });
- ContextMenu.Instance.addItem({ description: "Delete", event: this.deleteClicked });
+ const cm = ContextMenu.Instance;
+ cm.addItem({ description: "Full Screen", event: this.fullScreenClicked, icon: "expand-arrows-alt" });
+ cm.addItem({ description: this.props.Document.isButton ? "Remove Button" : "Make Button", event: this.makeButton, icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Fields", event: this.fieldsClicked, icon: "layer-group" });
+ cm.addItem({ description: "Center", event: () => this.props.focus(this.props.Document), icon: "align-center" });
+ cm.addItem({ description: "Open Tab", event: () => this.props.addDocTab && this.props.addDocTab(this.props.Document, "inTab"), icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Open Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.Document), icon: "caret-square-right" });
+ cm.addItem({
+ description: "Find aliases", event: async () => {
+ const aliases = await SearchUtil.GetAliasesOfDocument(this.props.Document);
+ CollectionDockingView.Instance.AddRightSplit(Docs.SchemaDocument(["title"], aliases, {}));
+ }, icon: "expand-arrows-alt"
+ });
+ cm.addItem({ description: "Copy URL", event: () => Utils.CopyText(DocServer.prepend("/doc/" + this.props.Document[Id])), icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Copy ID", event: () => Utils.CopyText(this.props.Document[Id]), icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Pin to Pres", event: () => PresentationView.Instance.PinDoc(this.props.Document), icon: "expand-arrows-alt" });
+ cm.addItem({ description: "Delete", event: this.deleteClicked, icon: "trash" });
if (!this.topMost) {
// DocumentViews should stop propagation of this event
e.stopPropagation();
@@ -316,8 +343,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu
render() {
var scaling = this.props.ContentScaling();
- var nativeHeight = this.nativeHeight > 0 ? this.nativeHeight.toString() + "px" : "100%";
- var nativeWidth = this.nativeWidth > 0 ? this.nativeWidth.toString() + "px" : "100%";
+ var nativeHeight = this.nativeHeight > 0 ? `${this.nativeHeight}px` : (StrCast(this.props.Document.layout).indexOf("IconBox") === -1 ? "100%" : "auto");
+ var nativeWidth = this.nativeWidth > 0 ? `${this.nativeWidth}px` : "100%";
return (
<div className={`documentView-node${this.props.isTopMost ? "-topmost" : ""}`}
diff --git a/src/client/views/nodes/FieldView.tsx b/src/client/views/nodes/FieldView.tsx
index 8bdf34181..5c149af99 100644
--- a/src/client/views/nodes/FieldView.tsx
+++ b/src/client/views/nodes/FieldView.tsx
@@ -7,7 +7,7 @@ import { VideoBox } from "./VideoBox";
import { AudioBox } from "./AudioBox";
import { DocumentContentsView } from "./DocumentContentsView";
import { Transform } from "../../util/Transform";
-import { returnFalse, emptyFunction } from "../../../Utils";
+import { returnFalse, emptyFunction, returnOne } from "../../../Utils";
import { CollectionView } from "../collections/CollectionView";
import { CollectionPDFView } from "../collections/CollectionPDFView";
import { CollectionVideoView } from "../collections/CollectionVideoView";
@@ -18,6 +18,7 @@ import { ImageField, VideoField, AudioField } from "../../../new_fields/URLField
import { IconField } from "../../../new_fields/IconField";
import { RichTextField } from "../../../new_fields/RichTextField";
import { DateField } from "../../../new_fields/DateField";
+import { NumCast } from "../../../new_fields/Types";
//
@@ -34,6 +35,7 @@ export interface FieldViewProps {
isTopMost: boolean;
selectOnLoad: boolean;
addDocument?: (document: Doc, allowDuplicates?: boolean) => boolean;
+ addDocTab: (document: Doc, where: string) => boolean;
removeDocument?: (document: Doc) => boolean;
moveDocument?: (document: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean) => boolean;
ScreenToLocalTransform: () => Transform;
@@ -82,14 +84,16 @@ export class FieldView extends React.Component<FieldViewProps> {
return <p>{field.date.toLocaleString()}</p>;
}
else if (field instanceof Doc) {
+ let returnHundred = () => 100;
return (
<DocumentContentsView Document={field}
addDocument={undefined}
+ addDocTab={this.props.addDocTab}
removeDocument={undefined}
ScreenToLocalTransform={Transform.Identity}
- ContentScaling={() => 1}
- PanelWidth={() => 100}
- PanelHeight={() => 100}
+ ContentScaling={returnOne}
+ PanelWidth={returnHundred}
+ PanelHeight={returnHundred}
isTopMost={true} //TODO Why is this top most?
selectOnLoad={false}
focus={emptyFunction}
diff --git a/src/client/views/nodes/FormattedTextBox.tsx b/src/client/views/nodes/FormattedTextBox.tsx
index 7ebc31b0a..d15813f9a 100644
--- a/src/client/views/nodes/FormattedTextBox.tsx
+++ b/src/client/views/nodes/FormattedTextBox.tsx
@@ -1,3 +1,5 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faEdit, faSmile } from '@fortawesome/free-solid-svg-icons';
import { action, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import { baseKeymap } from "prosemirror-commands";
@@ -5,7 +7,7 @@ import { history } from "prosemirror-history";
import { keymap } from "prosemirror-keymap";
import { EditorState, Plugin, Transaction } from "prosemirror-state";
import { EditorView } from "prosemirror-view";
-import { Doc, Field, HeightSym, Opt, WidthSym } from "../../../new_fields/Doc";
+import { Doc, Field, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc";
import { RichTextField } from "../../../new_fields/RichTextField";
import { createSchema, makeInterface } from "../../../new_fields/Schema";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
@@ -26,6 +28,10 @@ import { InkingControl } from "../InkingControl";
import { FieldView, FieldViewProps } from "./FieldView";
import "./FormattedTextBox.scss";
import React = require("react");
+import { DocUtils } from '../../documents/Documents';
+
+library.add(faEdit);
+library.add(faSmile);
// FormattedTextBox: Displays an editable plain text node that maps to a specified Key of a Document
//
@@ -111,9 +117,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
let sourceDoc = de.data.linkSourceDocument;
let destDoc = this.props.Document;
- const protoDest = destDoc.proto;
- const protoSrc = sourceDoc.proto;
- Doc.MakeLink(protoSrc ? protoSrc : sourceDoc, protoDest ? protoDest : destDoc);
+ DocUtils.MakeLink(sourceDoc, destDoc);
de.data.droppedDocuments.push(destDoc);
e.stopPropagation();
}
@@ -184,7 +188,7 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
state: field && field.Data ? EditorState.fromJSON(config, JSON.parse(field.Data)) : EditorState.create(config),
dispatchTransaction: this.dispatchTransaction,
nodeViews: {
- image(node, view, getPos) { return new ImageResizeView(node, view, getPos) }
+ image(node, view, getPos) { return new ImageResizeView(node, view, getPos); }
}
});
let text = StrCast(this.props.Document.documentText);
@@ -229,12 +233,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
if (e.target && (e.target as any).href) {
let href = (e.target as any).href;
if (href.indexOf(DocServer.prepend("/doc/")) === 0) {
- let docid = href.replace(DocServer.prepend("/doc/"), "");
+ let docid = href.replace(DocServer.prepend("/doc/"), "").split("?")[0];
DocServer.GetRefField(docid).then(action((f: Opt<Field>) => {
if (f instanceof Doc) {
- if (DocumentManager.Instance.getDocumentView(f))
+ if (DocumentManager.Instance.getDocumentView(f)) {
DocumentManager.Instance.getDocumentView(f)!.props.focus(f);
- else CollectionDockingView.Instance.AddRightSplit(f);
+ } else {
+ CollectionDockingView.Instance.AddRightSplit(f);
+ }
}
}));
}
@@ -269,14 +275,14 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
//REPLACE THIS WITH CAPABILITIES SPECIFIC TO THIS TYPE OF NODE
- textCapability = (e: React.MouseEvent): void => {
+ freezeNativeDimensions = (e: React.MouseEvent): void => {
if (NumCast(this.props.Document.nativeWidth)) {
- this.props.Document.nativeWidth = undefined;
- this.props.Document.nativeHeight = undefined;
+ this.props.Document.proto!.nativeWidth = undefined;
+ this.props.Document.proto!.nativeHeight = undefined;
} else {
- this.props.Document.nativeWidth = this.props.Document[WidthSym]();
- this.props.Document.nativeHeight = this.props.Document[HeightSym]();
+ this.props.Document.proto!.nativeWidth = this.props.Document[WidthSym]();
+ this.props.Document.proto!.nativeHeight = this.props.Document[HeightSym]();
}
}
specificContextMenu = (e: React.MouseEvent): void => {
@@ -286,7 +292,8 @@ export class FormattedTextBox extends DocComponent<(FieldViewProps & FormattedTe
}
ContextMenu.Instance.addItem({
description: NumCast(this.props.Document.nativeWidth) ? "Unfreeze" : "Freeze",
- event: this.textCapability
+ event: this.freezeNativeDimensions,
+ icon: "edit"
});
}
diff --git a/src/client/views/nodes/IconBox.scss b/src/client/views/nodes/IconBox.scss
index 85bbdeb59..893dc2d36 100644
--- a/src/client/views/nodes/IconBox.scss
+++ b/src/client/views/nodes/IconBox.scss
@@ -1,20 +1,20 @@
@import "../globalCssVariables";
.iconBox-container {
- position: absolute;
+ position: inherit;
left:0;
top:0;
- height: 100%;
+ height: auto;
width: max-content;
// overflow: hidden;
pointer-events: all;
svg {
width: $MINIMIZED_ICON_SIZE !important;
- height: 100%;
+ height: auto;
background: white;
}
.iconBox-label {
- position: inherit;
+ position: absolute;
width:max-content;
font-size: 14px;
margin-top: 3px;
diff --git a/src/client/views/nodes/IconBox.tsx b/src/client/views/nodes/IconBox.tsx
index 071930633..b42eb44a5 100644
--- a/src/client/views/nodes/IconBox.tsx
+++ b/src/client/views/nodes/IconBox.tsx
@@ -7,7 +7,7 @@ import { observer } from "mobx-react";
import { FieldView, FieldViewProps } from './FieldView';
import "./IconBox.scss";
import { Cast, StrCast, BoolCast } from "../../../new_fields/Types";
-import { Doc } from "../../../new_fields/Doc";
+import { Doc, DocListCast } from "../../../new_fields/Doc";
import { IconField } from "../../../new_fields/IconField";
import { ContextMenu } from "../ContextMenu";
import Measure from "react-measure";
@@ -41,25 +41,40 @@ export class IconBox extends React.Component<FieldViewProps> {
setLabelField = (e: React.MouseEvent): void => {
this.props.Document.hideLabel = !BoolCast(this.props.Document.hideLabel);
}
+ setUseOwnTitleField = (e: React.MouseEvent): void => {
+ this.props.Document.useOwnTitle = !BoolCast(this.props.Document.useTargetTitle);
+ }
specificContextMenu = (e: React.MouseEvent): void => {
ContextMenu.Instance.addItem({
- description: BoolCast(this.props.Document.hideLabel) ? "show label" : "hide label",
+ description: BoolCast(this.props.Document.hideLabel) ? "Show label with icon" : "Remove label from icon",
event: this.setLabelField
});
+ let maxDocs = DocListCast(this.props.Document.maximizedDocs);
+ if (maxDocs.length === 1 && !BoolCast(this.props.Document.hideLabel)) {
+ ContextMenu.Instance.addItem({
+ description: BoolCast(this.props.Document.useOwnTitle) ? "Use target title for label" : "Use own title label",
+ event: this.setUseOwnTitleField
+ });
+ }
}
@observable _panelWidth: number = 0;
@observable _panelHeight: number = 0;
render() {
let labelField = StrCast(this.props.Document.labelField);
let hideLabel = BoolCast(this.props.Document.hideLabel);
- let maxDoc = Cast(this.props.Document.maximizedDocs, listSpec(Doc), []);
- let firstDoc = maxDoc && maxDoc.length > 0 && maxDoc[0] instanceof Doc ? maxDoc[0] as Doc : undefined;
- let label = !hideLabel && firstDoc && labelField ? firstDoc[labelField] : "";
+ let maxDocs = DocListCast(this.props.Document.maximizedDocs);
+ let firstDoc = maxDocs.length ? maxDocs[0] : undefined;
+ let label = hideLabel ? "" : (firstDoc && labelField && !BoolCast(this.props.Document.useOwnTitle, false) ? firstDoc[labelField] : this.props.Document.title);
return (
<div className="iconBox-container" onContextMenu={this.specificContextMenu}>
{this.minimizedIcon}
- <Measure offset onResize={(r) => runInAction(() => { if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) this.props.Document.nativeWidth = this.props.Document.width = (r.offset!.width + Number(MINIMIZED_ICON_SIZE)); })}>
+ <Measure offset onResize={(r) => runInAction(() => {
+ if (r.offset!.width || BoolCast(this.props.Document.hideLabel)) {
+ this.props.Document.nativeWidth = (r.offset!.width + Number(MINIMIZED_ICON_SIZE));
+ if (this.props.Document.height === Number(MINIMIZED_ICON_SIZE)) this.props.Document.width = this.props.Document.nativeWidth;
+ }
+ })}>
{({ measureRef }) =>
<span ref={measureRef} className="iconBox-label">{label}</span>
}
diff --git a/src/client/views/nodes/ImageBox.scss b/src/client/views/nodes/ImageBox.scss
index 2316a050e..f1b73a676 100644
--- a/src/client/views/nodes/ImageBox.scss
+++ b/src/client/views/nodes/ImageBox.scss
@@ -25,7 +25,11 @@
}
.imageBox-cont img {
- height: 100%;
+ height: auto;
+ width:100%;
+}
+.imageBox-cont-interactive img {
+ height: auto;
width:100%;
}
diff --git a/src/client/views/nodes/ImageBox.tsx b/src/client/views/nodes/ImageBox.tsx
index f9659a4b2..828ac9bc8 100644
--- a/src/client/views/nodes/ImageBox.tsx
+++ b/src/client/views/nodes/ImageBox.tsx
@@ -1,5 +1,4 @@
-
-import { action, observable, trace } from 'mobx';
+import { action, observable } from 'mobx';
import { observer } from "mobx-react";
import Lightbox from 'react-image-lightbox';
import 'react-image-lightbox/style.css'; // This only needs to be imported once in your app
@@ -42,8 +41,8 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
onLoad = (target: any) => {
var h = this._imgRef.current!.naturalHeight;
var w = this._imgRef.current!.naturalWidth;
- if (this._photoIndex === 0) {
- this.Document.nativeHeight = FieldValue(this.Document.nativeWidth, 0) * h / w;
+ if (this._photoIndex === 0 && (this.props as any).id !== "isExpander") {
+ Doc.SetOnPrototype(this.Document, "nativeHeight", FieldValue(this.Document.nativeWidth, 0) * h / w);
this.Document.height = FieldValue(this.Document.width, 0) * h / w;
}
}
@@ -134,7 +133,7 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
ContextMenu.Instance.addItem({
description: "Copy path", event: () => {
Utils.CopyText(url);
- }
+ }, icon: "expand-arrows-alt"
});
}
}
@@ -157,16 +156,17 @@ export class ImageBox extends DocComponent<FieldViewProps, ImageDocument>(ImageD
}
render() {
- trace();
let field = this.Document[this.props.fieldKey];
let paths: string[] = ["http://www.cs.brown.edu/~bcz/face.gif"];
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, (this.props.PanelWidth as any) as string ? Number((this.props.PanelWidth as any) as string) : 50);
let interactive = InkingControl.Instance.selectedTool ? "" : "-interactive";
- let id = this.props.id;
+ let id = (this.props as any).id; // bcz: used to set id = "isExpander" in templates.tsx
return (
- <div id={id} className={`imageBox-cont${interactive}`} onPointerDown={this.onPointerDown} onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
+ <div id={id} className={`imageBox-cont${interactive}`}
+ // onPointerDown={this.onPointerDown}
+ onDrop={this.onDrop} ref={this.createDropTarget} onContextMenu={this.specificContextMenu}>
<img id={id} src={paths[Math.min(paths.length, this._photoIndex)]}
style={{ objectFit: (this._photoIndex === 0 ? undefined : "contain") }}
width={nativeWidth}
diff --git a/src/client/views/nodes/KeyValueBox.tsx b/src/client/views/nodes/KeyValueBox.tsx
index 876a3c173..86437a6c1 100644
--- a/src/client/views/nodes/KeyValueBox.tsx
+++ b/src/client/views/nodes/KeyValueBox.tsx
@@ -18,7 +18,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
@observable private _keyInput: string = "";
@observable private _valueInput: string = "";
@computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage, 50); }
-
+ get fieldDocToLayout() { return this.props.fieldKey ? FieldValue(Cast(this.props.Document[this.props.fieldKey], Doc)) : this.props.Document; }
constructor(props: FieldViewProps) {
super(props);
@@ -28,7 +28,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
onEnterKey = (e: React.KeyboardEvent): void => {
if (e.key === 'Enter') {
if (this._keyInput && this._valueInput) {
- let doc = FieldValue(Cast(this.props.Document.data, Doc));
+ let doc = this.fieldDocToLayout;
if (!doc) {
return;
}
@@ -60,7 +60,7 @@ export class KeyValueBox extends React.Component<FieldViewProps> {
}
createTable = () => {
- let doc = FieldValue(Cast(this.props.Document.data, Doc));
+ let doc = this.fieldDocToLayout;
if (!doc) {
return <tr><td>Loading...</td></tr>;
}
diff --git a/src/client/views/nodes/KeyValuePair.scss b/src/client/views/nodes/KeyValuePair.scss
index ff6885965..a1c5d5537 100644
--- a/src/client/views/nodes/KeyValuePair.scss
+++ b/src/client/views/nodes/KeyValuePair.scss
@@ -26,4 +26,12 @@
.keyValuePair-td-value {
display:inline-block;
overflow: scroll;
+ img {
+ max-height: 36px;
+ width: auto;
+ }
+ .videoBox-cont{
+ width: auto;
+ max-height: 36px;
+ }
} \ No newline at end of file
diff --git a/src/client/views/nodes/KeyValuePair.tsx b/src/client/views/nodes/KeyValuePair.tsx
index 5de660d57..4f7919f50 100644
--- a/src/client/views/nodes/KeyValuePair.tsx
+++ b/src/client/views/nodes/KeyValuePair.tsx
@@ -46,8 +46,9 @@ export class KeyValuePair extends React.Component<KeyValuePairProps> {
<td className="keyValuePair-td-key" style={{ width: `${this.props.keyWidth}%` }}>
<div className="keyValuePair-td-key-container">
<button className="keyValuePair-td-key-delete" onClick={() => {
- if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1)
+ if (Object.keys(props.Document).indexOf(props.fieldKey) !== -1) {
props.Document[props.fieldKey] = undefined;
+ }
else props.Document.proto![props.fieldKey] = undefined;
}}>
X
diff --git a/src/client/views/nodes/LinkBox.tsx b/src/client/views/nodes/LinkBox.tsx
index 611cb66b6..68b692aad 100644
--- a/src/client/views/nodes/LinkBox.tsx
+++ b/src/client/views/nodes/LinkBox.tsx
@@ -31,7 +31,7 @@ export class LinkBox extends React.Component<Props> {
@undoBatch
onViewButtonPressed = async (e: React.PointerEvent): Promise<void> => {
e.stopPropagation();
- DocumentManager.Instance.jumpToDocument(this.props.pairedDoc);
+ DocumentManager.Instance.jumpToDocument(this.props.pairedDoc, e.altKey);
}
onEditButtonPressed = (e: React.PointerEvent): void => {
diff --git a/src/client/views/nodes/LinkMenu.tsx b/src/client/views/nodes/LinkMenu.tsx
index 7bf13d5f9..4cf798249 100644
--- a/src/client/views/nodes/LinkMenu.tsx
+++ b/src/client/views/nodes/LinkMenu.tsx
@@ -6,8 +6,7 @@ import { LinkEditor } from "./LinkEditor";
import './LinkMenu.scss';
import React = require("react");
import { Doc, DocListCast } from "../../../new_fields/Doc";
-import { Cast, FieldValue } from "../../../new_fields/Types";
-import { listSpec } from "../../../new_fields/Schema";
+import { Cast, FieldValue, StrCast } from "../../../new_fields/Types";
import { Id } from "../../../new_fields/RefField";
interface Props {
@@ -24,7 +23,7 @@ export class LinkMenu extends React.Component<Props> {
return links.map(link => {
let doc = FieldValue(Cast(link[key], Doc));
if (doc) {
- return <LinkBox key={doc[Id]} linkDoc={link} linkName={Cast(link.title, "string", "")} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
+ return <LinkBox key={doc[Id]} linkDoc={link} linkName={StrCast(link.title)} pairedDoc={doc} showEditor={action(() => this._editingLink = link)} type={type} />;
}
});
}
@@ -32,11 +31,11 @@ export class LinkMenu extends React.Component<Props> {
render() {
//get list of links from document
let linkFrom = DocListCast(this.props.docView.props.Document.linkedFromDocs);
- let linkTo = DocListCast(this.props.docView.props.Document.linkedToDoc);
+ let linkTo = DocListCast(this.props.docView.props.Document.linkedToDocs);
if (this._editingLink === undefined) {
return (
<div id="linkMenu-container">
- <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input>
+ {/* <input id="linkMenu-searchBar" type="text" placeholder="Search..."></input> */}
<div id="linkMenu-list">
{this.renderLinkItems(linkTo, "linkedTo", "Destination: ")}
{this.renderLinkItems(linkFrom, "linkedFrom", "Source: ")}
diff --git a/src/client/views/nodes/PDFBox.tsx b/src/client/views/nodes/PDFBox.tsx
index bf3f299bc..9b0207d0c 100644
--- a/src/client/views/nodes/PDFBox.tsx
+++ b/src/client/views/nodes/PDFBox.tsx
@@ -1,5 +1,5 @@
import * as htmlToImage from "html-to-image";
-import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace } from 'mobx';
+import { action, computed, IReactionDisposer, observable, reaction, Reaction, trace, runInAction } from 'mobx';
import { observer } from "mobx-react";
import 'react-image-lightbox/style.css';
import Measure from "react-measure";
@@ -54,10 +54,12 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
public static LayoutString() { return FieldView.LayoutString(PDFBox); }
private _mainDiv = React.createRef<HTMLDivElement>();
+ private renderHeight = 2400;
@observable private _renderAsSvg = true;
+ @observable private _alt = false;
- private _reactionDisposer: Opt<IReactionDisposer>;
+ private _reactionDisposer?: IReactionDisposer;
@observable private _perPageInfo: Object[] = []; //stores pageInfo
@observable private _pageInfo: any = { area: [], divs: [], anno: [] }; //divs is array of objects linked to anno
@@ -66,26 +68,25 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
@observable private _interactive: boolean = false;
@observable private _loaded: boolean = false;
- @computed private get curPage() { return FieldValue(this.Document.curPage, 1); }
- @computed private get thumbnailPage() { return Cast(this.props.Document.thumbnailPage, "number", -1); }
+ @computed private get curPage() { return NumCast(this.Document.curPage, 1); }
+ @computed private get thumbnailPage() { return NumCast(this.props.Document.thumbnailPage, -1); }
componentDidMount() {
- // this._reactionDisposer = reaction(
- // () => [SelectionManager.SelectedDocuments().slice()],
- // () => {
- // if (this.curPage > 0 && this.thumbnailPage > 0 && this.curPage !== this.thumbnailPage && !this.props.isSelected()) {
- // this.saveThumbnail();
- // this._interactive = true;
- // }
- // },
- // { fireImmediately: true });
+ let wasSelected = false;
+ this._reactionDisposer = reaction(
+ () => this.props.isSelected(),
+ () => {
+ if (this.curPage > 0 && this.curPage !== this.thumbnailPage && wasSelected && !this.props.isSelected()) {
+ this.saveThumbnail();
+ }
+ wasSelected = this._interactive = this.props.isSelected();
+ },
+ { fireImmediately: true });
}
componentWillUnmount() {
- if (this._reactionDisposer) {
- this._reactionDisposer();
- }
+ if (this._reactionDisposer) this._reactionDisposer();
}
/**
@@ -163,10 +164,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
let index: any;
this._pageInfo.divs.forEach((obj: any) => {
obj.spans.forEach((element: any) => {
- if (element === span) {
- if (!index) {
- index = this._pageInfo.divs.indexOf(obj);
- }
+ if (element === span && !index) {
+ index = this._pageInfo.divs.indexOf(obj);
}
});
});
@@ -224,7 +223,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
document.addEventListener("pointerup", this.onPointerUp);
}
if (this.props.isSelected() && e.buttons === 2) {
- this._alt = true;
+ runInAction(() => this._alt = true);
document.removeEventListener("pointerup", this.onPointerUp);
document.addEventListener("pointerup", this.onPointerUp);
}
@@ -244,7 +243,6 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
-
@action
saveThumbnail = () => {
this._renderAsSvg = false;
@@ -260,7 +258,7 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
.catch(function (error: any) {
console.error('oops, something went wrong!', error);
});
- }, 250);
+ }, 1250);
}
@action
@@ -285,29 +283,28 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
this.props.Document.nativeHeight = nativeHeight;
}
}
- renderHeight = 2400;
@computed
get pdfPage() {
- return <Page height={this.renderHeight} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />;
+ return <Page height={this.renderHeight} renderTextLayer={false} pageNumber={this.curPage} onLoadSuccess={this.onLoaded} />;
}
@computed
get pdfContent() {
- let page = this.curPage;
- const renderHeight = 2400;
+ trace();
let pdfUrl = Cast(this.props.Document[this.props.fieldKey], PdfField);
if (!pdfUrl) {
return <p>No pdf url to render</p>;
}
- let xf = FieldValue(this.Document.nativeHeight, 0) / renderHeight;
- let body = NumCast(this.props.Document.nativeHeight) ?
- this.pdfPage :
+ let pdfpage = this.pdfPage;
+ let body = this.Document.nativeHeight ?
+ pdfpage :
<Measure offset onResize={this.setScaling}>
{({ measureRef }) =>
<div className="pdfBox-page" ref={measureRef}>
- {this.pdfPage}
+ {pdfpage}
</div>
}
</Measure>;
+ let xf = (this.Document.nativeHeight || 0) / this.renderHeight;
return <div className="pdfBox-contentContainer" key="container" style={{ transform: `scale(${xf}, ${xf})` }}>
<Document file={window.origin + RouteStore.corsProxy + `/${pdfUrl.url}`} renderMode={this._renderAsSvg ? "svg" : "canvas"}>
{body}
@@ -340,19 +337,8 @@ export class PDFBox extends DocComponent<FieldViewProps, PdfDocument>(PdfDocumen
}
return (null);
}
- @observable _alt = false;
- @action
- onKeyDown = (e: React.KeyboardEvent) => {
- if (e.key === "Alt") {
- this._alt = true;
- }
- }
- @action
- onKeyUp = (e: React.KeyboardEvent) => {
- if (e.key === "Alt") {
- this._alt = false;
- }
- }
+ @action onKeyDown = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = true);
+ @action onKeyUp = (e: React.KeyboardEvent) => e.key === "Alt" && (this._alt = false);
render() {
trace();
const pdfUrl = window.origin + RouteStore.corsProxy + "/https://mozilla.github.io/pdf.js/web/compressed.tracemonkey-pldi-09.pdf";
diff --git a/src/client/views/nodes/VideoBox.scss b/src/client/views/nodes/VideoBox.scss
index 76bbeb37c..35db64cf4 100644
--- a/src/client/views/nodes/VideoBox.scss
+++ b/src/client/views/nodes/VideoBox.scss
@@ -1,4 +1,8 @@
-.videobox-cont{
+.videoBox-cont, .videoBox-cont-fullScreen{
width: 100%;
height: Auto;
+}
+
+.videoBox-cont-fullScreen {
+ pointer-events: all;
} \ No newline at end of file
diff --git a/src/client/views/nodes/VideoBox.tsx b/src/client/views/nodes/VideoBox.tsx
index 97c5d8818..81c429a02 100644
--- a/src/client/views/nodes/VideoBox.tsx
+++ b/src/client/views/nodes/VideoBox.tsx
@@ -1,27 +1,31 @@
import React = require("react");
import { observer } from "mobx-react";
import { FieldView, FieldViewProps } from './FieldView';
+import * as rp from "request-promise";
import "./VideoBox.scss";
-import { action, computed, trace } from "mobx";
+import { action, IReactionDisposer, reaction, observable } from "mobx";
import { DocComponent } from "../DocComponent";
import { positionSchema } from "./DocumentView";
import { makeInterface } from "../../../new_fields/Schema";
import { pageSchema } from "./ImageBox";
-import { Cast, FieldValue, NumCast, ToConstructor, ListSpec } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
import Measure from "react-measure";
import "./VideoBox.scss";
-import { Field, FieldResult, Opt } from "../../../new_fields/Doc";
+import { RouteStore } from "../../../server/RouteStore";
+import { DocServer } from "../../DocServer";
type VideoDocument = makeInterface<[typeof positionSchema, typeof pageSchema]>;
const VideoDocument = makeInterface(positionSchema, pageSchema);
@observer
export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoDocument) {
-
+ private _reactionDisposer?: IReactionDisposer;
private _videoRef: HTMLVideoElement | null = null;
private _loaded: boolean = false;
- private get initialTimecode() { return FieldValue(this.Document.curPage, -1); }
+ @observable _playTimer?: NodeJS.Timeout = undefined;
+ @observable _fullScreen = false;
+ @observable public Playing: boolean = false;
public static LayoutString() { return FieldView.LayoutString(VideoBox); }
public get player(): HTMLVideoElement | undefined {
@@ -45,29 +49,106 @@ export class VideoBox extends DocComponent<FieldViewProps, VideoDocument>(VideoD
}
}
+ @action public Play() {
+ this.Playing = true;
+ if (this.player) this.player.play();
+ if (!this._playTimer) this._playTimer = setInterval(this.updateTimecode, 1000);
+ }
+
+ @action public Pause() {
+ this.Playing = false;
+ if (this.player) this.player.pause();
+ if (this._playTimer) {
+ clearInterval(this._playTimer);
+ this._playTimer = undefined;
+ }
+ }
+
+ @action public FullScreen() {
+ this._fullScreen = true;
+ this.player && this.player.requestFullscreen();
+ }
+
+ @action
+ updateTimecode = () => this.player && (this.props.Document.curPage = this.player.currentTime)
+
componentDidMount() {
if (this.props.setVideoBox) this.props.setVideoBox(this);
}
+ componentWillUnmount() {
+ this.Pause();
+ if (this._reactionDisposer) this._reactionDisposer();
+ }
@action
setVideoRef = (vref: HTMLVideoElement | null) => {
this._videoRef = vref;
- if (this.initialTimecode >= 0 && vref) {
- vref.currentTime = this.initialTimecode;
+ if (vref) {
+ vref.onfullscreenchange = action((e) => this._fullScreen = vref.webkitDisplayingFullscreen);
+ if (this._reactionDisposer) this._reactionDisposer();
+ this._reactionDisposer = reaction(() => this.props.Document.curPage, () =>
+ vref.currentTime = NumCast(this.props.Document.curPage, 0), { fireImmediately: true });
}
}
videoContent(path: string) {
- return <video className="videobox-cont" ref={this.setVideoRef}>
+ let style = "videoBox-cont" + (this._fullScreen ? "-fullScreen" : "");
+ return <video className={`${style}`} ref={this.setVideoRef} onPointerDown={this.onPointerDown}>
<source src={path} type="video/mp4" />
Not supported.
</video>;
}
+ getMp4ForVideo(videoId: string = "JN5beCVArMs") {
+ return new Promise(async (resolve, reject) => {
+ const videoInfoRequestConfig = {
+ headers: {
+ connection: 'keep-alive',
+ "user-agent": 'Mozilla/5.0 (Windows NT 10.0; WOW64; rv:43.0) Gecko/20100101 Firefox/46.0',
+ },
+
+ };
+ try {
+ let responseSchema: any = {};
+ const videoInfoResponse = await rp.get(DocServer.prepend(RouteStore.corsProxy + "/" + `https://www.youtube.com/watch?v=${videoId}`), videoInfoRequestConfig);
+ const dataHtml = videoInfoResponse;
+ const start = dataHtml.indexOf('ytplayer.config = ') + 18;
+ const end = dataHtml.indexOf(';ytplayer.load');
+ const subString = dataHtml.substring(start, end);
+ const subJson = JSON.parse(subString);
+ const stringSub = subJson.args.player_response;
+ const stringSubJson = JSON.parse(stringSub);
+ const adaptiveFormats = stringSubJson.streamingData.adaptiveFormats;
+ const videoDetails = stringSubJson.videoDetails;
+ responseSchema.adaptiveFormats = adaptiveFormats;
+ responseSchema.videoDetails = videoDetails;
+ resolve(responseSchema);
+ }
+ catch (err) {
+ console.log(`
+ --- Youtube ---
+ Function: getMp4ForVideo
+ Error: `, err);
+ reject(err);
+ }
+ });
+ }
+ onPointerDown = (e: React.PointerEvent) => {
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
render() {
let field = Cast(this.Document[this.props.fieldKey], VideoField);
if (!field) {
return <div>Loading</div>;
}
+
+ // this.getMp4ForVideo().then((mp4) => {
+ // console.log(mp4);
+ // }).catch(e => {
+ // console.log("")
+ // });
+ // //
let content = this.videoContent(field.url.href);
return NumCast(this.props.Document.nativeHeight) ?
content :