aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorbob <bcz@cs.brown.edu>2019-06-11 10:53:36 -0400
committerbob <bcz@cs.brown.edu>2019-06-11 10:53:36 -0400
commit95674ee7f68782d1ce85858120efea956825bcb9 (patch)
tree0d2ddc9838b45278d6533099f7030a9713d6413f /src/client/views/collections
parent4dacd1220e6a0ef73167f187f52f3b4c222c2586 (diff)
parent06d5bb5c65da6f4a115ebf8118989115420bccef (diff)
merged embedded linking with master
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionBaseView.scss11
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx46
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx77
-rw-r--r--src/client/views/collections/CollectionPDFView.scss39
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx6
-rw-r--r--src/client/views/collections/CollectionSchemaView.scss86
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx286
-rw-r--r--src/client/views/collections/CollectionStackingView.scss41
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx185
-rw-r--r--src/client/views/collections/CollectionSubView.tsx88
-rw-r--r--src/client/views/collections/CollectionTreeView.scss79
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx277
-rw-r--r--src/client/views/collections/CollectionVideoView.scss2
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx49
-rw-r--r--src/client/views/collections/CollectionView.tsx43
-rw-r--r--src/client/views/collections/ParentDocumentSelector.scss16
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx48
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss6
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx30
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx48
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx13
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss8
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx89
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx220
24 files changed, 1248 insertions, 545 deletions
diff --git a/src/client/views/collections/CollectionBaseView.scss b/src/client/views/collections/CollectionBaseView.scss
new file mode 100644
index 000000000..1f5acb96a
--- /dev/null
+++ b/src/client/views/collections/CollectionBaseView.scss
@@ -0,0 +1,11 @@
+@import "../globalCssVariables";
+#collectionBaseView {
+ border-width: 0;
+ box-shadow: $intermediate-color 0.2vw 0.2vw 0.8vw;
+ border-color: $light-color-secondary;
+ border-style: solid;
+ border-radius: 0 0 $border-radius $border-radius;
+ box-sizing: border-box;
+ border-radius: inherit;
+ pointer-events: all;
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 31bdf213e..5238ad114 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -1,14 +1,16 @@
-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';
-import { FieldViewProps } from '../nodes/FieldView';
-import { Cast, FieldValue, PromiseValue, NumCast } from '../../../new_fields/Types';
-import { Doc, FieldResult, Opt, DocListCast } from '../../../new_fields/Doc';
-import { listSpec } from '../../../new_fields/Schema';
+import { Doc, DocListCast, Opt } from '../../../new_fields/Doc';
+import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
-import { Id } from '../../../new_fields/RefField';
+import { listSpec } from '../../../new_fields/Schema';
+import { Cast, FieldValue, NumCast, PromiseValue } from '../../../new_fields/Types';
import { SelectionManager } from '../../util/SelectionManager';
+import { ContextMenu } from '../ContextMenu';
+import { FieldViewProps } from '../nodes/FieldView';
+import './CollectionBaseView.scss';
+import { DocumentManager } from '../../util/DocumentManager';
export enum CollectionViewType {
Invalid,
@@ -16,6 +18,7 @@ export enum CollectionViewType {
Schema,
Docking,
Tree,
+ Stacking
}
export interface CollectionRenderProps {
@@ -36,9 +39,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,15 +96,15 @@ 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 = NumCast(props.Document.curPage, -1);
- Doc.SetOnPrototype(doc, "page", curPage);
+ Doc.GetProto(doc).page = curPage;
if (curPage >= 0) {
- Doc.SetOnPrototype(doc, "annotationOn", props.Document);
+ Doc.GetProto(doc).annotationOn = props.Document;
}
if (!this.createsCycle(doc, props.Document)) {
//TODO This won't create the field if it doesn't already exist
@@ -108,7 +122,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
// set the ZoomBasis only if hasn't already been set -- bcz: maybe set/resetting the ZoomBasis should be a parameter to addDocument?
if (!alreadyAdded && (this.collectionViewType === CollectionViewType.Freeform || this.collectionViewType === CollectionViewType.Invalid)) {
let zoom = NumCast(this.props.Document.scale, 1);
- Doc.SetOnPrototype(doc, "zoomBasis", zoom);
+ // Doc.GetProto(doc).zoomBasis = zoom;
}
}
return true;
@@ -116,6 +130,8 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
@action.bound
removeDocument(doc: Doc): boolean {
+ let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView)
+ docView && SelectionManager.DeselectDoc(docView);
const props = this.props;
//TODO This won't create the field if it doesn't already exist
const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);
@@ -127,7 +143,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
break;
}
}
- PromiseValue(Cast(doc.annotationOn, Doc)).then((annotationOn) => {
+ PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => {
if (annotationOn === props.Document) {
doc.annotationOn = undefined;
}
@@ -145,11 +161,10 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
@action.bound
moveDocument(doc: Doc, targetCollection: Doc, addDocument: (doc: Doc) => boolean): boolean {
- if (this.props.Document === targetCollection) {
+ if (Doc.AreProtosEqual(this.props.Document, targetCollection)) {
return true;
}
if (this.removeDocument(doc)) {
- SelectionManager.DeselectAll();
return addDocument(doc);
}
return false;
@@ -165,8 +180,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
};
const viewtype = this.collectionViewType;
return (
- <div className={this.props.className || "collectionView-cont"}
- style={{ borderRadius: "inherit", pointerEvents: "all" }}
+ <div id="collectionBaseView" className={this.props.className || "collectionView-cont"}
onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}>
{viewtype !== undefined ? this.props.children(viewtype, props) : (null)}
</div>
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 58f1e33a1..51e29cb54 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,25 +1,29 @@
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction, Lambda } from "mobx";
+import { action, Lambda, observable, reaction } 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, DocListCast } from "../../../new_fields/Doc";
-import { FieldId, Id } from "../../../new_fields/RefField";
+import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc";
+import { Id } from '../../../new_fields/FieldSymbols';
+import { FieldId } from "../../../new_fields/RefField";
import { listSpec } from "../../../new_fields/Schema";
import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
import { emptyFunction, returnTrue, Utils } from "../../../Utils";
import { DocServer } from "../../DocServer";
+import { DocumentManager } from '../../util/DocumentManager';
import { DragLinksAsDocuments, DragManager } from "../../util/DragManager";
+import { SelectionManager } from '../../util/SelectionManager';
import { Transform } from '../../util/Transform';
import { undoBatch, UndoManager } from "../../util/UndoManager";
import { DocumentView } from "../nodes/DocumentView";
+import { CollectionViewType } from './CollectionBaseView';
import "./CollectionDockingView.scss";
import { SubCollectionViewProps } from "./CollectionSubView";
-import React = require("react");
import { ParentDocSelector } from './ParentDocumentSelector';
-import { DocumentManager } from '../../util/DocumentManager';
+import React = require("react");
+import { MainView } from '../MainView';
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
@@ -74,7 +78,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@undoBatch
@action
- public CloseRightSplit(document: Doc): boolean {
+ public CloseRightSplit = (document: Doc): boolean => {
let retVal = false;
if (this._goldenLayout.root.contentItems[0].isRow) {
retVal = Array.from(this._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
@@ -83,7 +87,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
child.contentItems[0].remove();
this.layoutChanged(document);
return true;
- } else
+ } 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();
@@ -94,8 +98,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
return false;
});
+ }
return false;
- })
+ });
}
if (retVal) {
this.stateChanged();
@@ -106,7 +111,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@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();
@@ -116,7 +121,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
// Creates a vertical split on the right side of the docking view, and then adds the Document to that split
//
@action
- public AddRightSplit(document: Doc, minimize: boolean = false) {
+ public AddRightSplit = (document: Doc, minimize: boolean = false) => {
let docs = Cast(this.props.Document.data, listSpec(Doc));
if (docs) {
docs.push(document);
@@ -152,6 +157,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);
@@ -257,6 +273,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,
@@ -314,7 +334,8 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
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);
+ const stack = tab.contentItem.parent;
+ ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.Instance.AddTab(stack, doc)} />, upDiv);
tab.reactComponents = [upDiv];
tab.element.append(upDiv);
counter.DashDocId = tab.contentItem.config.props.documentId;
@@ -327,6 +348,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
});
}
+ tab.titleElement[0].Tab = tab;
tab.closeElement.off('click') //unbind the current click handler
.click(async function () {
if (tab.reactionDisposer) {
@@ -336,6 +358,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (doc instanceof Doc) {
let theDoc = doc;
CollectionDockingView.Instance._removedDocs.push(theDoc);
+ SelectionManager.DeselectAll();
}
tab.contentItem.remove();
});
@@ -394,7 +417,12 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
@observable private _panelWidth = 0;
@observable private _panelHeight = 0;
@observable private _document: Opt<Doc>;
-
+ get _stack(): any {
+ let parent = (this.props as any).glContainer.parent.parent;
+ if (this._document && this._document.excludeFromLibrary && parent.parent && parent.parent.contentItems.length > 1)
+ return parent.parent.contentItems[1];
+ return parent;
+ }
constructor(props: any) {
super(props);
DocServer.GetRefField(this.props.documentId).then(action((f: Opt<Field>) => this._document = f as Doc));
@@ -413,21 +441,39 @@ 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 / this.contentScaling() / 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);
+ return scaling;
+ }
get previewPanelCenteringOffset() { return (this._panelWidth - this.nativeWidth() * this.contentScaling()) / 2; }
+ addDocTab = (doc: Doc, location: string) => {
+ if (doc.dockingConfig) {
+ MainView.Instance.openWorkspace(doc);
+ } else 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}
addDocument={undefined}
removeDocument={undefined}
@@ -440,6 +486,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.scss b/src/client/views/collections/CollectionPDFView.scss
index f6fb79582..50201bae8 100644
--- a/src/client/views/collections/CollectionPDFView.scss
+++ b/src/client/views/collections/CollectionPDFView.scss
@@ -1,49 +1,56 @@
.collectionPdfView-buttonTray {
- top : 15px;
- left : 20px;
- position: relative;
+ top: 15px;
+ left: 20px;
+ position: relative;
transform-origin: left top;
position: absolute;
}
+
.collectionPdfView-thumb {
- width:25px;
- height:25px;
+ width: 25px;
+ height: 25px;
transform-origin: left top;
position: absolute;
background: darkgray;
}
+
.collectionPdfView-slider {
- width:25px;
- height:25px;
+ width: 25px;
+ height: 25px;
transform-origin: left top;
position: absolute;
background: lightgray;
}
-.collectionPdfView-cont{
+
+.collectionPdfView-cont {
width: 100%;
height: 100%;
- position: absolute;
+ position: absolute;
top: 0;
- left:0;
+ left: 0;
+ z-index: -1;
}
+
.collectionPdfView-cont-dragging {
span {
user-select: none;
}
}
+
.collectionPdfView-backward {
- color : white;
+ color: white;
font-size: 24px;
- top :0px;
- left : 0px;
+ top: 0px;
+ left: 0px;
position: absolute;
background-color: rgba(50, 50, 50, 0.2);
}
+
.collectionPdfView-forward {
- color : white;
+ color: white;
font-size: 24px;
- top :0px;
- left : 45px;
+ top: 0px;
+ left: 45px;
position: absolute;
background-color: rgba(50, 50, 50, 0.2);
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index b3762206a..bf887ce7c 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -8,7 +8,7 @@ import { FieldView, FieldViewProps } from "../nodes/FieldView";
import { CollectionRenderProps, CollectionBaseView, CollectionViewType } from "./CollectionBaseView";
import { emptyFunction } from "../../../Utils";
import { NumCast } from "../../../new_fields/Types";
-import { Id } from "../../../new_fields/RefField";
+import { Id } from "../../../new_fields/FieldSymbols";
@observer
@@ -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" });
}
}
@@ -70,7 +70,7 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
return (
<>
<CollectionFreeFormView {...props} CollectionView={this} />
- {this.props.isSelected() ? this.uIButtons : (null)}
+ {renderProps.active() ? this.uIButtons : (null)}
</>
);
}
diff --git a/src/client/views/collections/CollectionSchemaView.scss b/src/client/views/collections/CollectionSchemaView.scss
index 5e9d2ac67..186e006f3 100644
--- a/src/client/views/collections/CollectionSchemaView.scss
+++ b/src/client/views/collections/CollectionSchemaView.scss
@@ -4,56 +4,63 @@
.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;
position: absolute;
width: 100%;
height: 100%;
+ overflow: hidden;
.collectionSchemaView-cellContents {
height: $MAX_ROW_HEIGHT;
+
img {
- width:auto;
+ 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;
@@ -61,6 +68,7 @@
top: 0;
background: $main-accent;
}
+
.collectionSchemaView-columnsHandle {
position: absolute;
height: 37px;
@@ -70,6 +78,7 @@
bottom: 0;
background: $main-accent;
}
+
.collectionSchemaView-colDividerDragger {
position: relative;
box-sizing: border-box;
@@ -78,6 +87,7 @@
float: top;
width: 100%;
}
+
.collectionSchemaView-dividerDragger {
position: relative;
box-sizing: border-box;
@@ -86,11 +96,13 @@
float: left;
height: 100%;
}
+
.collectionSchemaView-tableContainer {
position: relative;
float: left;
height: 100%;
}
+
.ReactTable {
// position: absolute; // display: inline-block;
// overflow: auto;
@@ -99,6 +111,7 @@
background: $light-color;
box-sizing: border-box;
border: none !important;
+
.rt-table {
overflow-y: auto;
overflow-x: auto;
@@ -107,26 +120,32 @@
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 {
object-fit: contain;
width: auto;
@@ -134,15 +153,17 @@
}
}
}
+
.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;
@@ -150,32 +171,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%;
}
@@ -189,10 +214,12 @@ ul {
padding: 0px;
margin: 0px;
}
+
.schema-options-subHeader {
color: $intermediate-color;
margin-bottom: 5px;
}
+
#schemaOptionsMenuBtn:hover {
transform: scale(1.15);
}
@@ -202,15 +229,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;
@@ -218,8 +245,8 @@ ul {
max-height: 175px;
font-family: $sans-serif;
font-size: 12px;
- }
-
+}
+
.Resizer {
box-sizing: border-box;
@@ -227,6 +254,7 @@ ul {
opacity: 0.5;
z-index: 1;
background-clip: padding-box;
+
&.horizontal {
height: 11px;
margin: -5px 0;
@@ -234,22 +262,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;
@@ -270,10 +302,12 @@ ul {
-ms-flex-direction: column;
flex-direction: column;
}
+
header {
padding: 1rem;
background: #eee;
}
+
footer {
padding: 1rem;
background: #eee;
@@ -287,10 +321,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 f4ad5b357..b9e5a5b65 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -2,7 +2,7 @@ import React = require("react");
import { library } from '@fortawesome/fontawesome-svg-core';
import { faCog, faPlus } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable, untracked, runInAction } from "mobx";
+import { action, computed, observable, untracked, runInAction, trace } from "mobx";
import { observer } from "mobx-react";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
@@ -20,22 +20,22 @@ 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, StrCast } from "../../../new_fields/Types";
+import { Cast, FieldValue, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
import { listSpec } from "../../../new_fields/Schema";
import { List } from "../../../new_fields/List";
-import { Id } from "../../../new_fields/RefField";
-import { isUndefined } from "typescript-collections/dist/lib/util";
-import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { Id } from "../../../new_fields/FieldSymbols";
import { Gateway } from "../../northstar/manager/Gateway";
-import { DocServer } from "../../DocServer";
-import { ColumnAttributeModel } from "../../northstar/core/attribute/AttributeModel";
-import { HistogramOperation } from "../../northstar/operations/HistogramOperation";
-import { AggregateFunction } from "../../northstar/model/idea/idea";
-import { AttributeTransformationModel } from "../../northstar/core/attribute/AttributeTransformationModel";
import { Docs } from "../../documents/Documents";
import { ContextMenu } from "../ContextMenu";
+import { CollectionView } from "./CollectionView";
+import { CollectionPDFView } from "./CollectionPDFView";
+import { CollectionVideoView } from "./CollectionVideoView";
+import { SelectionManager } from "../../util/SelectionManager";
+import { undoBatch } from "../../util/UndoManager";
+library.add(faCog);
+library.add(faPlus);
// bcz: need to add drag and drop of rows and columns. This seems like it might work for rows: https://codesandbox.io/s/l94mn1q657
@@ -58,7 +58,7 @@ class KeyToggle extends React.Component<{ keyName: string, checked: boolean, tog
@observer
export class CollectionSchemaView extends CollectionSubView(doc => doc) {
private _mainCont?: HTMLDivElement;
- private _startSplitPercent = 0;
+ private _startPreviewWidth = 0;
private DIVIDER_WIDTH = 4;
@observable _columns: Array<string> = ["title", "data", "author"];
@@ -66,15 +66,41 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@observable _columnsPercentage = 0;
@observable _keys: string[] = [];
@observable _newKeyName: string = "";
+ @observable previewScript: string = "";
- @computed get splitPercentage() { return NumCast(this.props.Document.schemaSplitPercentage); }
+ @computed get previewWidth() { return () => NumCast(this.props.Document.schemaPreviewWidth); }
+ @computed get previewHeight() { return () => this.props.PanelHeight() - 2 * this.borderWidth; }
+ @computed get tableWidth() { return this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH - this.previewWidth(); }
@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), undefined, "copy")}>{col}</p>,
+ accessor: (doc: Doc) => doc ? doc[col] : 0,
+ id: col
+ };
+ });
+ }
+
+ onHeaderDrag = (columnName: string) => {
+ let schemaDoc = Cast(this.props.Document.schemaDoc, Doc);
+ if (schemaDoc instanceof Doc) {
+ let columnDocs = DocListCast(schemaDoc.data);
+ if (columnDocs) {
+ let ddoc = columnDocs.find(doc => doc.title === columnName);
+ if (ddoc)
+ return ddoc;
+ }
+ }
+ 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,
@@ -86,12 +112,13 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
whenActiveChanged: emptyFunction,
PanelHeight: returnZero,
PanelWidth: returnZero,
+ addDocTab: this.props.addDocTab,
};
let fieldContentView = <FieldView {...props} />;
let reference = React.createRef<HTMLDivElement>();
let onItemDown = (e: React.PointerEvent) =>
- (this.props.CollectionView!.props.isSelected() ?
- SetupDrag(reference, () => props.Document, this.props.moveDocument)(e) : undefined);
+ (this.props.CollectionView.props.isSelected() ?
+ SetupDrag(reference, () => props.Document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e) : undefined);
let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
const res = run({ this: doc });
if (!res.success) return false;
@@ -106,28 +133,26 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
height={Number(MAX_ROW_HEIGHT)}
GetValue={() => {
let field = props.Document[props.fieldKey];
- if (field) {
- //TODO Types
- // return field.ToScriptString();
- return String(field);
+ if (Field.IsField(field)) {
+ return Field.toScriptString(field);
}
return "";
}}
SetValue={(value: string) => {
- let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
+ let script = CompileScript(value, { addReturn: true, params: { this: Doc.name } });
if (!script.compiled) {
return false;
}
return applyToDoc(props.Document, script.run);
}}
OnFillDown={async (value: string) => {
- let script = CompileScript(value, { addReturn: true, params: { this: Document.name } });
+ let script = CompileScript(value, { addReturn: true, params: { this: Doc.name } });
if (!script.compiled) {
return;
}
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>
@@ -178,30 +203,31 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
//toggles preview side-panel of schema
@action
- toggleExpander = (event: React.ChangeEvent<HTMLInputElement>) => {
- this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;
+ toggleExpander = () => {
+ this.props.Document.schemaPreviewWidth = this.previewWidth() === 0 ? Math.min(this.tableWidth / 3, 200) : 0;
}
+ onDividerDown = (e: React.PointerEvent) => {
+ this._startPreviewWidth = this.previewWidth();
+ e.stopPropagation();
+ e.preventDefault();
+ document.addEventListener("pointermove", this.onDividerMove);
+ document.addEventListener('pointerup', this.onDividerUp);
+ }
@action
onDividerMove = (e: PointerEvent): void => {
let nativeWidth = this._mainCont!.getBoundingClientRect();
- this.props.Document.schemaSplitPercentage = Math.max(0, 100 - Math.round((e.clientX - nativeWidth.left) / nativeWidth.width * 100));
+ this.props.Document.schemaPreviewWidth = Math.min(nativeWidth.right - nativeWidth.left - 40,
+ this.props.ScreenToLocalTransform().transformDirection(nativeWidth.right - e.clientX, 0)[0]);
}
@action
onDividerUp = (e: PointerEvent): void => {
document.removeEventListener("pointermove", this.onDividerMove);
document.removeEventListener('pointerup', this.onDividerUp);
- if (this._startSplitPercent === this.splitPercentage) {
- this.props.Document.schemaSplitPercentage = this.splitPercentage === 0 ? 33 : 0;
+ if (this._startPreviewWidth === this.previewWidth()) {
+ this.toggleExpander();
}
}
- onDividerDown = (e: React.PointerEvent) => {
- this._startSplitPercent = this.splitPercentage;
- e.stopPropagation();
- e.preventDefault();
- document.addEventListener("pointermove", this.onDividerMove);
- document.addEventListener('pointerup', this.onDividerUp);
- }
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
@@ -228,16 +254,17 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
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 += self.columns.reduce((val, col) => val + (doc[col] ? doc[col]!.toString() : "0") + ",", "");
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 });
+ let schemaDoc = await Docs.DBDocument("https://www.cs.brown.edu/" + dbName, { title: dbName }, { dbDoc: self.props.Document });
if (schemaDoc) {
- self.props.CollectionView.props.addDocument(schemaDoc, false);
+ //self.props.CollectionView.props.addDocument(schemaDoc, false);
+ self.props.Document.schemaDoc = schemaDoc;
}
}
}
@@ -253,62 +280,16 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
this._newKeyName = e.currentTarget.value;
}
- @observable previewScript: string = "";
- @action
- onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
- this.previewScript = e.currentTarget.value;
- }
-
@computed
get previewDocument(): Doc | undefined {
const children = DocListCast(this.props.Document[this.props.fieldKey]);
const selected = children.length > this._selectedIndex ? FieldValue(children[this._selectedIndex]) : 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; }
- get previewRegionWidth() { return (this.props.PanelWidth() - 2 * this.borderWidth - this.DIVIDER_WIDTH) * this.splitPercentage / 100; }
-
- private previewDocNativeWidth = () => Cast(this.previewDocument!.nativeWidth, "number", this.previewRegionWidth);
- private previewDocNativeHeight = () => Cast(this.previewDocument!.nativeHeight, "number", this.previewRegionHeight);
- private previewContentScaling = () => {
- let wscale = this.previewRegionWidth / (this.previewDocNativeWidth() ? this.previewDocNativeWidth() : this.previewRegionWidth);
- if (wscale * this.previewDocNativeHeight() > this.previewRegionHeight) {
- return this.previewRegionHeight / (this.previewDocNativeHeight() ? this.previewDocNativeHeight() : this.previewRegionHeight);
- }
- return wscale;
+ return selected ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
}
- private previewPanelWidth = () => this.previewDocNativeWidth() * this.previewContentScaling();
- private previewPanelHeight = () => this.previewDocNativeHeight() * this.previewContentScaling();
- get previewPanelCenteringOffset() { return (this.previewRegionWidth - this.previewDocNativeWidth() * this.previewContentScaling()) / 2; }
+
getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
- - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth - this.previewPanelCenteringOffset,
- - this.borderWidth).scale(1 / this.previewContentScaling())
+ - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth, - this.borderWidth);
- @computed
- get previewPanel() {
- // let doc = CompileScript(this.previewScript, { this: selected }, true)();
- const previewDoc = this.previewDocument;
- 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}
- addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
- ScreenToLocalTransform={this.getPreviewTransform}
- ContentScaling={this.previewContentScaling}
- PanelWidth={this.previewPanelWidth} PanelHeight={this.previewPanelHeight}
- ContainingCollectionView={this.props.CollectionView}
- focus={emptyFunction}
- parentActive={this.props.active}
- whenActiveChanged={this.props.whenActiveChanged}
- bringToFront={emptyFunction}
- />
- </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() {
const docs = DocListCast(this.props.Document[this.props.fieldKey]);
@@ -334,7 +315,7 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<div id="schema-options-header"><h5><b>Options</b></h5></div>
<div id="options-flyout-div">
<h6 className="schema-options-subHeader">Preview Window</h6>
- <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.splitPercentage !== 0} onChange={this.toggleExpander} /> Show Preview </div>
+ <div id="preview-schema-checkbox-div"><input type="checkbox" key={"Show Preview"} checked={this.previewWidth() !== 0} onChange={this.toggleExpander} /> Show Preview </div>
<h6 className="schema-options-subHeader" >Displayed Columns</h6>
<ul id="schema-col-checklist" >
{this.documentKeysCheckList}
@@ -349,33 +330,128 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
@computed
+ get reactTable() {
+ trace();
+ let previewWidth = this.previewWidth() + 2 * this.borderWidth + this.DIVIDER_WIDTH + 1;
+ return <ReactTable style={{ position: "relative", float: "left", width: `calc(100% - ${previewWidth}px` }} data={this.childDocs} page={0} pageSize={this.childDocs.length} showPagination={false}
+ columns={this.tableColumns}
+ column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
+ getTrProps={this.getTrProps}
+ />
+ }
+
+ @computed
get dividerDragger() {
- return this.splitPercentage === 0 ? (null) :
+ return this.previewWidth() === 0 ? (null) :
<div className="collectionSchemaView-dividerDragger" onPointerDown={this.onDividerDown} style={{ width: `${this.DIVIDER_WIDTH}px` }} />;
}
+ @computed
+ get previewPanel() {
+ trace();
+ return <CollectionSchemaPreview
+ Document={this.previewDocument}
+ width={this.previewWidth}
+ height={this.previewHeight}
+ getTransform={this.getPreviewTransform}
+ CollectionView={this.props.CollectionView}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ active={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ addDocTab={this.props.addDocTab}
+ setPreviewScript={this.setPreviewScript}
+ previewScript={this.previewScript}
+ />
+ }
+ @action
+ setPreviewScript = (script: string) => {
+ this.previewScript = script;
+ }
+
render() {
- library.add(faCog);
- library.add(faPlus);
- const children = this.children;
+ trace();
return (
<div className="collectionSchemaView-container" onPointerDown={this.onPointerDown} onWheel={this.onWheel}
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
- }))}
- column={{ ...ReactTableDefaults.column, Cell: this.renderCell, }}
- getTrProps={this.getTrProps}
- />
- </div>
+ {this.reactTable}
{this.dividerDragger}
- {this.previewPanel}
+ {!this.previewWidth() ? (null) : this.previewPanel}
{this.tableOptionsPanel}
</div>
);
}
+}
+interface CollectionSchemaPreviewProps {
+ Document?: Doc;
+ width: () => number;
+ height: () => number;
+ CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+ getTransform: () => Transform;
+ addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+ removeDocument: (document: Doc) => boolean;
+ active: () => boolean;
+ whenActiveChanged: (isActive: boolean) => void;
+ addDocTab: (document: Doc, where: string) => void;
+ setPreviewScript: (script: string) => void;
+ previewScript?: string;
+}
+
+@observer
+export class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewProps>{
+ private get nativeWidth() { return NumCast(this.props.Document!.nativeWidth, this.props.width()); }
+ private get nativeHeight() { return NumCast(this.props.Document!.nativeHeight, this.props.height()); }
+ private contentScaling = () => {
+ let wscale = this.props.width() / (this.nativeWidth ? this.nativeWidth : this.props.width());
+ if (wscale * this.nativeHeight > this.props.height()) {
+ return this.props.height() / (this.nativeHeight ? this.nativeHeight : this.props.height());
+ }
+ return wscale;
+ }
+ private PanelWidth = () => this.nativeWidth * this.contentScaling();
+ private PanelHeight = () => this.nativeHeight * this.contentScaling();
+ private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling())
+ get centeringOffset() { return (this.props.width() - this.nativeWidth * this.contentScaling()) / 2; }
+ @action
+ onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.props.setPreviewScript(e.currentTarget.value);
+ }
+ @undoBatch
+ @action
+ public collapseToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => {
+ SelectionManager.DeselectAll();
+ if (expandedDocs) {
+ let isMinimized: boolean | undefined;
+ expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
+ if (isMinimized === undefined) {
+ isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+ }
+ maximizedDoc.isMinimized = !isMinimized;
+ });
+ }
+ }
+ render() {
+ let input = this.props.previewScript === undefined ? (null) :
+ <input className="collectionSchemaView-input" value={this.props.previewScript} onChange={this.onPreviewScriptChange}
+ style={{ left: `calc(50% - ${Math.min(75, (this.props.Document ? this.PanelWidth() / 2 : 75))}px)` }} />;
+ return (<div className="collectionSchemaView-previewRegion" style={{ width: this.props.width(), height: "100%" }}>
+ {!this.props.Document || !this.props.width ? (null) : (
+ <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)`, height: "100%" }}>
+ <DocumentView Document={this.props.Document} isTopMost={false} selectOnLoad={false}
+ addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
+ ScreenToLocalTransform={this.getTransform}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight}
+ ContainingCollectionView={this.props.CollectionView}
+ focus={emptyFunction}
+ parentActive={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ bringToFront={emptyFunction}
+ addDocTab={this.props.addDocTab}
+ collapseToPoint={this.collapseToPoint}
+ />
+ </div>)}
+ {input}
+ </div>);
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
new file mode 100644
index 000000000..af194aec9
--- /dev/null
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -0,0 +1,41 @@
+@import "../globalCssVariables";
+.collectionStackingView {
+ overflow-y: auto;
+
+ .collectionStackingView-docView-container {
+ width: 45%;
+ margin: 5% 2.5%;
+ padding-left: 2.5%;
+ height: auto;
+ }
+
+ .collectionStackingView-flexCont {
+ display: flex;
+ flex-direction: row;
+ flex-wrap: wrap;
+ align-items: center;
+ }
+
+ .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid{
+ width:100%;
+ height:100%;
+ position: absolute;
+ display:grid;
+ top: 0;
+ left: 0;
+ width: 100%;
+ position: absolute;
+
+ }
+
+ .collectionStackingView-description {
+ font-size: 100%;
+ margin-bottom: 1vw;
+ padding: 10px;
+ height: 2vw;
+ width: 100%;
+ font-family: $sans-serif;
+ background: $dark-color;
+ color: $light-color;
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
new file mode 100644
index 000000000..cad7cd50c
--- /dev/null
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -0,0 +1,185 @@
+import React = require("react");
+import { action, computed, IReactionDisposer, reaction, trace } from "mobx";
+import { observer } from "mobx-react";
+import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { BoolCast, NumCast } from "../../../new_fields/Types";
+import { emptyFunction, returnOne, Utils } from "../../../Utils";
+import { SelectionManager } from "../../util/SelectionManager";
+import { undoBatch } from "../../util/UndoManager";
+import { DocumentView } from "../nodes/DocumentView";
+import { CollectionSchemaPreview } from "./CollectionSchemaView";
+import "./CollectionStackingView.scss";
+import { CollectionSubView } from "./CollectionSubView";
+import { ContextMenu } from "../ContextMenu";
+
+@observer
+export class CollectionStackingView extends CollectionSubView(doc => doc) {
+ _masonryGridRef: HTMLDivElement | null = null;
+ _heightDisposer?: IReactionDisposer;
+ _gridSize = 1;
+ @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); }
+ @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); }
+ @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); }
+ @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); }
+ @computed get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 2 * this.xMargin : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); }
+
+ singleColDocHeight(d: Doc) {
+ let nw = NumCast(d.nativeWidth);
+ let nh = NumCast(d.nativeHeight);
+ let aspect = nw && nh ? nh / nw : 1;
+ let wid = Math.min(d[WidthSym](), this.columnWidth);
+ return (nw && nh) ? wid * aspect : d[HeightSym]();
+ }
+ componentDidMount() {
+ this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
+ () => {
+ if (this.singleColumn) {
+ let children = this.childDocs.filter(d => !d.isMinimized);
+ this.props.Document.height = children.reduce((height, d, i) =>
+ height + this.singleColDocHeight(d) + (i === children.length - 1 ? this.yMargin : this.gridGap)
+ , this.yMargin);
+ }
+ }, { fireImmediately: true });
+ }
+ componentWillUnmount() {
+ if (this._heightDisposer) this._heightDisposer();
+ }
+
+ @action
+ moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
+ this.props.removeDocument(doc);
+ addDocument(doc);
+ return true;
+ }
+ getDocTransform(doc: Doc, dref: HTMLDivElement) {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
+ let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
+ let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+ return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.columnWidth);
+ }
+ createRef = (ele: HTMLDivElement | null) => {
+ this._masonryGridRef = ele;
+ this.createDropTarget(ele!);
+ }
+ @undoBatch
+ @action
+ public collapseToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => {
+ SelectionManager.DeselectAll();
+ if (expandedDocs) {
+ let isMinimized: boolean | undefined;
+ expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => {
+ if (isMinimized === undefined) {
+ isMinimized = BoolCast(maximizedDoc.isMinimized, false);
+ }
+ maximizedDoc.isMinimized = !isMinimized;
+ });
+ }
+ }
+
+ @computed
+ get singleColumnChildren() {
+ let children = this.childDocs.filter(d => !d.isMinimized);
+ return children.map((d, i) => {
+ let dref = React.createRef<HTMLDivElement>();
+ let script = undefined;
+ let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
+ let rowHeight = () => this.singleColDocHeight(d);
+ let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]());
+ return <div className="collectionStackingView-masonryDoc"
+ key={d[Id]}
+ ref={dref}
+ style={{ width: colWidth(), height: rowHeight(), marginLeft: "auto", marginRight: "auto" }} >
+ <CollectionSchemaPreview
+ Document={d}
+ width={colWidth}
+ height={rowHeight}
+ getTransform={dxf}
+ CollectionView={this.props.CollectionView}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ active={this.props.active}
+ whenActiveChanged={this.props.whenActiveChanged}
+ addDocTab={this.props.addDocTab}
+ setPreviewScript={emptyFunction}
+ previewScript={script}>
+ </CollectionSchemaPreview>
+ </div>;
+ });
+ }
+ @computed
+ get children() {
+ return this.childDocs.filter(d => !d.isMinimized).map((d, i) => {
+ let dref = React.createRef<HTMLDivElement>();
+ let dxf = () => this.getDocTransform(d, dref.current!);
+ let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this._gridSize + this.gridGap));
+ let childFocus = (doc: Doc) => {
+ doc.libraryBrush = true;
+ this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered.
+ }
+ return (<div className="collectionStackingView-masonryDoc"
+ key={d[Id]}
+ ref={dref}
+ style={{
+ width: NumCast(d.nativeWidth, d[WidthSym]()),
+ height: NumCast(d.nativeHeight, d[HeightSym]()),
+ transformOrigin: "top left",
+ gridRowEnd: `span ${rowSpan}`,
+ gridColumnEnd: `span 1`,
+ transform: `scale(${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())}, ${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())})`
+ }} >
+ <DocumentView key={d[Id]} Document={d}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ moveDocument={this.moveDocument}
+ ContainingCollectionView={this.props.CollectionView}
+ isTopMost={false}
+ ScreenToLocalTransform={dxf}
+ focus={childFocus}
+ ContentScaling={returnOne}
+ PanelWidth={d[WidthSym]}
+ PanelHeight={d[HeightSym]}
+ selectOnLoad={false}
+ parentActive={this.props.active}
+ addDocTab={this.props.addDocTab}
+ bringToFront={emptyFunction}
+ whenActiveChanged={this.props.whenActiveChanged}
+ collapseToPoint={this.collapseToPoint}
+ />
+ </div>);
+ })
+ }
+ 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: "Toggle multi-column",
+ event: () => this.props.Document.singleColumn = !BoolCast(this.props.Document.singleColumn, true), icon: "file-pdf"
+ });
+ }
+ }
+ render() {
+ let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.childDocs.filter(d => !d.isMinimized).length,
+ Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap))));
+ let templatecols = "";
+ for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `;
+ return (
+ <div className="collectionStackingView">
+ <div className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`}
+ onContextMenu={this.onContextMenu}
+ ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()}
+ style={{
+ padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`,
+ margin: "auto",
+ width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`,
+ height: "100%",
+ position: "relative",
+ gridGap: this.gridGap,
+ gridTemplateColumns: this.singleColumn ? undefined : templatecols,
+ gridAutoRows: this.singleColumn ? undefined : `${this._gridSize}px`
+ }}
+ >
+ {this.singleColumn ? this.singleColumnChildren : this.children}
+ </div></div>
+ );
+ }
+} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index ffd3e0659..762955a08 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,24 +1,24 @@
-import { action, runInAction } from "mobx";
-import React = require("react");
-import { undoBatch, UndoManager } from "../../util/UndoManager";
-import { DragManager } from "../../util/DragManager";
-import { Docs, DocumentOptions } from "../../documents/Documents";
-import { RouteStore } from "../../../server/RouteStore";
+import { action } from "mobx";
+import * as rp from 'request-promise';
+import CursorField from "../../../new_fields/CursorField";
+import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { Cast, PromiseValue } from "../../../new_fields/Types";
import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils";
+import { RouteStore } from "../../../server/RouteStore";
+import { DocServer } from "../../DocServer";
+import { Docs, DocumentOptions } from "../../documents/Documents";
+import { DragManager } from "../../util/DragManager";
+import { undoBatch, UndoManager } from "../../util/UndoManager";
+import { DocComponent } from "../DocComponent";
import { FieldViewProps } from "../nodes/FieldView";
-import * as rp from 'request-promise';
-import { CollectionView } from "./CollectionView";
import { CollectionPDFView } from "./CollectionPDFView";
import { CollectionVideoView } from "./CollectionVideoView";
-import { Doc, Opt, FieldResult, DocListCast } from "../../../new_fields/Doc";
-import { DocComponent } from "../DocComponent";
-import { listSpec } from "../../../new_fields/Schema";
-import { Cast, PromiseValue, FieldValue, ListSpec } from "../../../new_fields/Types";
-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";
+import { CollectionView } from "./CollectionView";
+import React = require("react");
+import { FormattedTextBox } from "../nodes/FormattedTextBox";
+import { Id } from "../../../new_fields/FieldSymbols";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -47,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]);
@@ -72,7 +72,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
if (cursors.length > 0 && (ind = cursors.findIndex(entry => entry.data.metadata.id === id)) > -1) {
cursors[ind].setPosition(pos);
} else {
- let entry = new CursorField({ metadata: { id: id, identifier: email }, position: pos });
+ let entry = new CursorField({ metadata: { id: id, identifier: email, timestamp: Date.now() }, position: pos });
cursors.push(entry);
}
}
@@ -82,28 +82,15 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@action
protected drop(e: Event, de: DragManager.DropEvent): boolean {
if (de.data instanceof DragManager.DocumentDragData) {
- if (de.data.dropAction || de.data.userDropAction) {
- ["width", "height", "curPage"].map(key =>
- de.data.draggedDocuments.map((draggedDocument: Doc, i: number) =>
- PromiseValue(Cast(draggedDocument[key], "number")).then(f => f && (de.data.droppedDocuments[i][key] = f))));
- }
let added = false;
if (de.data.dropAction || de.data.userDropAction) {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => {
- let moved = this.props.addDocument(d);
- return moved || added;
- }, false);
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
} else if (de.data.moveDocument) {
- const move = de.data.moveDocument;
- added = de.data.droppedDocuments.reduce((added: boolean, d) => {
- let moved = move(d, this.props.Document, this.props.addDocument);
- return moved || added;
- }, false);
+ let movedDocs = de.data.options === this.props.Document[Id] ? de.data.draggedDocuments : de.data.droppedDocuments;
+ added = movedDocs.reduce((added: boolean, d) =>
+ de.data.moveDocument(d, this.props.Document, this.props.addDocument) || added, false);
} else {
- added = de.data.droppedDocuments.reduce((added: boolean, d) => {
- let moved = this.props.addDocument(d);
- return moved || added;
- }, false);
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d) || added, false);
}
e.stopPropagation();
return added;
@@ -131,7 +118,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
options.dropAction = "copy";
}
if (type.indexOf("html") !== -1) {
- if (path.includes('localhost')) {
+ if (path.includes(window.location.hostname)) {
let s = path.split('/');
let id = s[s.length - 1];
DocServer.GetRefField(id).then(field => {
@@ -155,6 +142,10 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@undoBatch
@action
protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
+ if (e.ctrlKey) {
+ e.stopPropagation(); // bcz: this is a hack to stop propagation when dropping an image on a text document with shift+ctrl
+ return;
+ }
let html = e.dataTransfer.getData("text/html");
let text = e.dataTransfer.getData("text/plain");
@@ -164,6 +155,25 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
e.stopPropagation();
e.preventDefault();
+ if (html && FormattedTextBox.IsFragment(html)) {
+ let href = FormattedTextBox.GetHref(html);
+ if (href) {
+ let docid = FormattedTextBox.GetDocFromUrl(href);
+ if (docid) { // prosemirror text containing link to dash document
+ DocServer.GetRefField(docid).then(f => {
+ if (f instanceof Doc) {
+ if (options.x || options.y) { f.x = options.x; f.y = options.y; } // should be in CollectionFreeFormView
+ (f instanceof Doc) && this.props.addDocument(f, false);
+ }
+ });
+ } else {
+ this.props.addDocument && this.props.addDocument(Docs.WebDocument(href, options));
+ }
+ } else if (text) {
+ this.props.addDocument && this.props.addDocument(Docs.TextDocument({ ...options, documentText: "@@@" + text }), false);
+ }
+ return;
+ }
if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
let htmlDoc = Docs.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
this.props.addDocument(htmlDoc, false);
@@ -210,7 +220,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}).then(async (res: Response) => {
(await res.json()).map(action((file: any) => {
let path = window.location.origin + file;
- let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 600, width: 300, title: dropFileName });
+ let docPromise = this.getDocumentFromType(type, path, { ...options, nativeWidth: 300, width: 300, title: dropFileName });
docPromise.then(doc => doc && this.props.addDocument(doc));
}));
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 411d67ff7..2dc4b2e80 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -19,37 +19,20 @@
padding-left: 20px;
}
- li {
- margin: 5px 0;
- }
-
.no-indent {
padding-left: 0;
}
.bullet {
- float:left;
+ float: left;
position: relative;
width: 15px;
display: block;
color: $intermediate-color;
- margin-top: 3px;
- transform: scale(1.3,1.3);
- }
-
- .docContainer {
- margin-left: 10px;
- display: block;
- // width:100%;//width: max-content;
- }
- .docContainer:hover {
- .treeViewItem-openRight {
- display:inline;
- }
+ margin-top: 8px;
+ transform: scale(1.3, 1.3);
}
-
-
.editableView-container {
font-weight: bold;
}
@@ -61,10 +44,8 @@
// margin-top: 3px;
display: inline;
}
- .treeViewItem-openRight {
- margin-left: 5px;
- display:none;
- }
+
+
.docContainer:hover {
.delete-button {
display: inline;
@@ -73,16 +54,56 @@
}
.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;
}
+}
+
+.docContainer {
+ margin-left: 10px;
+ display: block;
+ // width:100%;//width: max-content;
+
+ .treeViewItem-openRight {
+ margin-left: 5px;
+ display: none;
+ }
+}
+#docContainer-data {
+ margin-top: 5px;
+}
+
+.docContainer:hover {
+ .treeViewItem-openRight {
+ display: inline-block;
+ height:13px;
+ // display: inline;
+ svg {
+ display:block;
+ padding:0px;
+ margin: 0px;
+ }
+ }
+}
+
+.treeViewItem-header {
+ border: transparent 1px solid;
+}
+.treeViewItem-header-above {
+ border: transparent 1px solid;
+ border-top: black 1px solid;
+}
+.treeViewItem-header-below {
+ border: transparent 1px solid;
+ border-bottom: black 1px solid;
+}
+.treeViewItem-header-inside {
+ border: transparent 1px solid;
+ border: black 1px solid;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index 70c09d97c..8a6764c58 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,25 +1,26 @@
import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretDown, faCaretRight, faTrashAlt, faAngleRight } from '@fortawesome/free-solid-svg-icons';
+import { faAngleRight, faCaretDown, faCaretRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
import { action, observable, trace } from "mobx";
import { observer } from "mobx-react";
-import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
+import { Doc, DocListCast } from '../../../new_fields/Doc';
+import { Id } from '../../../new_fields/FieldSymbols';
+import { Document, listSpec } from '../../../new_fields/Schema';
+import { BoolCast, Cast, NumCast, StrCast, PromiseValue } from '../../../new_fields/Types';
+import { Docs } from '../../documents/Documents';
+import { DocumentManager } from '../../util/DocumentManager';
+import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
+import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
import { EditableView } from "../EditableView";
+import { MainView } from '../MainView';
+import { CollectionViewType } from './CollectionBaseView';
+import { CollectionDockingView } from './CollectionDockingView';
import { CollectionSubView } from "./CollectionSubView";
import "./CollectionTreeView.scss";
import React = require("react");
-import { Document, listSpec } from '../../../new_fields/Schema';
-import { Cast, StrCast, BoolCast, FieldValue } from '../../../new_fields/Types';
-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 { Transform } from '../../util/Transform';
+import { SelectionManager } from '../../util/SelectionManager';
export interface TreeViewProps {
@@ -27,6 +28,12 @@ export interface TreeViewProps {
deleteDoc: (doc: Doc) => void;
moveDocument: DragManager.MoveFunction;
dropAction: "alias" | "copy" | undefined;
+ addDocTab: (doc: Doc, where: string) => void;
+ addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
+ ScreenToLocalTransform: () => Transform;
+ treeViewId: string;
+ parentKey: string;
+ active: () => boolean;
}
export enum BulletType {
@@ -45,23 +52,49 @@ library.add(faCaretRight);
* Component that takes in a document prop and a boolean whether it's collapsed or not.
*/
class TreeView extends React.Component<TreeViewProps> {
+ private _header?: React.RefObject<HTMLDivElement> = React.createRef();
+ private treedropDisposer?: DragManager.DragDropDisposer;
+ protected createTreeDropTarget = (ele: HTMLDivElement) => {
+ this.treedropDisposer && this.treedropDisposer();
+ if (ele) {
+ this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } });
+ }
+ }
+ @observable _isOver: boolean = false;
@observable _collapsed: boolean = true;
@undoBatch delete = () => this.props.deleteDoc(this.props.document);
+ @undoBatch openRight = async () => this.props.addDocTab(this.props.document, "openRight");
- @undoBatch openRight = async () => {
- if (this.props.document.dockingConfig) {
- Main.Instance.openWorkspace(this.props.document);
- } else {
- CollectionDockingView.Instance.AddRightSplit(this.props.document);
- }
- };
+ @action onMouseEnter = () => { this._isOver = true; }
+ @action onMouseLeave = () => { this._isOver = false; }
- get children() {
- return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed? .filter(doc => FieldValue(doc));
+ onPointerEnter = (e: React.PointerEvent): void => {
+ this.props.active() && (this.props.document.libraryBrush = true);
+ if (e.buttons === 1) {
+ this._header!.current!.className = "treeViewItem-header";
+ document.addEventListener("pointermove", this.onDragMove, true);
+ }
+ }
+ onPointerLeave = (e: React.PointerEvent): void => {
+ this.props.document.libraryBrush = false;
+ this._header!.current!.className = "treeViewItem-header";
+ document.removeEventListener("pointermove", this.onDragMove, true);
+ }
+ onDragMove = (e: PointerEvent): void => {
+ this.props.document.libraryBrush = false;
+ let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY);
+ let rect = this._header!.current!.getBoundingClientRect();
+ let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
+ let before = x[1] < bounds[1];
+ let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible);
+ this._header!.current!.className = "treeViewItem-header"
+ if (inside && this._bulletType != BulletType.List) this._header!.current!.className = "treeViewItem-header-inside";
+ else if (before) this._header!.current!.className = "treeViewItem-header-above";
+ else if (!before) this._header!.current!.className = "treeViewItem-header-below";
+ e.stopPropagation();
}
-
onPointerDown = (e: React.PointerEvent) => {
e.stopPropagation();
}
@@ -69,19 +102,17 @@ class TreeView extends React.Component<TreeViewProps> {
@action
remove = (document: Document, key: string) => {
let children = Cast(this.props.document[key], listSpec(Doc), []);
- if (children) {
- children.splice(children.indexOf(document), 1);
- }
+ children.indexOf(document) !== -1 && children.splice(children.indexOf(document), 1);
}
@action
- move: DragManager.MoveFunction = (document, target, addDoc) => {
- if (this.props.document === target) {
- return true;
+ move: DragManager.MoveFunction = (document: Doc, target: Doc, addDoc) => {
+ if (this.props.document !== target) {
+ //TODO This should check if it was removed
+ this.props.deleteDoc(document);
+ return addDoc(document);
}
- //TODO This should check if it was removed
- this.remove(document, "data");
- return addDoc(document);
+ return true;
}
renderBullet(type: BulletType) {
@@ -93,26 +124,16 @@ class TreeView extends React.Component<TreeViewProps> {
}
return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;
}
-
- @action
- onMouseEnter = () => {
- this._isOver = true;
- }
- @observable _isOver: boolean = false;
- @action
- onMouseLeave = () => {
- this._isOver = false;
- }
/**
* Renders the EditableView title element for placement into the tree.
*/
renderTitle() {
let reference = React.createRef<HTMLDivElement>();
- let onItemDown = SetupDrag(reference, () => this.props.document, this.props.moveDocument, this.props.dropAction);
+ let onItemDown = SetupDrag(reference, () => this.props.document, this.move, this.props.dropAction, this.props.treeViewId, true);
let editableView = (titleString: string) =>
(<EditableView
oneLine={!this._isOver ? true : false}
- display={"block"}
+ display={"inline"}
contents={titleString}
height={36}
GetValue={() => StrCast(this.props.document.title)}
@@ -122,16 +143,19 @@ class TreeView extends React.Component<TreeViewProps> {
return true;
}}
/>);
- let dataDocs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []);
+ let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc), []) : [];
let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
<div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
<FontAwesomeIcon icon="angle-right" size="lg" />
- <FontAwesomeIcon icon="angle-right" size="lg" />
+ {/* <FontAwesomeIcon icon="angle-right" size="lg" /> */}
</div>);
return (
- <div className="docContainer" ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
- style={{ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0" }}
- onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+ <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown} onMouseEnter={this.onMouseEnter} onMouseLeave={this.onMouseLeave}
+ style={{
+ background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0",
+ pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
+ }}
+ >
{editableView(StrCast(this.props.document.title))}
{openRight}
{/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
@@ -139,29 +163,64 @@ 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 Right", event: () => CollectionDockingView.Instance.AddRightSplit(this.props.document) });
- ContextMenu.Instance.addItem({
- description: "Open Fields", event: () => CollectionDockingView.Instance.AddRightSplit(Docs.KVPDocument(this.props.document,
- { title: this.props.document.title + ".kvp", width: 300, height: 300 }))
- });
- if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
- ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
+ if (!e.isPropagationStopped()) { // 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(() => MainView.Instance.openWorkspace(this.props.document)) });
+ ContextMenu.Instance.addItem({ description: "Open Fields", event: () => this.props.addDocTab(Docs.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" });
+ if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
+ ContextMenu.Instance.addItem({ description: "Open Tab", event: () => this.props.addDocTab(this.props.document, "inTab"), icon: "folder" });
+ ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, "onRight"), icon: "caret-square-right" });
+ if (DocumentManager.Instance.getDocumentViews(this.props.document).length) {
+ ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.props.document).map(view => view.props.focus(this.props.document)) });
+ }
+ ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+ } else {
+ ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+ }
+ ContextMenu.Instance.displayMenu(e.pageX - 156, e.pageY - 15);
+ e.stopPropagation();
+ }
+ }
+ treeDrop = (e: Event, de: DragManager.DropEvent) => {
+ let x = this.props.ScreenToLocalTransform().transformPoint(de.x, de.y);
+ let rect = this._header!.current!.getBoundingClientRect();
+ let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2);
+ let before = x[1] < bounds[1];
+ let inside = x[0] > bounds[0] + 75 || (!before && this._bulletType === BulletType.Collapsible);
+ if (de.data instanceof DragManager.DocumentDragData) {
+ let addDoc = (doc: Doc) => this.props.addDocument(doc, this.props.document, before);
+ if (inside) {
+ let docList = Cast(this.props.document.data, listSpec(Doc));
+ if (docList !== undefined) {
+ addDoc = (doc: Doc) => { docList && docList.push(doc); return true; }
+ }
+ }
+ let added = false;
+ if (de.data.dropAction || de.data.userDropAction) {
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before) || added, false);
+ } else if (de.data.moveDocument) {
+ let movedDocs = de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments;
+ added = movedDocs.reduce((added: boolean, d) =>
+ de.data.moveDocument(d, this.props.document, addDoc) || added, false);
+ } else {
+ added = de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.props.document, before), false);
}
- ContextMenu.Instance.addItem({
- description: "Delete", event: undoBatch(() => {
- this.props.deleteDoc(this.props.document);
- })
- });
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
e.stopPropagation();
+ return added;
}
+ return false;
}
- onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; };
- onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; };
+ public static AddDocToList(target: Doc, key: string, doc: Doc, relativeTo?: Doc, before?: boolean) {
+ let list = Cast(target[key], listSpec(Doc));
+ if (list) {
+ let ind = relativeTo ? list.indexOf(relativeTo) : -1;
+ if (ind === -1) list.push(doc);
+ else list.splice(before ? ind : ind + 1, 0, doc);
+ }
+ return true;
+ }
+ _bulletType: BulletType = BulletType.List;
render() {
let bulletType = BulletType.List;
let contentElement: (JSX.Element | null)[] = [];
@@ -171,68 +230,102 @@ class TreeView extends React.Component<TreeViewProps> {
while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
}
keys.map(key => {
- let docList = DocListCast(this.props.document[key]);
+ let docList = Cast(this.props.document[key], listSpec(Doc));
+ let remDoc = (doc: Doc) => this.remove(doc, key);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.document, key, doc, addBefore, before);
let doc = Cast(this.props.document[key], Doc);
- if (doc instanceof Doc || docList.length) {
+ if (doc instanceof Doc || docList) {
if (!this._collapsed) {
bulletType = BulletType.Collapsible;
- let spacing = (key === "data") ? 0 : -10;
contentElement.push(<ul key={key + "more"}>
{(key === "data") ? (null) :
<span className="collectionTreeView-keyHeader" style={{ display: "block", marginTop: "7px" }} key={key}>{key}</span>}
- <div style={{ display: "block", marginTop: `${spacing}px` }}>
- {TreeView.GetChildElements(doc instanceof Doc ? [doc] : docList, key !== "data", (doc: Doc) => this.remove(doc, key), this.move, this.props.dropAction)}
+ <div style={{ display: "block" }}>
+ {TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, key, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active)}
</div>
</ul >);
- } else
+ } else {
bulletType = BulletType.Collapsed;
+ }
}
});
+ this._bulletType = bulletType;
return <div className="treeViewItem-container"
+ ref={this.createTreeDropTarget}
onContextMenu={this.onWorkspaceContextMenu}>
<li className="collection-child">
- {this.renderBullet(bulletType)}
- {this.renderTitle()}
+ <div className="treeViewItem-header" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+ {this.renderBullet(bulletType)}
+ {this.renderTitle()}
+ </div>
{contentElement}
</li>
</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} />);
+ public static GetChildElements(
+ docs: Doc[],
+ treeViewId: string,
+ key: string,
+ add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean,
+ remove: ((doc: Doc) => void),
+ move: DragManager.MoveFunction,
+ dropAction: dropActionType,
+ addDocTab: (doc: Doc, where: string) => void,
+ screenToLocalXf: () => Transform,
+ active: () => boolean
+ ) {
+ return docs.filter(child => !child.excludeFromLibrary && (key !== "data" || !child.isMinimized)).map(child =>
+ <TreeView document={child} treeViewId={treeViewId} key={child[Id]} deleteDoc={remove} addDocument={add} moveDocument={move}
+ dropAction={dropAction} addDocTab={addDocTab} ScreenToLocalTransform={screenToLocalXf} parentKey={key} active={active} />);
}
}
@observer
export class CollectionTreeView extends CollectionSubView(Document) {
+ private treedropDisposer?: DragManager.DragDropDisposer;
+ protected createTreeDropTarget = (ele: HTMLDivElement) => {
+ if (this.treedropDisposer) {
+ this.treedropDisposer();
+ }
+ if (ele) {
+ this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+
@action
remove = (document: Document) => {
- let children = Cast(this.props.Document.data, listSpec(Doc), []);
- if (children) {
- children.splice(children.indexOf(document), 1);
- }
+ let children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
+ children.indexOf(document) !== -1 && children.splice(children.indexOf(document), 1);
}
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()) });
- }
- if (!ContextMenu.Instance.getItems().some(item => item.description === "Delete")) {
- ContextMenu.Instance.addItem({ description: "Delete", event: undoBatch(() => this.remove(this.props.Document)) });
+ // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout
+ if (!e.isPropagationStopped() && this.props.Document.excludeFromLibrary) { // excludeFromLibrary means this is the user document
+ ContextMenu.Instance.addItem({ description: "Create Workspace", event: undoBatch(() => MainView.Instance.createNewWorkspace()) });
+ ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.remove(this.props.Document)) });
}
}
+
+ onTreeDrop = (e: React.DragEvent) => {
+ this.onDrop(e, {});
+ }
render() {
- let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
- if (!this.children) {
+ let dropAction = Cast(this.props.Document.dropAction, "string") as dropActionType;
+ if (!this.childDocs) {
return (null);
}
- let childElements = TreeView.GetChildElements(this.children, false, this.remove, this.props.moveDocument, dropAction);
+ let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => TreeView.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before);
+ let moveDoc = (d: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => this.props.moveDocument(d, target, addDoc);
+ let childElements = TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.fieldKey, addDoc, this.remove,
+ moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.active);
return (
<div id="body" className="collectionTreeView-dropTarget"
style={{ borderRadius: "inherit" }}
onContextMenu={this.onContextMenu}
- onWheel={(e: React.WheelEvent) => e.stopPropagation()}
- onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
+ onWheel={(e: React.WheelEvent) => this.props.isSelected() && e.stopPropagation()}
+ onDrop={this.onTreeDrop}
+ ref={this.createTreeDropTarget}>
<div className="coll-title">
<EditableView
contents={this.props.Document.title}
diff --git a/src/client/views/collections/CollectionVideoView.scss b/src/client/views/collections/CollectionVideoView.scss
index db8b84832..9d2c23d3e 100644
--- a/src/client/views/collections/CollectionVideoView.scss
+++ b/src/client/views/collections/CollectionVideoView.scss
@@ -5,7 +5,7 @@
position: inherit;
top: 0;
left:0;
-
+ z-index: -1;
}
.collectionVideoView-time{
color : white;
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index 16121bb1b..7853544d5 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -1,4 +1,5 @@
import { action, observable, trace } from "mobx";
+import * as htmlToImage from "html-to-image";
import { observer } from "mobx-react";
import { ContextMenu } from "../ContextMenu";
import { CollectionViewType, CollectionBaseView, CollectionRenderProps } from "./CollectionBaseView";
@@ -6,10 +7,14 @@ import React = require("react");
import "./CollectionVideoView.scss";
import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
-import { emptyFunction } from "../../../Utils";
-import { Id } from "../../../new_fields/RefField";
+import { emptyFunction, Utils } from "../../../Utils";
+import { Id } from "../../../new_fields/FieldSymbols";
import { VideoBox } from "../nodes/VideoBox";
-import { NumCast } from "../../../new_fields/Types";
+import { NumCast, Cast, StrCast } from "../../../new_fields/Types";
+import { VideoField } from "../../../new_fields/URLField";
+import { SearchBox } from "../SearchBox";
+import { DocServer } from "../../DocServer";
+import { Docs, DocUtils } from "../../documents/Documents";
@observer
@@ -66,8 +71,44 @@ export class CollectionVideoView 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: "VideoOptions", event: emptyFunction });
}
+
+ let field = Cast(this.props.Document[this.props.fieldKey], VideoField);
+ if (field) {
+ let url = field.url.href;
+ ContextMenu.Instance.addItem({
+ description: "Copy path", event: () => { Utils.CopyText(url); }, icon: "expand-arrows-alt"
+ });
+ }
+ let width = NumCast(this.props.Document.width);
+ let height = NumCast(this.props.Document.height);
+ ContextMenu.Instance.addItem({
+ description: "Take Snapshot", event: async () => {
+ var canvas = document.createElement('canvas');
+ canvas.width = 640;
+ canvas.height = 640 * NumCast(this.props.Document.nativeHeight) / NumCast(this.props.Document.nativeWidth);
+ var ctx = canvas.getContext('2d');//draw image to canvas. scale to target dimensions
+ ctx && ctx.drawImage(this._videoBox!.player!, 0, 0, canvas.width, canvas.height);
+
+ //convert to desired file format
+ var dataUrl = canvas.toDataURL('image/png'); // can also use 'image/png'
+ // if you want to preview the captured image,
+
+ let filename = encodeURIComponent("snapshot" + this.props.Document.title + "_" + this.props.Document.curPage).replace(/\./g, "");
+ SearchBox.convertDataUri(dataUrl, filename).then((returnedFilename) => {
+ if (returnedFilename) {
+ let url = DocServer.prepend(returnedFilename);
+ let imageSummary = Docs.ImageDocument(url, {
+ x: NumCast(this.props.Document.x) + width, y: NumCast(this.props.Document.y),
+ width: 150, height: height / width * 150, title: "--snapshot" + NumCast(this.props.Document.curPage) + " image-"
+ });
+ this.props.addDocument && this.props.addDocument(imageSummary, false);
+ DocUtils.MakeLink(imageSummary, this.props.Document);
+ }
+ });
+ },
+ icon: "expand-arrows-alt"
+ });
}
setVideoBox = (videoBox: VideoBox) => { this._videoBox = videoBox; };
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 55fd2a284..68eefab4c 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -1,15 +1,27 @@
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faProjectDiagram, faSignature, faSquare, faTh, faThList, 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/FieldSymbols';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { observer } from 'mobx-react';
import { undoBatch } from '../../util/UndoManager';
-import { Id } from '../../../new_fields/RefField';
+import { ContextMenu } from "../ContextMenu";
+import { ContextMenuProps } from '../ContextMenuItem';
+import { FieldView, FieldViewProps } from '../nodes/FieldView';
+import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from './CollectionBaseView';
+import { CollectionDockingView } from "./CollectionDockingView";
+import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView';
+import { CollectionSchemaView } from "./CollectionSchemaView";
+import { CollectionStackingView } from './CollectionStackingView';
+import { CollectionTreeView } from "./CollectionTreeView";
+export const COLLECTION_BORDER_WIDTH = 2;
+
+library.add(faTh);
+library.add(faTree);
+library.add(faSquare);
+library.add(faProjectDiagram);
+library.add(faSignature);
+library.add(faThList);
@observer
export class CollectionView extends React.Component<FieldViewProps> {
@@ -21,6 +33,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
case CollectionViewType.Schema: return (<CollectionSchemaView {...props} CollectionView={this} />);
case CollectionViewType.Docking: return (<CollectionDockingView {...props} CollectionView={this} />);
case CollectionViewType.Tree: return (<CollectionTreeView {...props} CollectionView={this} />);
+ case CollectionViewType.Stacking: return (<CollectionStackingView {...props} CollectionView={this} />);
case CollectionViewType.Freeform:
default:
return (<CollectionFreeFormView {...props} CollectionView={this} />);
@@ -32,9 +45,15 @@ 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) });
+ let subItems: ContextMenuProps[] = [];
+ subItems.push({ description: "Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Freeform), icon: "signature" });
+ if (CollectionBaseView.InSafeMode()) {
+ ContextMenu.Instance.addItem({ description: "Test Freeform", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Invalid), icon: "project-diagram" });
+ }
+ subItems.push({ description: "Schema", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Schema), icon: "th-list" });
+ subItems.push({ description: "Treeview", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Tree), icon: "tree" });
+ subItems.push({ description: "Stacking", event: undoBatch(() => this.props.Document.viewType = CollectionViewType.Stacking), icon: "th-list" });
+ ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems });
}
}
diff --git a/src/client/views/collections/ParentDocumentSelector.scss b/src/client/views/collections/ParentDocumentSelector.scss
index f3c605f3e..2dd3e49f2 100644
--- a/src/client/views/collections/ParentDocumentSelector.scss
+++ b/src/client/views/collections/ParentDocumentSelector.scss
@@ -1,8 +1,22 @@
.PDS-flyout {
position: absolute;
z-index: 9999;
- background-color: #d3d3d3;
+ background-color: #eeeeee;
box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
min-width: 150px;
color: black;
+ top: 12px;
+
+ padding: 10px;
+ border-radius: 3px;
+
+ hr {
+ height: 1px;
+ margin: 0px;
+ background-color: gray;
+ border-top: 0px;
+ border-bottom: 0px;
+ border-right: 0px;
+ border-left: 0px;
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 52f7914f3..f11af04a3 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -3,36 +3,64 @@ 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 { Id } from "../../../new_fields/FieldSymbols";
import { SearchUtil } from "../../util/SearchUtil";
import { CollectionDockingView } from "./CollectionDockingView";
+import { NumCast } from "../../../new_fields/Types";
+import { CollectionViewType } from "./CollectionBaseView";
+type SelectorProps = { Document: Doc, addDocTab(doc: Doc, location: string): void };
@observer
-export class SelectorContextMenu extends React.Component<{ Document: Doc }> {
- @observable private _docs: Doc[] = [];
+export class SelectorContextMenu extends React.Component<SelectorProps> {
+ @observable private _docs: { col: Doc, target: Doc }[] = [];
+ @observable private _otherDocs: { col: Doc, target: Doc }[] = [];
- constructor(props: { Document: Doc }) {
+ constructor(props: SelectorProps) {
super(props);
this.fetchDocuments();
}
async fetchDocuments() {
+ let aliases = (await SearchUtil.GetAliasesOfDocument(this.props.Document)).filter(doc => doc !== this.props.Document);
const docs = await SearchUtil.Search(`data_l:"${this.props.Document[Id]}"`, true);
- runInAction(() => this._docs = docs);
+ const map: Map<Doc, Doc> = new Map;
+ const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search(`data_l:"${doc[Id]}"`, true)));
+ allDocs.forEach((docs, index) => docs.forEach(doc => map.set(doc, aliases[index])));
+ docs.forEach(doc => map.delete(doc));
+ runInAction(() => {
+ this._docs = docs.filter(doc => !Doc.AreProtosEqual(doc, CollectionDockingView.Instance.props.Document)).map(doc => ({ col: doc, target: this.props.Document }));
+ this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.Instance.props.Document)).map(([col, target]) => ({ col, target }));
+ });
+ }
+
+ getOnClick({ col, target }: { col: Doc, target: Doc }) {
+ return () => {
+ col = Doc.IsPrototype(col) ? Doc.MakeDelegate(col) : col;
+ if (NumCast(col.viewType, CollectionViewType.Invalid) === CollectionViewType.Freeform) {
+ const newPanX = NumCast(target.x) + NumCast(target.width) / NumCast(target.zoomBasis, 1) / 2;
+ const newPanY = NumCast(target.y) + NumCast(target.height) / NumCast(target.zoomBasis, 1) / 2;
+ col.panX = newPanX;
+ col.panY = newPanY;
+ }
+ this.props.addDocTab(col, "inTab");
+ };
}
render() {
return (
<>
- {this._docs.map(doc => <p><a onClick={() => CollectionDockingView.Instance.AddRightSplit(doc)}>{doc.title}</a></p>)}
+ <p>Contexts:</p>
+ {this._docs.map(doc => <p><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
+ {this._otherDocs.length ? <hr></hr> : null}
+ {this._otherDocs.map(doc => <p><a onClick={this.getOnClick(doc)}>{doc.col.title}</a></p>)}
</>
);
}
}
@observer
-export class ParentDocSelector extends React.Component<{ Document: Doc }> {
+export class ParentDocSelector extends React.Component<SelectorProps> {
@observable hover = false;
@action
@@ -49,13 +77,13 @@ export class ParentDocSelector extends React.Component<{ Document: Doc }> {
let flyout;
if (this.hover) {
flyout = (
- <div className="PDS-flyout">
- <SelectorContextMenu Document={this.props.Document} />
+ <div className="PDS-flyout" title=" ">
+ <SelectorContextMenu {...this.props} />
</div>
);
}
return (
- <span style={{ position: "relative", display: "inline-block" }}
+ <span style={{ position: "relative", display: "inline-block", paddingLeft: "5px", paddingRight: "5px" }}
onMouseEnter={this.onMouseEnter}
onMouseLeave={this.onMouseLeave}>
<p>^</p>
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 737ffba7d..7a0fd2b31 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -10,3 +10,9 @@
transform: translate(10000px,10000px);
pointer-events: all;
}
+.collectionfreeformlinkview-linkText {
+ stroke: rgb(0,0,0);
+ opacity: 0.5;
+ transform: translate(10000px,10000px);
+ pointer-events: all;
+}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 63d2f7642..ba7e6cf9e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
@@ -1,11 +1,10 @@
import { observer } from "mobx-react";
-import { Utils } from "../../../../Utils";
+import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { BoolCast, NumCast, StrCast } from "../../../../new_fields/Types";
+import { InkingControl } from "../../InkingControl";
import "./CollectionFreeFormLinkView.scss";
import React = require("react");
import v5 = require("uuid/v5");
-import { StrCast, NumCast, BoolCast } from "../../../../new_fields/Types";
-import { Doc, WidthSym, HeightSym } from "../../../../new_fields/Doc";
-import { InkingControl } from "../../InkingControl";
export interface CollectionFreeFormLinkViewProps {
A: Doc;
@@ -40,18 +39,25 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let l = this.props.LinkDocs;
let a = this.props.A;
let b = this.props.B;
- let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / 2);
- let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / 2);
- let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / 2);
- let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / 2);
+ let x1 = NumCast(a.x) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.width) / NumCast(a.zoomBasis, 1) / 2);
+ let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : NumCast(a.height) / NumCast(a.zoomBasis, 1) / 2);
+ let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.width) / NumCast(b.zoomBasis, 1) / 2);
+ let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : NumCast(b.height) / NumCast(b.zoomBasis, 1) / 2);
+ let text = "";
+ let first = this.props.LinkDocs[0];
+ if (this.props.LinkDocs.length === 1) text += first.title + (first.linkDescription ? "(" + StrCast(first.linkDescription) + ")" : "");
+ else text = "-multiple-";
return (
<>
- <line key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkLine"
- style={{ strokeWidth: `${l.length / 2}` }}
+ <line key="linkLine" className="collectionfreeformlinkview-linkLine"
+ style={{ strokeWidth: `${2 * l.length / 2}` }}
x1={`${x1}`} y1={`${y1}`}
x2={`${x2}`} y2={`${y2}`} />
- <circle key={Utils.GenerateGuid()} className="collectionfreeformlinkview-linkCircle"
- cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={5} onPointerDown={this.onPointerDown} />
+ {/* <circle key="linkCircle" className="collectionfreeformlinkview-linkCircle"
+ cx={(x1 + x2) / 2} cy={(y1 + y2) / 2} r={8} onPointerDown={this.onPointerDown} /> */}
+ <text key="linkText" textAnchor="middle" className="collectionfreeformlinkview-linkText" x={`${(x1 + x2) / 2}`} y={`${(y1 + y2) / 2}`}>
+ {text}
+ </text>
</>
);
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index d5ce4e1e7..c4dd534ed 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,17 +1,16 @@
-import { computed, IReactionDisposer, reaction, trace } from "mobx";
+import { computed, IReactionDisposer, reaction } from "mobx";
import { observer } from "mobx-react";
-import { Utils } from "../../../../Utils";
+import { Doc, DocListCast } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { List } from "../../../../new_fields/List";
+import { listSpec } from "../../../../new_fields/Schema";
+import { Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
import { DocumentManager } from "../../../util/DocumentManager";
import { DocumentView } from "../../nodes/DocumentView";
import { CollectionViewProps } from "../CollectionSubView";
import "./CollectionFreeFormLinksView.scss";
import { CollectionFreeFormLinkView } from "./CollectionFreeFormLinkView";
import React = require("react");
-import { Doc, DocListCastAsync, DocListCast } from "../../../../new_fields/Doc";
-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";
@observer
export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
@@ -32,8 +31,8 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
let srcTarg = srcDoc;
let x1 = NumCast(srcDoc.x);
let x2 = NumCast(dstDoc.x);
- let x1w = NumCast(srcDoc.width, -1);
- let x2w = NumCast(dstDoc.width, -1);
+ let x1w = NumCast(srcDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1);
+ let x2w = NumCast(dstDoc.width, -1) / NumCast(srcDoc.zoomBasis, 1);
if (x1w < 0 || x2w < 0 || i === j) { }
else {
let findBrush = (field: (Doc | Promise<Doc>)[]) => field.findIndex(brush => {
@@ -60,12 +59,12 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
}
};
}
+ if (dstTarg.brushingDocs === undefined) dstTarg.brushingDocs = new List<Doc>();
+ if (srcTarg.brushingDocs === undefined) srcTarg.brushingDocs = new List<Doc>();
let dstBrushDocs = Cast(dstTarg.brushingDocs, listSpec(Doc), []);
let srcBrushDocs = Cast(srcTarg.brushingDocs, listSpec(Doc), []);
- if (dstBrushDocs === undefined) dstTarg.brushingDocs = dstBrushDocs = new List<Doc>();
- else brushAction(dstBrushDocs);
- if (srcBrushDocs === undefined) srcTarg.brushingDocs = srcBrushDocs = new List<Doc>();
- else brushAction(srcBrushDocs);
+ brushAction(dstBrushDocs);
+ brushAction(srcBrushDocs);
}
});
});
@@ -100,21 +99,26 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
let targetViews = this.documentAnchors(connection.b);
let possiblePairs: { a: Doc, b: Doc, }[] = [];
srcViews.map(sv => targetViews.map(tv => possiblePairs.push({ a: sv.props.Document, b: tv.props.Document })));
- possiblePairs.map(possiblePair =>
- drawnPairs.reduce((found, drawnPair) => {
- let match = (possiblePair.a === drawnPair.a && possiblePair.b === drawnPair.b);
+ possiblePairs.map(possiblePair => {
+ if (!drawnPairs.reduce((found, drawnPair) => {
+ let match1 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.a) && Doc.AreProtosEqual(possiblePair.b, drawnPair.b));
+ let match2 = (Doc.AreProtosEqual(possiblePair.a, drawnPair.b) && Doc.AreProtosEqual(possiblePair.b, drawnPair.a));
+ let match = match1 || match2;
if (match && !drawnPair.l.reduce((found, link) => found || link[Id] === connection.l[Id], false)) {
drawnPair.l.push(connection.l);
}
return match || found;
- }, false)
- ||
- drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
- );
+ }, false)) {
+ drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
+ }
+ });
return drawnPairs;
}, [] as { a: Doc, b: Doc, l: Doc[] }[]);
- return connections.map(c => <CollectionFreeFormLinkView key={Utils.GenerateGuid()} A={c.a} B={c.b} LinkDocs={c.l}
- removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />);
+ return connections.map(c => {
+ let x = c.l.reduce((p, l) => p + l[Id], "");
+ return <CollectionFreeFormLinkView key={x} A={c.a} B={c.b} LinkDocs={c.l}
+ removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />;
+ });
}
render() {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 40ec8a325..3193f5624 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,12 +1,13 @@
import { observer } from "mobx-react";
+import * as mobxUtils from 'mobx-utils';
+import CursorField from "../../../../new_fields/CursorField";
+import { listSpec } from "../../../../new_fields/Schema";
+import { Cast } from "../../../../new_fields/Types";
+import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
import { CollectionViewProps } from "../CollectionSubView";
import "./CollectionFreeFormView.scss";
import React = require("react");
import v5 = require("uuid/v5");
-import { CurrentUserUtils } from "../../../../server/authentication/models/current_user_utils";
-import CursorField from "../../../../new_fields/CursorField";
-import { Cast } from "../../../../new_fields/Types";
-import { listSpec } from "../../../../new_fields/Schema";
@observer
export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
@@ -21,7 +22,9 @@ export class CollectionFreeFormRemoteCursors extends React.Component<CollectionV
let cursors = Cast(doc.cursors, listSpec(CursorField));
- return (cursors || []).filter(cursor => cursor.data.metadata.id !== id);
+ const now = mobxUtils.now();
+ // const now = Date.now();
+ return (cursors || []).filter(cursor => cursor.data.metadata.id !== id && (now - cursor.data.metadata.timestamp) < 1000);
}
private crosshairs?: HTMLCanvasElement;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index 063c9e2cf..e10ba9d7e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -25,6 +25,9 @@
height: 100%;
width: 100%;
}
+ >.jsx-parser {
+ z-index:0;
+ }
//nested freeform views
// .collectionfreeformview-container {
@@ -52,6 +55,10 @@
position: inherit;
height: 100%;
}
+
+ >.jsx-parser {
+ z-index:0;
+ }
.formattedTextBox-cont {
background: $light-color-secondary;
@@ -63,6 +70,7 @@
border-radius: $border-radius;
box-sizing: border-box;
position:absolute;
+ z-index: -1;
.marqueeView {
overflow: hidden;
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index b8bed795d..63f24ff53 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,8 +1,14 @@
-import { action, computed, trace } from "mobx";
+import { action, computed } from "mobx";
import { observer } from "mobx-react";
-import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";
+import { Doc, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { createSchema, makeInterface } from "../../../../new_fields/Schema";
+import { BoolCast, Cast, FieldValue, NumCast } from "../../../../new_fields/Types";
+import { emptyFunction, returnOne } from "../../../../Utils";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from "../../../util/DragManager";
+import { HistoryUtil } from "../../../util/History";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
import { undoBatch } from "../../../util/UndoManager";
@@ -11,6 +17,7 @@ import { InkingCanvas } from "../../InkingCanvas";
import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView";
import { DocumentContentsView } from "../../nodes/DocumentContentsView";
import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
+import { pageSchema } from "../../nodes/ImageBox";
import { CollectionSubView } from "../CollectionSubView";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
@@ -18,13 +25,6 @@ import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
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, 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",
@@ -37,7 +37,6 @@ const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema)
@observer
export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
- public static RIGHT_BTN_DRAG = false;
private _selectOnLoaded: string = ""; // id of document that should be selected once it's loaded (used for click-to-type)
private _lastX: number = 0;
private _lastY: number = 0;
@@ -46,8 +45,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@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 = () => this.Document.panX || 0;
private panY = () => this.Document.panY || 0;
private zoomScaling = () => this.Document.scale || 1;
@@ -66,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;
});
@@ -90,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)) {
@@ -103,7 +102,6 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
this.bringToFront(d);
});
- SelectionManager.ReselectAll();
}
return true;
}
@@ -112,11 +110,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);
@@ -134,20 +128,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerMove = (e: PointerEvent): void => {
if (!e.cancelBubble) {
- let x = this.props.Document.panX || 0;
- let y = this.props.Document.panY || 0;
- 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, 1) + 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, 1) + 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, 1);
let y = NumCast(doc.y);
- let ye = y + NumCast(doc.height);
+ let ye = y + NumCast(doc.height) / NumCast(doc.zoomBasis, 1);
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]]);
@@ -160,12 +154,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
});
}
- let panelwidth = this._pwidth / this.zoomScaling() / 2;
- let panelheight = this._pheight / this.zoomScaling() / 2;
- if (x - dx < ranges[0][0] - panelwidth) x = ranges[0][1] + panelwidth + dx;
- if (x - dx > ranges[0][1] + panelwidth) x = ranges[0][0] - panelwidth + dx;
- if (y - dy < ranges[1][0] - panelheight) y = ranges[1][1] + panelheight + dy;
- if (y - dy > ranges[1][1] + panelheight) y = ranges[1][0] - panelheight + dy;
+ let panelDim = this.props.ScreenToLocalTransform().transformDirection(this._pwidth / this.zoomScaling(),
+ this._pheight / this.zoomScaling());
+ let panelwidth = panelDim[0];
+ let panelheight = panelDim[1];
+ if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2;
+ if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2;
+ if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2;
+ if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2;
}
this.setPan(x - dx, y - dy);
this._lastX = e.pageX;
@@ -180,7 +176,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;
});
@@ -211,7 +207,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();
@@ -220,7 +216,7 @@ 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));
@@ -239,7 +235,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;
@@ -248,7 +244,6 @@ 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;
@@ -270,22 +265,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
SelectionManager.DeselectAll();
- const newPanX = NumCast(doc.x) + NumCast(doc.width) / 2;
- const newPanY = NumCast(doc.y) + NumCast(doc.height) / 2;
+ const newPanX = NumCast(doc.x) + NumCast(doc.width) / NumCast(doc.zoomBasis, 1) / 2;
+ const newPanY = NumCast(doc.y) + NumCast(doc.height) / NumCast(doc.zoomBasis, 1) / 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.props.focus(this.props.Document);
- this.panDisposer = setTimeout(() => this.props.Document.panTransformType = "None", 2000); // wait 3 seconds, then reset to false
}
getDocumentViewProps(document: Doc): DocumentViewProps {
return {
Document: document,
- toggleMinimized: emptyFunction,
addDocument: this.props.addDocument,
removeDocument: this.props.removeDocument,
moveDocument: this.props.moveDocument,
@@ -300,16 +293,17 @@ 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) {
+ if (Math.round(page) === Math.round(curPage) || page === -1) {
let minim = BoolCast(doc.isMinimized, false);
if (minim === undefined || !minim) {
prev.push(<CollectionFreeFormDocumentView key={doc[Id]} {...this.getDocumentViewProps(doc)} />);
@@ -328,7 +322,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
- private childViews = () => [...this.views, <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />];
+ private childViews = () => [
+ <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />,
+ ...this.views
+ ];
render() {
const containerName = `collectionfreeformview${this.isAnnotationOverlay ? "-overlay" : "-container"}`;
const easing = () => this.props.Document.panTransformType === "Ease";
@@ -350,7 +347,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
</CollectionFreeFormViewPannableContents>
</MarqueeView>
- <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} />
+ <CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} />
</div>
);
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 865bae729..c699b3437 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,25 +1,25 @@
import * as htmlToImage from "html-to-image";
import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
+import { Doc } from "../../../../new_fields/Doc";
+import { Id } from "../../../../new_fields/FieldSymbols";
+import { InkField, StrokeData } from "../../../../new_fields/InkField";
+import { List } from "../../../../new_fields/List";
+import { Cast, NumCast } from "../../../../new_fields/Types";
+import { Utils } from "../../../../Utils";
+import { DocServer } from "../../../DocServer";
import { Docs } from "../../../documents/Documents";
import { SelectionManager } from "../../../util/SelectionManager";
import { Transform } from "../../../util/Transform";
-import { undoBatch, UndoManager } from "../../../util/UndoManager";
+import { undoBatch } from "../../../util/UndoManager";
import { InkingCanvas } from "../../InkingCanvas";
import { PreviewCursor } from "../../PreviewCursor";
+import { SearchBox } from "../../SearchBox";
+import { Templates } from "../../Templates";
+import { CollectionViewType } from "../CollectionBaseView";
import { CollectionFreeFormView } from "./CollectionFreeFormView";
import "./MarqueeView.scss";
import React = require("react");
-import { Utils } from "../../../../Utils";
-import { Doc } from "../../../../new_fields/Doc";
-import { NumCast, Cast } from "../../../../new_fields/Types";
-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;
@@ -63,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(";") ||
@@ -80,68 +80,83 @@ 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 === "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
- // assumes that 1st row has header entry for each column
- // assumes subsequent rows have entries for each column header OR
- // any row that has only one column is a section header-- this header is then added as a column to subsequent rows until the next header
- // assumes each cell is a string or a number
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)
- ns.splice(0, 1);
- if (ns.length > 0) {
- let columns = ns[0].split("\t");
- let docList: Doc[] = [];
- let groupAttr: string | number = "";
- for (let i = 1; i < ns.length - 1; i++) {
- let values = ns[i].split("\t");
- if (values.length === 1 && columns.length > 1) {
- groupAttr = values[0];
- continue;
- }
- let doc = new Doc();
- 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.title = i.toString();
- docList.push(doc);
- }
- 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);
+ navigator.clipboard.readText().then(text => {
+ let ns = text.split("\n").filter(t => t.trim() !== "\r" && t.trim() !== "");
+ if (ns.length === 1 && text.startsWith("http")) {
+ this.props.addDocument(Docs.ImageDocument(text, { nativeWidth: 300, width: 300, x: x, y: y }), false);// paste an image from its URL in the paste buffer
+ } else {
+ this.pasteTable(ns, x, y);
}
- })();
- } else {
+ });
+ } else if (!e.ctrlKey) {
let newBox = Docs.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
this.props.addLiveTextDocument(newBox);
}
e.stopPropagation();
}
+ //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
+ // assumes that 1st row has header entry for each column
+ // assumes subsequent rows have entries for each column header OR
+ // any row that has only one column is a section header-- this header is then added as a column to subsequent rows until the next header
+ // assumes each cell is a string or a number
+ pasteTable(ns: string[], x: number, y: number) {
+ 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[] = [];
+ let groupAttr: string | number = "";
+ let rowProto = new Doc();
+ rowProto.title = rowProto.Id;
+ rowProto.width = 200;
+ rowProto.isPrototype = true;
+ for (let i = 1; i < ns.length - 1; i++) {
+ let values = ns[i].split("\t");
+ if (values.length === 1 && columns.length > 1) {
+ groupAttr = values[0];
+ continue;
+ }
+ let docDataProto = Doc.MakeDelegate(rowProto);
+ docDataProto.isPrototype = true;
+ columns.forEach((col, i) => docDataProto[columns[i]] = (values.length > i ? ((values[i].indexOf(Number(values[i]).toString()) !== -1) ? Number(values[i]) : values[i]) : undefined));
+ if (groupAttr) {
+ docDataProto._group = groupAttr;
+ }
+ docDataProto.title = i.toString();
+ let doc = Doc.MakeDelegate(docDataProto);
+ doc.width = 200;
+ docList.push(doc);
+ }
+ 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);
+ }
+ }
@action
onPointerDown = (e: React.PointerEvent): void => {
this._downX = this._lastX = e.pageX;
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(); // bcz: removed so that you can alt-click on button in a collection to switch link following behaviors.
+ 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
@@ -223,27 +238,22 @@ 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 === "S" || e.key === "e" || e.key === "p") {
this._commandExecuted = true;
e.stopPropagation();
+ e.preventDefault();
(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);
@@ -253,42 +263,68 @@ 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: e.key === "s" ? "-summary-" : e.key === "r" ? "-replacement-" : e.key === "p" ? "-summary-" : "a nested collection",
+ title: e.key === "s" || e.key === "S" ? "-summary-" : e.key === "p" ? "-summary-" : "a nested collection",
});
-
+ newCollection.zoomBasis = zoomBasis;
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));
- 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;
+ if (e.key === "s") {
+ 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: "#e2ad32" /* yellow */, title: "-summary-" });
+ newCollection.proto!.summaryDoc = summary;
+ selected = [newCollection];
+ newCollection.x = bounds.left + bounds.width;
+ summary.proto!.subBulletDocs = new List<Doc>(selected);
+ //summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight"
+ summary.templates = new List<string>([Templates.Bullet.Layout]);
+ let container = Docs.FreeformDocument([summary, newCollection], { x: bounds.left, y: bounds.top, width: 300, height: 200, title: "-summary-" });
+ container.viewType = CollectionViewType.Stacking;
+ this.props.addLiveTextDocument(container);
+ // });
+ } else if (e.key === "S") {
+ await htmlToImage.toPng(this._mainCont.current!, { width: bounds.width * zoomBasis, height: bounds.height * zoomBasis, quality: 0.2 }).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: "#e2ad32" /* yellow */, title: "-summary-" });
+ SearchBox.convertDataUri(dataUrl, "icon" + summary[Id] + "_image").then((returnedFilename) => {
+ if (returnedFilename) {
+ let url = DocServer.prepend(returnedFilename);
+ let imageSummary = Docs.ImageDocument(url, {
+ x: bounds.left, y: bounds.top + 100 / zoomBasis,
+ width: 150, height: bounds.height / bounds.width * 150, title: "-summary image-"
+ });
+ summary.imageSummary = imageSummary;
+ this.props.addDocument(imageSummary, false);
+ }
+ })
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);
+ 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);
- SelectionManager.DeselectAll();
this.props.selectDocuments([newCollection]);
}
this.cleanupInteractions(false);
@@ -353,10 +389,10 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
render() {
- let p = this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY);
+ let p: [number, number] = this._visible ? this.props.getContainerTransform().transformPoint(this._downX < this._lastX ? this._downX : this._lastX, this._downY < this._lastY ? this._downY : this._lastY) : [0, 0];
return <div className="marqueeView" style={{ borderRadius: "inherit" }} onClick={this.onClick} onPointerDown={this.onPointerDown}>
<div style={{ position: "relative", transform: `translate(${p[0]}px, ${p[1]}px)` }} >
- {!this._visible ? null : this.marqueeDiv}
+ {this._visible ? this.marqueeDiv : null}
<div ref={this._mainCont} style={{ transform: `translate(${-p[0]}px, ${-p[1]}px)` }} >
{this.props.children}
</div>