aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionBaseView.scss11
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx113
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx653
-rw-r--r--src/client/views/collections/CollectionPDFView.tsx85
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx196
-rw-r--r--src/client/views/collections/CollectionStackingView.scss52
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx283
-rw-r--r--src/client/views/collections/CollectionSubView.tsx191
-rw-r--r--src/client/views/collections/CollectionTreeView.scss120
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx617
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx5
-rw-r--r--src/client/views/collections/CollectionView.tsx16
-rw-r--r--src/client/views/collections/DockedFrameRenderer.tsx116
-rw-r--r--src/client/views/collections/ParentDocumentSelector.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx28
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx128
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx12
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss16
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx306
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx113
22 files changed, 1837 insertions, 1239 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 096c65092..e9a693f55 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -1,14 +1,16 @@
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 } from '../../../new_fields/Doc';
+import { Id } from '../../../new_fields/FieldSymbols';
import { List } from '../../../new_fields/List';
+import { listSpec } from '../../../new_fields/Schema';
+import { BoolCast, Cast, NumCast, PromiseValue } from '../../../new_fields/Types';
+import { DocumentManager } from '../../util/DocumentManager';
import { SelectionManager } from '../../util/SelectionManager';
-import { Id } from '../../../new_fields/FieldSymbols';
+import { ContextMenu } from '../ContextMenu';
+import { FieldViewProps } from '../nodes/FieldView';
+import './CollectionBaseView.scss';
export enum CollectionViewType {
Invalid,
@@ -34,7 +36,6 @@ export interface CollectionViewProps extends FieldViewProps {
contentRef?: React.Ref<HTMLDivElement>;
}
-
@observer
export class CollectionBaseView extends React.Component<CollectionViewProps> {
@observable private static _safeMode = false;
@@ -58,10 +59,12 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
}
}
+ @computed get dataDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) ? this.props.DataDoc ? this.props.DataDoc : this.props.Document : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
+ @computed get dataField() { return this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey; }
+
active = (): boolean => {
var isSelected = this.props.isSelected();
- var topMost = this.props.isTopMost;
- return isSelected || this._isChildActive || topMost;
+ return isSelected || this._isChildActive || this.props.renderDepth === 0 || BoolCast(this.props.Document.excludeFromLibrary);
}
//TODO should this be observable?
@@ -71,85 +74,35 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
this.props.whenActiveChanged(isActive);
}
- createsCycle(documentToAdd: Doc, containerDocument: Doc): boolean {
- if (!(documentToAdd instanceof Doc)) {
- return false;
- }
- let data = DocListCast(documentToAdd.data);
- for (const doc of data) {
- if (this.createsCycle(doc, containerDocument)) {
- return true;
- }
- }
- let annots = DocListCast(documentToAdd.annotations);
- for (const annot of annots) {
- if (this.createsCycle(annot, containerDocument)) {
- return true;
- }
- }
- for (let containerProto: Opt<Doc> = containerDocument; containerProto !== undefined; containerProto = FieldValue(containerProto.proto)) {
- if (containerProto[Id] === documentToAdd[Id]) {
- return true;
- }
- }
- return false;
- }
- @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);
+ var curPage = NumCast(this.props.Document.curPage, -1);
+ Doc.GetProto(doc).page = curPage;
if (curPage >= 0) {
- Doc.SetOnPrototype(doc, "annotationOn", props.Document);
+ Doc.GetProto(doc).annotationOn = this.props.Document;
}
- if (!this.createsCycle(doc, props.Document)) {
- //TODO This won't create the field if it doesn't already exist
- const childDocs = Cast(props.Document[props.fieldKey], listSpec(Doc));
- let alreadyAdded = true;
- if (childDocs !== undefined) {
- // if this is not the first document added to the collection
- if (allowDuplicates || !childDocs.some(v => v instanceof Doc && v[Id] === doc[Id])) {
- alreadyAdded = false;
- childDocs.push(doc);
- }
- // if we're here, we've tried to add a duplicate
- } else {
- // if we are the first, set up a new list for this and all
- // future child documents stored in the associated collection document at the fieldKey (likely .data)
- // passed in via props
- alreadyAdded = false;
- Doc.SetOnPrototype(this.props.Document, this.props.fieldKey, new List([doc]));
- }
- // 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.GetProto(doc).zoomBasis = zoom;
+ allowDuplicates = true;
+ const value = Cast(this.dataDoc[this.dataField], listSpec(Doc));
+ if (value !== undefined) {
+ if (allowDuplicates || !value.some(v => v instanceof Doc && v[Id] === doc[Id])) {
+ value.push(doc);
}
+ } else {
+ Doc.GetProto(this.dataDoc)[this.dataField] = new List([doc]);
}
return true;
}
@action.bound
removeDocument(doc: Doc): boolean {
- SelectionManager.DeselectAll();
- const props = this.props;
+ let docView = DocumentManager.Instance.getDocumentView(doc, this.props.ContainingCollectionView);
+ docView && SelectionManager.DeselectDoc(docView);
//TODO This won't create the field if it doesn't already exist
- const value = Cast(props.Document[props.fieldKey], listSpec(Doc), []);
- let index = -1;
- for (let i = 0; i < value.length; i++) {
- let v = value[i];
- if (v instanceof Doc && v[Id] === doc[Id]) {
- index = i;
- break;
- }
- }
- PromiseValue(Cast(doc.annotationOn, Doc)).then((annotationOn) => {
- if (annotationOn === props.Document) {
- doc.annotationOn = undefined;
- }
- });
+ const value = Cast(this.dataDoc[this.dataField], listSpec(Doc), []);
+ let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1);
+ PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn =>
+ annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)
+ );
if (index !== -1) {
value.splice(index, 1);
@@ -163,11 +116,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.dataDoc, targetCollection)) {
return true;
}
if (this.removeDocument(doc)) {
- SelectionManager.DeselectAll();
return addDocument(doc);
}
return false;
@@ -183,12 +135,11 @@ 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>
);
}
-} \ No newline at end of file
+}
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 5270a4624..f44ab50c7 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -1,122 +1,39 @@
import 'golden-layout/src/css/goldenlayout-base.css';
import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction, Lambda, IReactionDisposer } from "mobx";
+import { action, Lambda, observable, reaction } from "mobx";
import { observer } from "mobx-react";
import * as ReactDOM from 'react-dom';
-import Measure, { ContentRect } from "react-measure";
+import Measure from "react-measure";
import * as GoldenLayout from "../../../client/goldenLayout";
-import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
+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 { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types";
+import { emptyFunction, returnTrue, Utils, returnOne } 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 { Id } from '../../../new_fields/FieldSymbols';
-import { DockedFrameRenderer } from './DockedFrameRenderer';
+import React = require("react");
+import { MainView } from '../MainView';
+import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faFile } from '@fortawesome/free-solid-svg-icons';
+import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+library.add(faFile);
@observer
export class CollectionDockingView extends React.Component<SubCollectionViewProps> {
- public static TopLevel: CollectionDockingView;
- private _goldenLayout: any = null;
- private _containerRef = React.createRef<HTMLDivElement>();
- reactionDisposer?: IReactionDisposer;
- _removedDocs: Doc[] = [];
- private _flush: boolean = false;
- private _ignoreStateChange = "";
- private _isPointerDown = false;
- hack: boolean = false;
- undohack: any = null;
-
- constructor(props: SubCollectionViewProps) {
- super(props);
- CollectionDockingView.TopLevel = this;
- (window as any).React = React;
- (window as any).ReactDOM = ReactDOM;
- }
-
- componentDidMount: () => void = () => {
- if (this._containerRef.current) {
- this.reactionDisposer = reaction(
- () => StrCast(this.props.Document.dockingConfig),
- () => {
- if (!this._goldenLayout || this._ignoreStateChange !== this.retrieveConfiguration()) {
- // Because this is in a set timeout, if this component unmounts right after mounting,
- // we will leak a GoldenLayout, because we try to destroy it before we ever create it
- setTimeout(() => this.setupGoldenLayout(), 1);
- }
- this._ignoreStateChange = "";
- }, { fireImmediately: true });
-
- // window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
- }
- }
-
- componentWillUnmount: () => void = () => {
- try {
- this._goldenLayout.unbind('itemDropped', this.itemDropped);
- this._goldenLayout.unbind('tabCreated', this.tabCreated);
- this._goldenLayout.unbind('stackCreated', this.stackCreated);
- this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
- } catch (e) {
- console.log("Unable to unbind Golden Layout event listener...", e);
- }
- if (this._goldenLayout) this._goldenLayout.destroy();
- this._goldenLayout = null;
-
- if (this.reactionDisposer) {
- this.reactionDisposer();
- }
- }
-
- setupGoldenLayout() {
- var config = StrCast(this.props.Document.dockingConfig);
- if (config) {
- if (!this._goldenLayout) {
- this.initializeConfiguration(config);
- }
- else {
- if (config === this.retrieveConfiguration()) {
- return;
- }
- try {
- this._goldenLayout.unbind('itemDropped', this.itemDropped);
- this._goldenLayout.unbind('tabCreated', this.tabCreated);
- this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
- this._goldenLayout.unbind('stackCreated', this.stackCreated);
- } catch (e) { }
- this._goldenLayout.destroy();
- this.initializeConfiguration(config);
- }
- this._goldenLayout.on('itemDropped', this.itemDropped);
- this._goldenLayout.on('tabCreated', this.tabCreated);
- this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
- this._goldenLayout.on('stackCreated', this.stackCreated);
- this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
- this._goldenLayout.container = this._containerRef.current;
- if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
- try {
- this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
- } catch (e) {
- this._goldenLayout.config.maximisedItemId = null;
- }
- }
- this._goldenLayout.init();
- }
- }
-
- private makeDocConfig = (document: Doc, width?: number) => {
- const config = CollectionDockingView.makeDocumentConfig(document, width);
- (config.props as any).parent = this;
- return config;
- }
-
- public static makeDocumentConfig(document: Doc, width?: number) {
+ public static Instance: CollectionDockingView;
+ public static makeDocumentConfig(document: Doc, dataDoc: Doc | undefined, width?: number) {
return {
type: 'react-component',
component: 'DocumentFrameRenderer',
@@ -124,83 +41,147 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
width: width,
props: {
documentId: document[Id],
+ dataDocumentId: dataDoc ? dataDoc[Id] : ""
+ //collectionDockingView: CollectionDockingView.Instance
}
};
}
- initializeConfiguration = (configText: string) => {
- let configuration: any = JSON.parse(configText);
- this.injectParentProp(configuration.content);
- this._goldenLayout = new GoldenLayout(configuration);
- }
+ private _goldenLayout: any = null;
+ private _containerRef = React.createRef<HTMLDivElement>();
+ private _flush: boolean = false;
+ private _ignoreStateChange = "";
+ private _isPointerDown = false;
+ private _maximizedSrc: Opt<DocumentView>;
- retrieveConfiguration = () => {
- let configuration: any = this._goldenLayout.toConfig();
- this.injectParentProp(configuration.content, true);
- return JSON.stringify(configuration);
+ constructor(props: SubCollectionViewProps) {
+ super(props);
+ if (props.addDocTab === emptyFunction) CollectionDockingView.Instance = this;
+ //Why is this here?
+ (window as any).React = React;
+ (window as any).ReactDOM = ReactDOM;
}
-
- injectParentProp = (contentArray: any[], reverse: boolean = false) => {
- if (!contentArray || contentArray.length === 0) return;
- contentArray.forEach(member => {
- let baseCase = Object.keys(member).includes("props");
- if (!baseCase) {
- this.injectParentProp(member.content, reverse);
- } else {
- reverse ? delete member.props.parent : member.props.parent = this;
- }
+ hack: boolean = false;
+ undohack: any = null;
+ public StartOtherDrag(e: any, dragDocs: Doc[], dragDataDocs: (Doc | undefined)[] = []) {
+ let config: any;
+ if (dragDocs.length === 1) {
+ config = CollectionDockingView.makeDocumentConfig(dragDocs[0], dragDataDocs[0]);
+ } else {
+ config = {
+ type: 'row',
+ content: dragDocs.map((doc, i) => {
+ CollectionDockingView.makeDocumentConfig(doc, dragDataDocs[i]);
+ })
+ };
+ }
+ const div = document.createElement("div");
+ const dragSource = this._goldenLayout.createDragSource(div, config);
+ dragSource._dragListener.on("dragStop", () => {
+ dragSource.destroy();
});
- }
-
- public StartOtherDrag(dragDocs: Doc[], e: any) {
- this.hack = true;
- this.undohack = UndoManager.StartBatch("goldenDrag");
- dragDocs.map(dragDoc =>
- CollectionDockingView.AddRightSplit(dragDoc, true).contentItems[0].tab._dragListener.
- onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
+ dragSource._dragListener.onMouseDown(e);
+ // dragSource.destroy();
+ // this.hack = true;
+ // this.undohack = UndoManager.StartBatch("goldenDrag");
+ // dragDocs.map((dragDoc, i) =>
+ // this.AddRightSplit(dragDoc, dragDataDocs[i], true).contentItems[0].tab._dragListener.
+ // onMouseDown({ pageX: e.pageX, pageY: e.pageY, preventDefault: emptyFunction, button: 0 }));
}
@action
- public static OpenFullScreen(document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) {
- dockingView.openFullScreen(document);
- }
-
- private openFullScreen = (document: Doc) => {
+ public OpenFullScreen(docView: DocumentView) {
+ let document = Doc.MakeAlias(docView.props.Document);
+ let dataDoc = docView.dataDoc;
let newItemStackConfig = {
type: 'stack',
- content: [this.makeDocConfig(document)]
+ content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]
};
var docconfig = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
this._goldenLayout.root.contentItems[0].addChild(docconfig);
docconfig.callDownwards('_$init');
this._goldenLayout._$maximiseItem(docconfig);
- this._ignoreStateChange = this.retrieveConfiguration();
+ this._maximizedSrc = docView;
+ this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
+ this.stateChanged();
+ }
+
+ public CloseFullScreen = () => {
+ let target = this._goldenLayout._maximisedItem;
+ if (target !== null && this._maximizedSrc) {
+ this._goldenLayout._maximisedItem.remove();
+ SelectionManager.SelectDoc(this._maximizedSrc, false);
+ this._maximizedSrc = undefined;
+ this.stateChanged();
+ }
+ }
+
+ public HasFullScreen = () => {
+ return this._goldenLayout._maximisedItem !== null;
+ }
+
+ @undoBatch
+ @action
+ 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) => {
+ if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
+ Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
+ child.contentItems[0].remove();
+ this.layoutChanged(document);
+ return true;
+ } else {
+ Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
+ if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
+ child.contentItems[j].remove();
+ child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
+ let docs = Cast(this.props.Document.data, listSpec(Doc));
+ docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
+ return true;
+ }
+ return false;
+ });
+ }
+ return false;
+ });
+ }
+ if (retVal) {
+ this.stateChanged();
+ }
+ return retVal;
+ }
+
+ @action
+ layoutChanged(removed?: Doc) {
+ this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
+ this._goldenLayout.emit('stateChanged');
+ this._ignoreStateChange = JSON.stringify(this._goldenLayout.toConfig());
+ if (removed) CollectionDockingView.Instance._removedDocs.push(removed);
this.stateChanged();
}
//
// Creates a vertical split on the right side of the docking view, and then adds the Document to that split
//
- public static AddRightSplit = (document: Doc, minimize: boolean = false, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) => {
- return dockingView.addRightSplit(document, minimize);
- }
-
- private addRightSplit(document: Doc, minimize = false) {
+ @action
+ public AddRightSplit = (document: Doc, dataDoc: Doc | undefined, minimize: boolean = false) => {
let docs = Cast(this.props.Document.data, listSpec(Doc));
if (docs) {
docs.push(document);
}
let newItemStackConfig = {
type: 'stack',
- content: [this.makeDocConfig(document)]
+ content: [CollectionDockingView.makeDocumentConfig(document, dataDoc)]
};
var newContentItem = this._goldenLayout.root.layoutManager.createContentItem(newItemStackConfig, this._goldenLayout);
- if (this._goldenLayout.root.contentItems[0].isRow) {
+ if (this._goldenLayout.root.contentItems.length === 0) {
+ this._goldenLayout.root.addChild(newContentItem);
+ } else if (this._goldenLayout.root.contentItems[0].isRow) {
this._goldenLayout.root.contentItems[0].addChild(newContentItem);
- }
- else {
+ } else {
var collayout = this._goldenLayout.root.contentItems[0];
var newRow = collayout.layoutManager.createContentItem({ type: "row" }, this._goldenLayout);
collayout.parent.replaceChild(collayout, newRow);
@@ -221,75 +202,99 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
return newContentItem;
}
-
- public static AddTab = (stack: any, document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel) => {
- dockingView.addTab(stack, document);
- }
-
- private addTab = (stack: any, document: Doc) => {
+ @action
+ public AddTab = (stack: any, document: Doc, dataDocument: Doc | undefined) => {
let docs = Cast(this.props.Document.data, listSpec(Doc));
if (docs) {
docs.push(document);
}
- let docContentConfig = this.makeDocConfig(document);
+ let docContentConfig = CollectionDockingView.makeDocumentConfig(document, dataDocument);
var newContentItem = stack.layoutManager.createContentItem(docContentConfig, this._goldenLayout);
stack.addChild(newContentItem.contentItems[0], undefined);
this.layoutChanged();
}
- @undoBatch
- @action
- public static CloseRightSplit = (document: Doc, dockingView: CollectionDockingView = CollectionDockingView.TopLevel): boolean => {
- let retVal = false;
- if (dockingView._goldenLayout.root.contentItems[0].isRow) {
- retVal = Array.from(dockingView._goldenLayout.root.contentItems[0].contentItems).some((child: any) => {
- if (child.contentItems.length === 1 && child.contentItems[0].config.component === "DocumentFrameRenderer" &&
- Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(child.contentItems[0].config.props.documentId)!.Document, document)) {
- child.contentItems[0].remove();
- dockingView.layoutChanged(document);
- return true;
- } else {
- Array.from(child.contentItems).filter((tab: any) => tab.config.component === "DocumentFrameRenderer").some((tab: any, j: number) => {
- if (Doc.AreProtosEqual(DocumentManager.Instance.getDocumentViewById(tab.config.props.documentId)!.Document, document)) {
- child.contentItems[j].remove();
- child.config.activeItemIndex = Math.max(child.contentItems.length - 1, 0);
- let docs = Cast(dockingView.props.Document.data, listSpec(Doc));
- docs && docs.indexOf(document) !== -1 && docs.splice(docs.indexOf(document), 1);
- return true;
- }
- return false;
- });
+ setupGoldenLayout() {
+ var config = StrCast(this.props.Document.dockingConfig);
+ if (config) {
+ if (!this._goldenLayout) {
+ this._goldenLayout = new GoldenLayout(JSON.parse(config));
+ }
+ else {
+ if (config === JSON.stringify(this._goldenLayout.toConfig())) {
+ return;
}
- return false;
- });
- }
- if (retVal) {
- dockingView.stateChanged();
+ try {
+ this._goldenLayout.unbind('itemDropped', this.itemDropped);
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ } catch (e) { }
+ this._goldenLayout.destroy();
+ this._goldenLayout = new GoldenLayout(JSON.parse(config));
+ }
+ this._goldenLayout.on('itemDropped', this.itemDropped);
+ this._goldenLayout.on('tabCreated', this.tabCreated);
+ this._goldenLayout.on('tabDestroyed', this.tabDestroyed);
+ this._goldenLayout.on('stackCreated', this.stackCreated);
+ this._goldenLayout.registerComponent('DocumentFrameRenderer', DockedFrameRenderer);
+ this._goldenLayout.container = this._containerRef.current;
+ if (this._goldenLayout.config.maximisedItemId === '__glMaximised') {
+ try {
+ this._goldenLayout.config.root.getItemsById(this._goldenLayout.config.maximisedItemId)[0].toggleMaximise();
+ } catch (e) {
+ this._goldenLayout.config.maximisedItemId = null;
+ }
+ }
+ this._goldenLayout.init();
}
- return retVal;
}
+ reactionDisposer?: Lambda;
+ componentDidMount: () => void = () => {
+ if (this._containerRef.current) {
+ this.reactionDisposer = reaction(
+ () => StrCast(this.props.Document.dockingConfig),
+ () => {
+ if (!this._goldenLayout || this._ignoreStateChange !== JSON.stringify(this._goldenLayout.toConfig())) {
+ // Because this is in a set timeout, if this component unmounts right after mounting,
+ // we will leak a GoldenLayout, because we try to destroy it before we ever create it
+ setTimeout(() => this.setupGoldenLayout(), 1);
+ this.props.Document.workspaceBrush = true;
+ }
+ this._ignoreStateChange = "";
+ }, { fireImmediately: true });
- @action
- layoutChanged(removed?: Doc) {
- this._goldenLayout.root.callDownwards('setSize', [this._goldenLayout.width, this._goldenLayout.height]);
- this._goldenLayout.emit('stateChanged');
- this._ignoreStateChange = this.retrieveConfiguration();
- if (removed) CollectionDockingView.TopLevel._removedDocs.push(removed);
- this.stateChanged();
+ window.addEventListener('resize', this.onResize); // bcz: would rather add this event to the parent node, but resize events only come from Window
+ }
}
+ componentWillUnmount: () => void = () => {
+ try {
+ this.props.Document.workspaceBrush = false;
+ this._goldenLayout.unbind('itemDropped', this.itemDropped);
+ this._goldenLayout.unbind('tabCreated', this.tabCreated);
+ this._goldenLayout.unbind('stackCreated', this.stackCreated);
+ this._goldenLayout.unbind('tabDestroyed', this.tabDestroyed);
+ } catch (e) {
+ }
+ if (this._goldenLayout) this._goldenLayout.destroy();
+ this._goldenLayout = null;
+ window.removeEventListener('resize', this.onResize);
+
+ if (this.reactionDisposer) {
+ this.reactionDisposer();
+ }
+ }
@action
- onResize = (size: ContentRect) => {
+ onResize = (event: any) => {
+ var cur = this._containerRef.current;
+
// bcz: since GoldenLayout isn't a React component itself, we need to notify it to resize when its document container's size has changed
- // this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
- if (this._goldenLayout) {
- this._goldenLayout.updateSize(size.offset!.width, size.offset!.height);
- }
+ this._goldenLayout && this._goldenLayout.updateSize(cur!.getBoundingClientRect().width, cur!.getBoundingClientRect().height);
}
@action
onPointerUp = (e: React.PointerEvent): void => {
- this._isPointerDown = false;
if (this._flush) {
this._flush = false;
setTimeout(() => this.stateChanged(), 10);
@@ -298,6 +303,11 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
@action
onPointerDown = (e: React.PointerEvent): void => {
this._isPointerDown = true;
+ let onPointerUp = action(() => {
+ window.removeEventListener("pointerup", onPointerUp);
+ this._isPointerDown = false;
+ });
+ window.addEventListener("pointerup", onPointerUp);
var className = (e.target as any).className;
if (className === "messageCounter") {
e.stopPropagation();
@@ -306,48 +316,22 @@ 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;
- DocServer.getRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
+ DocServer.GetRefField(docid).then(action(async (sourceDoc: Opt<Field>) =>
(sourceDoc instanceof Doc) && DragLinksAsDocuments(tab, x, y, sourceDoc)));
- } else
- if ((className === "lm_title" || className === "lm_tab lm_active") && !e.shiftKey) {
- e.stopPropagation();
- e.preventDefault();
- let x = e.clientX;
- 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,
- {
- handlers: {
- dragComplete: emptyFunction,
- },
- hideSource: false
- });
- }
- }));
- }
+ }
if (className === "lm_drag_handle" || className === "lm_close" || className === "lm_maximise" || className === "lm_minimise" || className === "lm_close_tab") {
this._flush = true;
}
- if (this.props.active()) {
- e.stopPropagation();
- }
}
@undoBatch
stateChanged = () => {
- let docs = Cast(CollectionDockingView.TopLevel.props.Document.data, listSpec(Doc));
- CollectionDockingView.TopLevel._removedDocs.map(theDoc =>
+ let docs = Cast(CollectionDockingView.Instance.props.Document.data, listSpec(Doc));
+ CollectionDockingView.Instance._removedDocs.map(theDoc =>
docs && docs.indexOf(theDoc) !== -1 &&
docs.splice(docs.indexOf(theDoc), 1));
- CollectionDockingView.TopLevel._removedDocs.length = 0;
- var json = this.retrieveConfiguration();
+ CollectionDockingView.Instance._removedDocs.length = 0;
+ var json = JSON.stringify(this._goldenLayout.toConfig());
this.props.Document.dockingConfig = json;
if (this.undohack && !this.hack) {
this.undohack.end();
@@ -356,52 +340,61 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
this.hack = false;
}
- private itemDropped = () => {
+ itemDropped = () => {
this.stateChanged();
}
- private htmlToElement(html: string) {
+ htmlToElement(html: string) {
var template = document.createElement('template');
html = html.trim(); // Never return a text node of whitespace as the result
template.innerHTML = html;
return template.content.firstChild;
}
- private tabCreated = async (tab: any) => {
+ tabCreated = async (tab: any) => {
if (tab.hasOwnProperty("contentItem") && tab.contentItem.config.type !== "stack") {
if (tab.contentItem.config.fixed) {
tab.contentItem.parent.config.fixed = true;
}
- DocServer.getRefField(tab.contentItem.config.props.documentId).then(async doc => {
- if (doc instanceof Doc) {
- let counter: any = this.htmlToElement(`<span class="messageCounter">0</div>`);
- tab.element.append(counter);
- let upDiv = document.createElement("span");
- const stack = tab.contentItem.parent;
- // shifts the focus to this tab when another tab is dragged over it
- tab.element[0].onmouseenter = (e: any) => {
- if (!this._isPointerDown) return;
- var activeContentItem = tab.header.parent.getActiveContentItem();
- if (tab.contentItem !== activeContentItem) {
- tab.header.parent.setActiveContentItem(tab.contentItem);
- }
- tab.setActive(true);
- };
- tab.header.element[0].ondrop = (e: any) => {
- console.log("DROPPPP THE BASS!", e);
- };
- ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={(doc, location) => CollectionDockingView.AddTab(stack, doc)} />, upDiv);
- tab.reactComponents = [upDiv];
- tab.element.append(upDiv);
- counter.DashDocId = tab.contentItem.config.props.documentId;
- tab.reactionDisposer = reaction(() => [doc.linkedFromDocs, doc.LinkedToDocs, doc.title],
- () => {
- counter.innerHTML = DocListCast(doc.linkedFromDocs).length + DocListCast(doc.linkedToDocs).length;
- tab.titleElement[0].textContent = doc.title;
- }, { fireImmediately: true });
- tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
- }
- });
+ let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId) as Doc;
+ let dataDoc = await DocServer.GetRefField(tab.contentItem.config.props.dataDocumentId) as Doc;
+ if (doc instanceof Doc) {
+ let dragSpan = document.createElement("span");
+ dragSpan.style.position = "relative";
+ dragSpan.style.bottom = "6px";
+ dragSpan.style.paddingLeft = "4px";
+ dragSpan.style.paddingRight = "2px";
+ let upDiv = document.createElement("span");
+ const stack = tab.contentItem.parent;
+ // shifts the focus to this tab when another tab is dragged over it
+ tab.element[0].onmouseenter = (e: any) => {
+ if (!this._isPointerDown) return;
+ var activeContentItem = tab.header.parent.getActiveContentItem();
+ if (tab.contentItem !== activeContentItem) {
+ tab.header.parent.setActiveContentItem(tab.contentItem);
+ }
+ tab.setActive(true);
+ };
+ ReactDOM.render(<span title="Drag as document" onPointerDown={
+ e => {
+ e.preventDefault();
+ e.stopPropagation();
+ DragManager.StartDocumentDrag([dragSpan], new DragManager.DocumentDragData([doc], [dataDoc]), e.clientX, e.clientY, {
+ handlers: { dragComplete: emptyFunction },
+ hideSource: false
+ });
+ }}><FontAwesomeIcon icon="file" size="lg" /></span>, dragSpan);
+ ReactDOM.render(<ParentDocSelector Document={doc} addDocTab={doc => CollectionDockingView.Instance.AddTab(stack, doc, dataDoc)} />, upDiv);
+ tab.reactComponents = [dragSpan, upDiv];
+ tab.element.append(dragSpan);
+ tab.element.append(upDiv);
+ tab.reactionDisposer = reaction(() => [doc.title],
+ () => {
+ tab.titleElement[0].textContent = doc.title;
+ }, { fireImmediately: true });
+ //TODO why can't this just be doc instead of the id?
+ tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId;
+ }
}
tab.titleElement[0].Tab = tab;
tab.closeElement.off('click') //unbind the current click handler
@@ -409,35 +402,41 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (tab.reactionDisposer) {
tab.reactionDisposer();
}
- let doc = await DocServer.getRefField(tab.contentItem.config.props.documentId);
+ let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId);
if (doc instanceof Doc) {
let theDoc = doc;
- CollectionDockingView.TopLevel._removedDocs.push(theDoc);
+ CollectionDockingView.Instance._removedDocs.push(theDoc);
+ if (CurrentUserUtils.UserDocument.recentlyClosed instanceof Doc) {
+ Doc.AddDocToList(CurrentUserUtils.UserDocument.recentlyClosed, "data", doc, undefined, true, true);
+ }
+ SelectionManager.DeselectAll();
}
tab.contentItem.remove();
});
}
- private tabDestroyed = (tab: any) => {
+ tabDestroyed = (tab: any) => {
if (tab.reactComponents) {
for (const ele of tab.reactComponents) {
ReactDOM.unmountComponentAtNode(ele);
}
}
}
+ _removedDocs: Doc[] = [];
- private stackCreated = (stack: any) => {
+ stackCreated = (stack: any) => {
//stack.header.controlsContainer.find('.lm_popout').hide();
+ stack.header.element[0].style.backgroundColor = DocServer.Control.isReadOnly() ? "#228540" : undefined;
stack.header.controlsContainer.find('.lm_close') //get the close icon
.off('click') //unbind the current click handler
.click(action(function () {
//if (confirm('really close this?')) {
stack.remove();
stack.contentItems.map(async (contentItem: any) => {
- let doc = await DocServer.getRefField(contentItem.config.props.documentId);
+ let doc = await DocServer.GetRefField(contentItem.config.props.documentId);
if (doc instanceof Doc) {
let theDoc = doc;
- CollectionDockingView.TopLevel._removedDocs.push(theDoc);
+ CollectionDockingView.Instance._removedDocs.push(theDoc);
}
});
//}
@@ -452,16 +451,130 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
render() {
+ if (this.props.renderDepth > 0) {
+ return <div style={{ width: "100%", height: "100%" }}>Nested workspaces can't be rendered</div>;
+ }
return (
- <Measure onResize={this.onResize} offset>
- {({ measureRef }) => (
- <div ref={measureRef} style={{ width: "100%", height: "100%" }}>
+ <Measure offset onResize={this.onResize}>
+ {({ measureRef }) =>
+ <div ref={measureRef}>
<div className="collectiondockingview-container" id="menuContainer"
onPointerDown={this.onPointerDown} onPointerUp={this.onPointerUp} ref={this._containerRef} />
</div>
- )}
+ }
</Measure>
);
}
+}
+
+interface DockedFrameProps {
+ documentId: FieldId;
+ dataDocumentId: FieldId;
+ //collectionDockingView: CollectionDockingView
+}
+@observer
+export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
+ _mainCont = React.createRef<HTMLDivElement>();
+ @observable private _panelWidth = 0;
+ @observable private _panelHeight = 0;
+ @observable private _document: Opt<Doc>;
+ @observable private _dataDoc: 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;
+ if (this.props.dataDocumentId && this.props.documentId !== this.props.dataDocumentId) {
+ DocServer.GetRefField(this.props.dataDocumentId).then(action((f: Opt<Field>) => this._dataDoc = f as Doc));
+ }
+ }));
+ }
+
+ nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
+ nativeHeight = () => {
+ let nh = NumCast(this._document!.nativeHeight, this._panelHeight);
+ let res = BoolCast(this._document!.ignoreAspect) ? this._panelHeight : nh;
+ return res;
+ }
+ contentScaling = () => {
+ const nativeH = this.nativeHeight();
+ const nativeW = this.nativeWidth();
+ let wscale = this._panelWidth / nativeW;
+ return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
+ }
+
+ ScreenToLocalTransform = () => {
+ 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(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 ||
+ !BoolCast(this._document!.fitToContents, false) ||
+ 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, dataDoc: Doc | undefined, location: string) => {
+ if (doc.dockingConfig) {
+ MainView.Instance.openWorkspace(doc);
+ } else if (location === "onRight") {
+ CollectionDockingView.Instance.AddRightSplit(doc, dataDoc);
+ } else {
+ CollectionDockingView.Instance.AddTab(this._stack, doc, dataDoc);
+ }
+ }
+ get content() {
+ if (!this._document) {
+ return (null);
+ }
+ let resolvedDataDoc = this._document.layout instanceof Doc ? this._document : this._dataDoc;
+ return (
+ <div className="collectionDockingView-content" ref={this._mainCont}
+ style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px) scale(${this.scaleToFitMultiplier})` }}>
+ <DocumentView key={this._document[Id]}
+ Document={this._document}
+ DataDoc={resolvedDataDoc}
+ bringToFront={emptyFunction}
+ addDocument={undefined}
+ removeDocument={undefined}
+ ContentScaling={this.contentScaling}
+ PanelWidth={this.nativeWidth}
+ PanelHeight={this.nativeHeight}
+ ScreenToLocalTransform={this.ScreenToLocalTransform}
+ renderDepth={0}
+ selectOnLoad={false}
+ parentActive={returnTrue}
+ whenActiveChanged={emptyFunction}
+ focus={emptyFunction}
+ addDocTab={this.addDocTab}
+ ContainingCollectionView={undefined}
+ zoomToScale={emptyFunction}
+ getScale={returnOne} />
+ </div >);
+ }
+
+ render() {
+ let theContent = this.content;
+ return !this._document ? (null) :
+ <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
+ {({ measureRef }) => <div ref={measureRef}> {theContent} </div>}
+ </Measure>;
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx
index 5e51437a4..31a73ab36 100644
--- a/src/client/views/collections/CollectionPDFView.tsx
+++ b/src/client/views/collections/CollectionPDFView.tsx
@@ -1,60 +1,74 @@
-import { action, observable } from "mobx";
+import { action, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
+import { WidthSym } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { NumCast } from "../../../new_fields/Types";
+import { emptyFunction } from "../../../Utils";
import { ContextMenu } from "../ContextMenu";
+import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView";
+import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
import "./CollectionPDFView.scss";
import React = require("react");
-import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView";
-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/FieldSymbols";
+import { PDFBox } from "../nodes/PDFBox";
@observer
export class CollectionPDFView extends React.Component<FieldViewProps> {
+ private _pdfBox?: PDFBox;
+ private _reactionDisposer?: IReactionDisposer;
+ private _buttonTray: React.RefObject<HTMLDivElement>;
+
+ constructor(props: FieldViewProps) {
+ super(props);
+
+ this._buttonTray = React.createRef();
+ }
+
+ componentDidMount() {
+ this._reactionDisposer = reaction(
+ () => NumCast(this.props.Document.scrollY),
+ () => {
+ // let transform = this.props.ScreenToLocalTransform();
+ if (this._buttonTray.current) {
+ // console.log(this._buttonTray.current.offsetHeight);
+ // console.log(NumCast(this.props.Document.scrollY));
+ let scale = this.nativeWidth() / this.props.Document[WidthSym]();
+ this.props.Document.panY = NumCast(this.props.Document.scrollY);
+ // console.log(scale);
+ }
+ // console.log(this.props.Document[HeightSym]());
+ },
+ { fireImmediately: true }
+ );
+ }
+
+ componentWillUnmount() {
+ this._reactionDisposer && this._reactionDisposer();
+ }
public static LayoutString(fieldKey: string = "data") {
return FieldView.LayoutString(CollectionPDFView, fieldKey);
}
@observable _inThumb = false;
- private set curPage(value: number) { this.props.Document.curPage = value; }
+ private set curPage(value: number) { this._pdfBox && this._pdfBox.GotoPage(value); }
private get curPage() { return NumCast(this.props.Document.curPage, -1); }
private get numPages() { return NumCast(this.props.Document.numPages); }
- @action onPageBack = () => this.curPage > 1 ? (this.props.Document.curPage = this.curPage - 1) : -1;
- @action onPageForward = () => this.curPage < this.numPages ? (this.props.Document.curPage = this.curPage + 1) : -1;
+ @action onPageBack = () => this._pdfBox && this._pdfBox.BackPage();
+ @action onPageForward = () => this._pdfBox && this._pdfBox.ForwardPage();
- @action
- onThumbDown = (e: React.PointerEvent) => {
- document.addEventListener("pointermove", this.onThumbMove, false);
- document.addEventListener("pointerup", this.onThumbUp, false);
- e.stopPropagation();
- this._inThumb = true;
- }
- @action
- onThumbMove = (e: PointerEvent) => {
- let pso = (e.clientY - (e as any).target.parentElement.getBoundingClientRect().top) / (e as any).target.parentElement.getBoundingClientRect().height;
- this.curPage = Math.trunc(Math.min(this.numPages, pso * this.numPages + 1));
- e.stopPropagation();
- }
- @action
- onThumbUp = (e: PointerEvent) => {
- this._inThumb = false;
- document.removeEventListener("pointermove", this.onThumbMove);
- document.removeEventListener("pointerup", this.onThumbUp);
- }
nativeWidth = () => NumCast(this.props.Document.nativeWidth);
nativeHeight = () => NumCast(this.props.Document.nativeHeight);
private get uIButtons() {
let ratio = (this.curPage - 1) / this.numPages * 100;
return (
- <div className="collectionPdfView-buttonTray" key="tray" style={{ height: "100%" }}>
+ <div className="collectionPdfView-buttonTray" ref={this._buttonTray} key="tray" style={{ height: "100%" }}>
<button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button>
<button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button>
- <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} >
+ {/* <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} >
<div className="collectionPdfView-thumb" onPointerDown={this.onThumbDown} style={{ top: `${ratio}%`, width: 50, height: 50 }} />
- </div>
+ </div> */}
</div>
);
}
@@ -65,12 +79,15 @@ export class CollectionPDFView extends React.Component<FieldViewProps> {
}
}
+ setPdfBox = (pdfBox: PDFBox) => { this._pdfBox = pdfBox; };
+
+
private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => {
let props = { ...this.props, ...renderProps };
return (
<>
- <CollectionFreeFormView {...props} CollectionView={this} />
- {this.props.isSelected() ? this.uIButtons : (null)}
+ <CollectionFreeFormView {...props} setPdfBox={this.setPdfBox} CollectionView={this} />
+ {renderProps.active() ? this.uIButtons : (null)}
</>
);
}
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index 477879b79..cf39e26a8 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -2,36 +2,35 @@ 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, trace } from "mobx";
+import { action, computed, observable, trace, untracked } from "mobx";
import { observer } from "mobx-react";
import ReactTable, { CellInfo, ComponentPropsGetterR, ReactTableDefaults } from "react-table";
-import { MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
import "react-table/react-table.css";
-import { emptyFunction, returnFalse, returnZero } from "../../../Utils";
-import { SetupDrag } from "../../util/DragManager";
+import { emptyFunction, returnFalse, returnZero, returnOne } from "../../../Utils";
+import { Doc, DocListCast, DocListCastAsync, Field } 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, BoolCast } from "../../../new_fields/Types";
+import { Docs } from "../../documents/Documents";
+import { Gateway } from "../../northstar/manager/Gateway";
+import { SetupDrag, DragManager } from "../../util/DragManager";
import { CompileScript } from "../../util/Scripting";
import { Transform } from "../../util/Transform";
-import { COLLECTION_BORDER_WIDTH } from "../../views/globalCssVariables.scss";
+import { COLLECTION_BORDER_WIDTH, MAX_ROW_HEIGHT } from '../../views/globalCssVariables.scss';
+import { ContextMenu } from "../ContextMenu";
import { anchorPoints, Flyout } from "../DocumentDecorations";
import '../DocumentDecorations.scss';
import { EditableView } from "../EditableView";
import { DocumentView } from "../nodes/DocumentView";
import { FieldView, FieldViewProps } from "../nodes/FieldView";
+import { CollectionPDFView } from "./CollectionPDFView";
import "./CollectionSchemaView.scss";
import { CollectionSubView } from "./CollectionSubView";
-import { Opt, Field, Doc, DocListCastAsync, DocListCast } from "../../../new_fields/Doc";
-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/FieldSymbols";
-import { Gateway } from "../../northstar/manager/Gateway";
-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 { CollectionView } from "./CollectionView";
import { undoBatch } from "../../util/UndoManager";
+import { timesSeries } from "async";
library.add(faCog);
@@ -90,8 +89,9 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
let columnDocs = DocListCast(schemaDoc.data);
if (columnDocs) {
let ddoc = columnDocs.find(doc => doc.title === columnName);
- if (ddoc)
+ if (ddoc) {
return ddoc;
+ }
}
}
return this.props.Document;
@@ -100,11 +100,13 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
renderCell = (rowProps: CellInfo) => {
let props: FieldViewProps = {
Document: rowProps.original,
+ DataDoc: rowProps.original,
fieldKey: rowProps.column.id as string,
+ fieldExt: "",
ContainingCollectionView: this.props.CollectionView,
isSelected: returnFalse,
select: emptyFunction,
- isTopMost: false,
+ renderDepth: this.props.renderDepth + 1,
selectOnLoad: false,
ScreenToLocalTransform: Transform.Identity,
focus: emptyFunction,
@@ -116,9 +118,10 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
};
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, this.props.Document.schemaDoc ? "copy" : undefined)(e) : undefined);
+ let onItemDown = (e: React.PointerEvent) => {
+ (!this.props.CollectionView.props.isSelected() ? undefined :
+ SetupDrag(reference, () => props.Document, this.props.moveDocument, this.props.Document.schemaDoc ? "copy" : undefined)(e));
+ };
let applyToDoc = (doc: Doc, run: (args?: { [name: string]: any }) => any) => {
const res = run({ this: doc });
if (!res.success) return false;
@@ -232,7 +235,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
onPointerDown = (e: React.PointerEvent): void => {
if (e.button === 0 && !e.altKey && !e.ctrlKey && !e.metaKey) {
if (this.props.isSelected()) e.stopPropagation();
- else e.preventDefault();
}
}
@@ -282,13 +284,13 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
@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;
+ const selected = this.childDocs.length > this._selectedIndex ? this.childDocs[this._selectedIndex] : undefined;
+ let pdc = selected ? (this.previewScript && this.previewScript !== "this" ? FieldValue(Cast(selected[this.previewScript], Doc)) : selected) : undefined;
+ return pdc;
}
getPreviewTransform = (): Transform => this.props.ScreenToLocalTransform().translate(
- - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth, - this.borderWidth);
+ - this.borderWidth - this.DIVIDER_WIDTH - this.tableWidth, - this.borderWidth)
get documentKeysCheckList() {
@@ -331,13 +333,12 @@ 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
@@ -346,23 +347,38 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
<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}
- />
+ // let layoutDoc = this.previewDocument;
+ // let resolvedDataDoc = (layoutDoc !== this.props.DataDoc) ? this.props.DataDoc : undefined;
+ // if (layoutDoc && !(Cast(layoutDoc.layout, Doc) instanceof Doc) &&
+ // resolvedDataDoc && resolvedDataDoc !== layoutDoc) {
+ // // ... so change the layout to be an expanded view of the template layout. This allows the view override the template's properties and be referenceable as its own document.
+ // layoutDoc = Doc.expandTemplateLayout(layoutDoc, resolvedDataDoc);
+ // }
+
+ let layoutDoc = this.previewDocument ? Doc.expandTemplateLayout(this.previewDocument, this.props.DataDoc) : undefined;
+ return <div ref={this.createTarget}>
+ <CollectionSchemaPreview
+ Document={layoutDoc}
+ DataDocument={this.previewDocument !== this.props.DataDoc ? this.props.DataDoc : undefined}
+ childDocs={this.childDocs}
+ renderDepth={this.props.renderDepth}
+ width={this.previewWidth}
+ height={this.previewHeight}
+ getTransform={this.getPreviewTransform}
+ CollectionView={this.props.CollectionView}
+ moveDocument={this.props.moveDocument}
+ 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}
+ />
+ </div>;
}
@action
setPreviewScript = (script: string) => {
@@ -370,7 +386,6 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
render() {
- 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}>
@@ -384,21 +399,28 @@ export class CollectionSchemaView extends CollectionSubView(doc => doc) {
}
interface CollectionSchemaPreviewProps {
Document?: Doc;
+ DataDocument?: Doc;
+ childDocs?: Doc[];
+ fitToBox?: () => number[];
+ renderDepth: number;
width: () => number;
height: () => number;
- CollectionView: CollectionView | CollectionPDFView | CollectionVideoView;
+ CollectionView?: CollectionView | CollectionPDFView | CollectionVideoView;
getTransform: () => Transform;
addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
+ moveDocument: (document: Doc, target: Doc, addDoc: ((doc: Doc) => boolean)) => boolean;
removeDocument: (document: Doc) => boolean;
active: () => boolean;
whenActiveChanged: (isActive: boolean) => void;
- addDocTab: (document: Doc, where: string) => void;
+ addDocTab: (document: Doc, dataDoc: Doc | undefined, where: string) => void;
setPreviewScript: (script: string) => void;
previewScript?: string;
}
@observer
export class CollectionSchemaPreview extends React.Component<CollectionSchemaPreviewProps>{
+ private dropDisposer?: DragManager.DragDropDisposer;
+ _mainCont?: HTMLDivElement;
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 = () => {
@@ -408,49 +430,79 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
}
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);
+ protected createDropTarget = (ele: HTMLDivElement) => {
}
+ private createTarget = (ele: HTMLDivElement) => {
+ this._mainCont = ele;
+ this.dropDisposer && this.dropDisposer();
+ if (ele) {
+ this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+
@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;
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ let docDrag = de.data;
+ this.props.childDocs && this.props.childDocs.map(otherdoc => {
+ Doc.GetProto(otherdoc).layout = Doc.MakeDelegate(docDrag.draggedDocuments[0]);
});
+ e.stopPropagation();
}
+ return true;
+ }
+ private PanelWidth = () => this.nativeWidth ? this.nativeWidth * this.contentScaling() : this.props.width();
+ private PanelHeight = () => this.nativeHeight ? this.nativeHeight * this.contentScaling() : this.props.height();
+ private getTransform = () => this.props.getTransform().translate(-this.centeringOffset, 0).scale(1 / this.contentScaling());
+ get centeringOffset() { return this.nativeWidth ? (this.props.width() - this.nativeWidth * this.contentScaling()) / 2 : 0; }
+ @action
+ onPreviewScriptChange = (e: React.ChangeEvent<HTMLInputElement>) => {
+ this.props.setPreviewScript(e.currentTarget.value);
+ }
+ @computed get borderRounding() {
+ let br = StrCast(this.props.Document!.borderRounding);
+ if (br.endsWith("%")) {
+ let percent = Number(br.substr(0, br.length - 1)) / 100;
+ let nativeDim = Math.min(NumCast(this.props.Document!.nativeWidth), NumCast(this.props.Document!.nativeHeight));
+ let minDim = percent * (nativeDim ? nativeDim : Math.min(this.PanelWidth(), this.PanelHeight()));
+ return minDim;
+ }
+ return undefined;
}
render() {
- trace();
- console.log(this.props.Document);
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() }}>
+ <div ref={this.createTarget}><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)` }} /></div>;
+ 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)` }}>
- <DocumentView Document={this.props.Document} isTopMost={false} selectOnLoad={false}
- addDocument={this.props.addDocument} removeDocument={this.props.removeDocument}
+ <div className="collectionSchemaView-previewDoc"
+ style={{
+ transform: `translate(${this.centeringOffset}px, 0px)`,
+ borderRadius: this.borderRounding,
+ height: "100%"
+ }}>
+ <DocumentView
+ DataDoc={this.props.Document.layout instanceof Doc ? this.props.Document : this.props.DataDocument}
+ Document={this.props.Document}
+ fitToBox={this.props.fitToBox}
+ renderDepth={this.props.renderDepth + 1}
+ selectOnLoad={false}
+ addDocument={this.props.addDocument}
+ removeDocument={this.props.removeDocument}
+ moveDocument={this.props.moveDocument}
ScreenToLocalTransform={this.getTransform}
ContentScaling={this.contentScaling}
- PanelWidth={this.PanelWidth} PanelHeight={this.PanelHeight}
+ 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}
+ zoomToScale={emptyFunction}
+ getScale={returnOne}
/>
</div>)}
{input}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 4d84aaaa9..bc733f152 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -1,19 +1,6 @@
@import "../globalCssVariables";
-
.collectionStackingView {
- top: 0;
- left: 0;
- display: flex;
- flex-direction: column;
- width: 100%;
- position: absolute;
overflow-y: auto;
- 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;
.collectionStackingView-docView-container {
width: 45%;
@@ -29,13 +16,26 @@
align-items: center;
}
- .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid{
+ .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid {
width:100%;
height:100%;
position: absolute;
- }
- .collectionStackingView-masonryGrid {
display:grid;
+ top: 0;
+ left: 0;
+ width: 100%;
+ position: absolute;
+ }
+ .collectionStackingView-masonrySingle {
+ width:100%;
+ height:100%;
+ position: absolute;
+ display:flex;
+ flex-direction: column;
+ top: 0;
+ left: 0;
+ width: 100%;
+ position: absolute;
}
.collectionStackingView-description {
@@ -48,4 +48,24 @@
background: $dark-color;
color: $light-color;
}
+
+ .collectionStackingView-columnDragger {
+ width: 15;
+ height: 15;
+ position: absolute;
+ margin-left: -5;
+ }
+
+
+ .collectionStackingView-columnDoc,
+ .collectionStackingView-masonryDoc {
+ margin-left: auto;
+ margin-right: auto;
+ }
+
+ .collectionStackingView-masonryDoc {
+ transform-origin: top left;
+ grid-column-end: span 1;
+ height: 100%;
+ }
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index da7ea50c6..416c536f6 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,40 +1,44 @@
import React = require("react");
-import { action, computed, IReactionDisposer, reaction } from "mobx";
+import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
+import { action, computed, IReactionDisposer, reaction, untracked } 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 { BoolCast, NumCast, Cast } from "../../../new_fields/Types";
+import { emptyFunction, Utils } from "../../../Utils";
+import { ContextMenu } from "../ContextMenu";
import { CollectionSchemaPreview } from "./CollectionSchemaView";
import "./CollectionStackingView.scss";
import { CollectionSubView } from "./CollectionSubView";
+import { undoBatch } from "../../util/UndoManager";
+import { DragManager } from "../../util/DragManager";
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
_masonryGridRef: HTMLDivElement | null = null;
+ _draggerRef = React.createRef<HTMLDivElement>();
_heightDisposer?: IReactionDisposer;
- get gridGap() { return 10; }
- get gridSize() { return 20; }
- get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); }
- get columnWidth() { return this.singleColumn ? this.props.PanelWidth() - 4 * this.gridGap : NumCast(this.props.Document.columnWidth, 250); }
+ _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() / (this.props as any).ContentScaling() - 2 * this.xMargin) : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); }
+ @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); }
+ 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.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized]), this.columnWidth, this.props.PanelHeight()],
- () => {
- if (this.singleColumn) {
- this.props.Document.height = this.childDocs.filter(d => !d.isMinimized).reduce((height, d) => {
- let hgt = d[HeightSym]();
- let wid = d[WidthSym]();
- let nw = NumCast(d.nativeWidth);
- let nh = NumCast(d.nativeHeight);
- if (nw && nh) hgt = nh / nw * Math.min(this.columnWidth, wid);
- return height + hgt + 2 * this.gridGap;
- }, this.gridGap * 2);
- }
- }, { fireImmediately: true });
+ 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])],
+ () => this.singleColumn &&
+ (this.props.Document.height = this.filteredChildren.reduce((height, d, i) =>
+ height + this.singleColDocHeight(d) + (i === this.filteredChildren.length - 1 ? this.yMargin : this.gridGap), this.yMargin))
+ , { fireImmediately: true });
}
componentWillUnmount() {
if (this._heightDisposer) this._heightDisposer();
@@ -42,136 +46,203 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@action
moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => {
- this.props.removeDocument(doc);
- addDocument(doc);
- return true;
+ return this.props.removeDocument(doc) && addDocument(doc);
}
- getDocTransform(doc: Doc, dref: HTMLDivElement) {
- let { scale, translateX, translateY } = Utils.GetScreenTransform(dref);
+ getSingleDocTransform(doc: Doc, ind: number, width: number) {
+ let localY = this.filteredChildren.reduce((height, d, i) =>
+ height + (i < ind ? this.singleColDocHeight(d) + this.gridGap : 0), this.yMargin);
+ let translate = this.props.ScreenToLocalTransform().inverse().transformPoint((this.props.PanelWidth() - width) / 2, localY);
let outerXf = Utils.GetScreenTransform(this._masonryGridRef!);
- let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+ let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translate[0], outerXf.translateY - translate[1]);
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() {
- return this.childDocs.filter(d => !d.isMinimized).map((d, i) => {
- let dref = React.createRef<HTMLDivElement>();
- let script = undefined;
- let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
- let margin = colWidth() < this.columnWidth ? "auto" : undefined;
- let rowHeight = () => {
- let hgt = d[HeightSym]();
- let nw = NumCast(d.nativeWidth);
- let nh = NumCast(d.nativeHeight);
- if (nw && nh) hgt = nh / nw * colWidth();
- return hgt;
- }
- let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]());
- return <div className="collectionStackingView-masonryDoc"
+ return this.filteredChildren.map((d, i) => {
+ let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
+ let width = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
+ let height = () => this.singleColDocHeight(layoutDoc);
+ let dxf = () => this.getSingleDocTransform(layoutDoc, i, width()).scale(this.columnWidth / d[WidthSym]());
+ let gap = i === 0 ? 0 : this.gridGap;
+ return <div className="collectionStackingView-columnDoc"
key={d[Id]}
- ref={dref}
- style={{ marginTop: `${i ? 2 * this.gridGap : 0}px`, width: colWidth(), height: rowHeight(), marginLeft: margin, marginRight: margin }} >
+ style={{ width: width(), display: "inline-block", marginTop: gap, height: `${height() / (this.props.Document[HeightSym]() - 2 * this.yMargin) * 100}%` }} >
<CollectionSchemaPreview
- Document={d}
- width={colWidth}
- height={rowHeight}
+ Document={layoutDoc}
+ DataDocument={d !== this.props.DataDoc ? this.props.DataDoc : undefined}
+ renderDepth={this.props.renderDepth}
+ width={width}
+ height={height}
getTransform={dxf}
CollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
removeDocument={this.props.removeDocument}
active={this.props.active}
whenActiveChanged={this.props.whenActiveChanged}
addDocTab={this.props.addDocTab}
setPreviewScript={emptyFunction}
- previewScript={script}>
+ previewScript={undefined}>
</CollectionSchemaPreview>
</div>;
});
}
+ 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);
+ }
+ docXfs: any[] = [];
@computed
get children() {
- return this.childDocs.filter(d => !d.isMinimized).map(d => {
+ this.docXfs.length = 0;
+ return this.filteredChildren.map((d, i) => {
+ let aspect = d.nativeHeight ? NumCast(d.nativeWidth) / NumCast(d.nativeHeight) : undefined;
let dref = React.createRef<HTMLDivElement>();
- let dxf = () => this.getDocTransform(d, dref.current!);
- let colSpan = Math.ceil(Math.min(d[WidthSym](), this.columnWidth + this.gridGap) / (this.gridSize + this.gridGap));
- 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.
- }
+ let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]());
+ let width = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth;
+ let height = () => aspect ? width() / aspect : d[HeightSym]();
+ let rowSpan = Math.ceil((height() + this.gridGap) / (this._gridSize + this.gridGap));
+ this.docXfs.push({ dxf: dxf, width: width, height: height });
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 ${colSpan}`,
- transform: `scale(${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())}, ${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())})`
- }} >
- <DocumentView key={d[Id]} Document={d}
+ style={{ gridRowEnd: `span ${rowSpan}` }} >
+ <CollectionSchemaPreview
+ Document={d}
+ DataDocument={this.props.Document.layout instanceof Doc ? this.props.Document : this.props.DataDoc}
+ renderDepth={this.props.renderDepth}
+ CollectionView={this.props.CollectionView}
addDocument={this.props.addDocument}
+ moveDocument={this.props.moveDocument}
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}
+ getTransform={dxf}
+ width={width}
+ height={height}
+ active={this.props.active}
addDocTab={this.props.addDocTab}
- bringToFront={emptyFunction}
whenActiveChanged={this.props.whenActiveChanged}
- collapseToPoint={this.collapseToPoint}
- />
+ setPreviewScript={emptyFunction}
+ previewScript={undefined}>
+ </CollectionSchemaPreview>
</div>);
- })
+ });
+ }
+
+ _columnStart: number = 0;
+ columnDividerDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ e.preventDefault();
+ document.addEventListener("pointermove", this.onDividerMove);
+ document.addEventListener('pointerup', this.onDividerUp);
+ this._columnStart = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+ }
+ @action
+ onDividerMove = (e: PointerEvent): void => {
+ let dragPos = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY)[0];
+ let delta = dragPos - this._columnStart;
+ this._columnStart = dragPos;
+
+ this.props.Document.columnWidth = this.columnWidth + delta;
+ }
+
+ @action
+ onDividerUp = (e: PointerEvent): void => {
+ document.removeEventListener("pointermove", this.onDividerMove);
+ document.removeEventListener('pointerup', this.onDividerUp);
+ }
+
+ @computed get columnDragger() {
+ return <div className="collectionStackingView-columnDragger" onPointerDown={this.columnDividerDown} ref={this._draggerRef} style={{ left: `${this.columnWidth + this.xMargin}px` }} >
+ <FontAwesomeIcon icon={"caret-down"} />
+ </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"
+ });
+ }
+ }
+
+ @undoBatch
+ @action
+ drop = (e: Event, de: DragManager.DropEvent) => {
+ let targInd = -1;
+ let where = [de.x, de.y];
+ if (de.data instanceof DragManager.DocumentDragData) {
+ this.docXfs.map((cd, i) => {
+ let pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
+ let pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
+ if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
+ targInd = i;
+ }
+ });
+ }
+ if (super.drop(e, de)) {
+ let newDoc = de.data.droppedDocuments[0];
+ let docs = this.childDocList;
+ if (docs) {
+ if (targInd === -1) targInd = docs.length;
+ else targInd = docs.indexOf(this.filteredChildren[targInd]);
+ let srcInd = docs.indexOf(newDoc);
+ docs.splice(srcInd, 1);
+ docs.splice(targInd > srcInd ? targInd - 1 : targInd, 0, newDoc);
+ }
+ }
+ return false;
+ }
+ @undoBatch
+ @action
+ onDrop = (e: React.DragEvent): void => {
+ let where = [e.clientX, e.clientY];
+ let targInd = -1;
+ this.docXfs.map((cd, i) => {
+ let pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap);
+ let pos1 = cd.dxf().inverse().transformPoint(cd.width(), cd.height());
+ if (where[0] > pos[0] && where[0] < pos1[0] && where[1] > pos[1] && where[1] < pos1[1]) {
+ targInd = i;
+ }
+ });
+ super.onDrop(e, {}, () => {
+ if (targInd !== -1) {
+ let newDoc = this.childDocs[this.childDocs.length - 1];
+ let docs = this.childDocList;
+ if (docs) {
+ docs.splice(docs.length - 1, 1);
+ docs.splice(targInd, 0, newDoc);
+ }
+ }
+ });
}
render() {
- let leftMargin = 2 * this.gridGap;
- let topMargin = 2 * this.gridGap;
- let itemCols = Math.ceil(this.columnWidth / (this.gridSize + this.gridGap));
- let cells = Math.floor((this.props.PanelWidth() - leftMargin) / (itemCols * (this.gridSize + this.gridGap)));
+ let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.filteredChildren.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" style={{ height: "100%" }}
- ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()}>
+ <div className="collectionStackingView" ref={this.createRef} onDrop={this.onDrop.bind(this)} onContextMenu={this.onContextMenu} onWheel={(e: React.WheelEvent) => e.stopPropagation()} >
<div className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`}
style={{
- padding: `${topMargin}px 0px 0px ${leftMargin}px`,
- width: this.singleColumn ? "100%" : `${cells * itemCols * (this.gridSize + this.gridGap) + leftMargin}`,
+ 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%",
- overflow: "hidden",
- marginRight: "auto",
position: "relative",
gridGap: this.gridGap,
- gridTemplateColumns: this.singleColumn ? undefined : `repeat(auto-fill, minmax(${this.gridSize}px,1fr))`,
- gridAutoRows: this.singleColumn ? undefined : `${this.gridSize}px`
+ gridTemplateColumns: this.singleColumn ? undefined : templatecols,
+ gridAutoRows: this.singleColumn ? undefined : `${this._gridSize}px`
}}
>
{this.singleColumn ? this.singleColumnChildren : this.children}
+ {this.singleColumn ? (null) : this.columnDragger}
</div>
</div>
);
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 36e276d13..a7614b605 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -1,23 +1,25 @@
-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, computed } from "mobx";
+import * as rp from 'request-promise';
+import CursorField from "../../../new_fields/CursorField";
+import { Doc, DocListCast, Opt } from "../../../new_fields/Doc";
+import { Id } from "../../../new_fields/FieldSymbols";
+import { List } from "../../../new_fields/List";
+import { listSpec } from "../../../new_fields/Schema";
+import { BoolCast, Cast } 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 { FormattedTextBox } from "../nodes/FormattedTextBox";
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 CursorField from "../../../new_fields/CursorField";
-import { DocumentManager } from "../../util/DocumentManager";
+import { CollectionView } from "./CollectionView";
+import React = require("react");
+import { MainView } from "../MainView";
export interface CollectionViewProps extends FieldViewProps {
addDocument: (document: Doc, allowDuplicates?: boolean) => boolean;
@@ -35,9 +37,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
class CollectionSubView extends DocComponent<SubCollectionViewProps, T>(schemaCtor) {
private dropDisposer?: DragManager.DragDropDisposer;
protected createDropTarget = (ele: HTMLDivElement) => {
- if (this.dropDisposer) {
- this.dropDisposer();
- }
+ this.dropDisposer && this.dropDisposer();
if (ele) {
this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
}
@@ -46,10 +46,18 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
this.createDropTarget(ele);
}
+ @computed get extensionDoc() { return Doc.resolvedFieldDataDoc(BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, this.props.fieldExt); }
+
get childDocs() {
+ let self = this;
+ //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((BoolCast(this.props.Document.isTemplate) ? this.extensionDoc : this.props.Document)[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey]);
+ }
+ get childDocList() {
//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]);
+ return Cast((BoolCast(this.props.Document.isTemplate) ? this.extensionDoc : this.props.Document)[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey], listSpec(Doc));
}
@action
@@ -60,10 +68,17 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
let email = CurrentUserUtils.email;
let pos = { x: position[0], y: position[1] };
if (id && email) {
- const proto = await doc.proto;
+ const proto = Doc.GetProto(doc);
if (!proto) {
return;
}
+ // The following conditional detects a recurring bug we've seen on the server
+ if (proto[Id] === "collectionProto") {
+ alert("COLLECTION PROTO CURSOR ISSUE DETECTED! Check console for more info...");
+ console.log(doc);
+ console.log(proto);
+ throw new Error(`AHA! You were trying to set a cursor on a collection's proto, which is the original collection proto! Look at the two previously printed lines for document values!`);
+ }
let cursors = Cast(proto.cursors, listSpec(CursorField));
if (!cursors) {
proto.cursors = cursors = new List<CursorField>();
@@ -81,79 +96,29 @@ 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;
}
- return false;
- }
-
- protected async getDocumentFromType(type: string, path: string, options: DocumentOptions): Promise<Opt<Doc>> {
- let ctor: ((path: string, options: DocumentOptions) => (Doc | Promise<Doc | undefined>)) | undefined = undefined;
- if (type.indexOf("image") !== -1) {
- ctor = Docs.Create.ImageDocument;
- }
- if (type.indexOf("video") !== -1) {
- ctor = Docs.Create.VideoDocument;
- }
- if (type.indexOf("audio") !== -1) {
- ctor = Docs.Create.AudioDocument;
- }
- if (type.indexOf("pdf") !== -1) {
- ctor = Docs.Create.PdfDocument;
- options.nativeWidth = 1200;
- }
- if (type.indexOf("excel") !== -1) {
- ctor = Docs.Create.DBDocument;
- options.dropAction = "copy";
- }
- if (type.indexOf("html") !== -1) {
- if (path.includes(window.location.hostname)) {
- let s = path.split('/');
- let id = s[s.length - 1];
- DocServer.getRefField(id).then(field => {
- if (field instanceof Doc) {
- let alias = Doc.MakeAlias(field);
- alias.x = options.x || 0;
- alias.y = options.y || 0;
- alias.width = options.width || 300;
- alias.height = options.height || options.width || 300;
- this.props.addDocument(alias, false);
- }
- });
- return undefined;
- }
- ctor = Docs.Create.WebDocument;
- options = { height: options.width, ...options, title: path, nativeWidth: undefined };
+ else if (de.data instanceof DragManager.AnnotationDragData) {
+ e.stopPropagation();
+ return this.props.addDocument(de.data.dropDocument);
}
- return ctor ? ctor(path, options) : undefined;
+ return false;
}
@undoBatch
@action
- protected onDrop(e: React.DragEvent, options: DocumentOptions): void {
+ protected onDrop(e: React.DragEvent, options: DocumentOptions, completed?: () => 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;
@@ -167,17 +132,50 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
e.stopPropagation();
e.preventDefault();
- if (html && html.indexOf(document.location.origin)) { // prosemirror text containing link to dash document
- let start = html.indexOf(window.location.origin);
- let path = html.substr(start, html.length - start);
- let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.Util.prepend("/doc/"), "").split("?")[0];
- DocServer.getRefField(docid).then(f => (f instanceof Doc) && this.props.addDocument(f, false));
+ 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.Create.WebDocument(href, options));
+ }
+ } else if (text) {
+ this.props.addDocument && this.props.addDocument(Docs.Create.TextDocument({ ...options, width: 100, height: 25, documentText: "@@@" + text }), false);
+ }
return;
}
- if (html && html.indexOf("<img") !== 0 && !html.startsWith("<a")) {
- let htmlDoc = Docs.Create.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
- this.props.addDocument(htmlDoc, false);
- return;
+ if (html && !html.startsWith("<a")) {
+ let tags = html.split("<");
+ if (tags[0] === "") tags.splice(0, 1);
+ let img = tags[0].startsWith("img") ? tags[0] : tags.length > 1 && tags[1].startsWith("img") ? tags[1] : "";
+ if (img) {
+ let split = img.split("src=\"")[1].split("\"")[0];
+ let doc = Docs.Create.ImageDocument(split, { ...options, width: 300 });
+ this.props.addDocument(doc, false);
+ return;
+ } else {
+ let path = window.location.origin + "/doc/";
+ if (text.startsWith(path)) {
+ let docid = text.replace(DocServer.prepend("/doc/"), "").split("?")[0];
+ 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 {
+ let htmlDoc = Docs.Create.HtmlDocument(html, { ...options, width: 300, height: 300, documentText: text });
+ this.props.addDocument(htmlDoc, false);
+ }
+ return;
+ }
}
if (text && text.indexOf("www.youtube.com/watch") !== -1) {
const url = text.replace("youtube.com/watch?v=", "youtube.com/embed/");
@@ -194,11 +192,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
if (item.kind === "string" && item.type.indexOf("uri") !== -1) {
let str: string;
let prom = new Promise<string>(resolve => e.dataTransfer.items[i].getAsString(resolve))
- .then(action((s: string) => rp.head(DocServer.Util.prepend(RouteStore.corsProxy + "/" + (str = s)))))
+ .then(action((s: string) => rp.head(DocServer.prepend(RouteStore.corsProxy + "/" + (str = s)))))
.then(result => {
let type = result["content-type"];
if (type) {
- this.getDocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
+ Docs.Get.DocumentFromType(type, str, { ...options, width: 300, nativeWidth: 300 })
.then(doc => doc && this.props.addDocument(doc, false));
}
});
@@ -219,10 +217,9 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
body: formData
}).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: 300, width: 300, title: dropFileName });
-
- docPromise.then(doc => doc && this.props.addDocument(doc));
+ let full = { ...options, nativeWidth: 300, width: 300, title: dropFileName };
+ let path = DocServer.prepend(file);
+ Docs.Get.DocumentFromType(type, path, full).then(doc => doc && this.props.addDocument(doc));
}));
});
promises.push(prom);
@@ -230,7 +227,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
}
if (promises.length) {
- Promise.all(promises).finally(() => batch.end());
+ Promise.all(promises).finally(() => { completed && completed(); batch.end(); });
} else {
batch.end();
}
diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss
index 458030b28..5205f4313 100644
--- a/src/client/views/collections/CollectionTreeView.scss
+++ b/src/client/views/collections/CollectionTreeView.scss
@@ -4,60 +4,33 @@
border-width: $COLLECTION_BORDER_WIDTH;
border-color: transparent;
border-style: solid;
- border-radius: $border-radius;
+ border-radius: inherit;
box-sizing: border-box;
height: 100%;
- padding: 20px;
+ padding-top: 20px;
padding-left: 10px;
padding-right: 0px;
background: $light-color-secondary;
font-size: 13px;
- overflow: scroll;
+ overflow: auto;
ul {
list-style: none;
padding-left: 20px;
}
- li {
- margin: 5px 0;
- }
-
.no-indent {
padding-left: 0;
}
.bullet {
- float: left;
position: relative;
width: 15px;
- display: block;
color: $intermediate-color;
- margin-top: 3px;
+ margin-top: 4px;
transform: scale(1.3, 1.3);
}
-
- .docContainer {
- margin-left: 10px;
- display: block;
- // width:100%;//width: max-content;
- }
-
- .docContainer:hover {
- .treeViewItem-openRight {
- display: inline-block;
- height:13px;
- // display: inline;
- svg {
- display:block;
- padding:0px;
- margin: 0px;
- }
- }
- }
-
-
.editableView-container {
font-weight: bold;
}
@@ -70,31 +43,72 @@
display: inline;
}
- .treeViewItem-openRight {
- margin-left: 5px;
- display: none;
- }
-
- .docContainer:hover {
- .delete-button {
- display: inline;
- // width: auto;
- }
- }
-
- .coll-title {
- width: max-content;
+ .editableView-input, .editableView-container-editing {
display: block;
+ text-overflow: ellipsis;
font-size: 24px;
+ white-space: nowrap;
}
-
- .collection-child {
- margin-top: 10px;
- margin-bottom: 10px;
- }
-
+}
+.collectionTreeView-keyHeader {
+ font-style: italic;
+ font-size: 8pt;
+ margin-left: 3px;
+ display:none;
+ background: lightgray;
+}
+
+.collectionTreeView-subtitle {
+ font-style: italic;
+ font-size: 8pt;
+ color: $intermediate-color;
+}
+
+.docContainer {
+ position: relative;
+ text-overflow: ellipsis;
+ white-space: pre-wrap;
+ overflow: hidden;
+ // width:100%;//width: max-content;
+
+}
+.treeViewItem-openRight {
+ display: none;
+}
+
+.treeViewItem-border {
+ display:inherit;
+ border-left: dashed 1px #00000042;
+}
+
+.treeViewItem-header:hover {
.collectionTreeView-keyHeader {
- font-style: italic;
- font-size: 8pt;
+ display:inherit;
+ }
+ .treeViewItem-openRight {
+ display: inline-block;
+ height:13px;
+ margin-top:2px;
+ margin-left: 5px;
+ // display: inline;
+ svg {
+ display:block;
+ padding:0px;
+ margin: 0px;
+ }
}
+}
+
+.treeViewItem-header {
+ border: transparent 1px solid;
+ display:flex;
+}
+.treeViewItem-header-above {
+ border-top: black 1px solid;
+}
+.treeViewItem-header-below {
+ border-bottom: black 1px solid;
+}
+.treeViewItem-header-inside {
+ 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 424042fee..ef340d770 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,251 +1,558 @@
-import { IconProp, library } from '@fortawesome/fontawesome-svg-core';
-import { faCaretDown, faCaretRight, faTrashAlt, faAngleRight } from '@fortawesome/free-solid-svg-icons';
+import { library } from '@fortawesome/fontawesome-svg-core';
+import { faAngleRight, faCamera, faExpand, faTrash, faBell, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, observable, trace } from "mobx";
+import { action, computed, observable, trace } from "mobx";
import { observer } from "mobx-react";
-import { DragManager, SetupDrag, dropActionType } from "../../util/DragManager";
-import { EditableView } from "../EditableView";
-import { CollectionSubView } from "./CollectionSubView";
-import "./CollectionTreeView.scss";
-import React = require("react");
-import { Document, listSpec } from '../../../new_fields/Schema';
-import { Cast, StrCast, BoolCast, FieldValue, NumCast } from '../../../new_fields/Types';
-import { Doc, DocListCast } from '../../../new_fields/Doc';
+import { Doc, DocListCast, HeightSym, WidthSym, Opt } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
-import { ContextMenu } from '../ContextMenu';
-import { undoBatch } from '../../util/UndoManager';
-import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
-import { CollectionDockingView } from './CollectionDockingView';
+import { List } from '../../../new_fields/List';
+import { Document, listSpec } from '../../../new_fields/Schema';
+import { BoolCast, Cast, NumCast, StrCast } from '../../../new_fields/Types';
+import { emptyFunction, Utils } from '../../../Utils';
+import { Docs, DocUtils, DocTypes } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
-import { Docs } from '../../documents/Documents';
+import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
+import { SelectionManager } from '../../util/SelectionManager';
+import { Transform } from '../../util/Transform';
+import { undoBatch } from '../../util/UndoManager';
+import { ContextMenu } from '../ContextMenu';
+import { EditableView } from "../EditableView";
import { MainView } from '../MainView';
+import { Templates } from '../Templates';
import { CollectionViewType } from './CollectionBaseView';
+import { CollectionDockingView } from './CollectionDockingView';
+import { CollectionSchemaPreview } from './CollectionSchemaView';
+import { CollectionSubView } from "./CollectionSubView";
+import "./CollectionTreeView.scss";
+import React = require("react");
+import { LinkManager } from '../../util/LinkManager';
export interface TreeViewProps {
document: Doc;
- deleteDoc: (doc: Doc) => void;
+ dataDoc?: Doc;
+ containingCollection: Doc;
+ renderDepth: number;
+ deleteDoc: (doc: Doc) => boolean;
moveDocument: DragManager.MoveFunction;
dropAction: "alias" | "copy" | undefined;
- addDocTab: (doc: Doc, where: string) => void;
-}
-
-export enum BulletType {
- Collapsed,
- Collapsible,
- List
+ addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void;
+ panelWidth: () => number;
+ panelHeight: () => number;
+ addDocument: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean;
+ indentDocument?: () => void;
+ ScreenToLocalTransform: () => Transform;
+ outerXf: () => { translateX: number, translateY: number };
+ treeViewId: string;
+ parentKey: string;
+ active: () => boolean;
}
library.add(faTrashAlt);
library.add(faAngleRight);
+library.add(faBell);
+library.add(faTrash);
+library.add(faCamera);
+library.add(faExpand);
library.add(faCaretDown);
library.add(faCaretRight);
+library.add(faCaretSquareDown);
+library.add(faCaretSquareRight);
@observer
/**
* 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;
+ private _dref = React.createRef<HTMLDivElement>();
+ @observable __chosenKey: string = "";
+ @computed get _chosenKey() { return this.__chosenKey ? this.__chosenKey : this.fieldKey; }
+ @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
@observable _collapsed: boolean = true;
- @undoBatch delete = () => this.props.deleteDoc(this.props.document);
-
- @undoBatch openRight = async () => {
- if (this.props.document.dockingConfig) {
- MainView.Instance.openWorkspace(this.props.document);
- } else {
- this.props.addDocTab(this.props.document, "openRight");
+ @computed get fieldKey() {
+ let keys = Array.from(Object.keys(this.resolvedDataDoc));
+ if (this.resolvedDataDoc.proto instanceof Doc) {
+ keys.push(...Array.from(Object.keys(this.resolvedDataDoc.proto)));
+ while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
+ }
+ let keyList: string[] = [];
+ keys.map(key => {
+ let docList = Cast(this.resolvedDataDoc[key], listSpec(Doc));
+ if (docList && docList.length > 0) {
+ keyList.push(key);
+ }
+ });
+ let layout = StrCast(this.props.document.layout);
+ if (layout.indexOf("fieldKey={\"") !== -1) {
+ return layout.split("fieldKey={\"")[1].split("\"")[0];
}
+ return keyList.length ? keyList[0] : "data";
}
- get children() {
- return Cast(this.props.document.data, listSpec(Doc), []); // bcz: needed? .filter(doc => FieldValue(doc));
+ @computed get resolvedDataDoc() { return BoolCast(this.props.document.isTemplate) && this.props.dataDoc ? this.props.dataDoc : this.props.document; }
+
+ protected createTreeDropTarget = (ele: HTMLDivElement) => {
+ this._treedropDisposer && this._treedropDisposer();
+ if (ele) {
+ this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } });
+ }
}
- onPointerDown = (e: React.PointerEvent) => {
+ @undoBatch delete = () => this.props.deleteDoc(this.resolvedDataDoc);
+ @undoBatch openRight = async () => this.props.addDocTab(this.props.document, undefined, "onRight");
+
+ onPointerDown = (e: React.PointerEvent) => e.stopPropagation();
+ onPointerEnter = (e: React.PointerEvent): void => {
+ this.props.active() && (this.props.document.libraryBrush = true);
+ if (e.buttons === 1 && SelectionManager.GetIsDragging()) {
+ 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._collapsed);
+ this._header!.current!.className = "treeViewItem-header";
+ if (inside) 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();
}
@action
- remove = (document: Document, key: string) => {
- let children = Cast(this.props.document[key], listSpec(Doc), []);
- if (children) {
+ remove = (document: Document, key: string): boolean => {
+ let children = Cast(this.resolvedDataDoc[key], listSpec(Doc), []);
+ if (children.indexOf(document) !== -1) {
children.splice(children.indexOf(document), 1);
+ return true;
}
+ return false;
}
@action
- move: DragManager.MoveFunction = (document, target, addDoc) => {
- if (this.props.document === target) {
- return true;
- }
- //TODO This should check if it was removed
- this.remove(document, "data");
- return addDoc(document);
+ move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => {
+ return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc);
}
+ @action
+ indent = () => this.props.addDocument(this.props.document) && this.delete()
- renderBullet(type: BulletType) {
- let onClicked = action(() => this._collapsed = !this._collapsed);
- let bullet: IconProp | undefined = undefined;
- switch (type) {
- case BulletType.Collapsed: bullet = "caret-right"; break;
- case BulletType.Collapsible: bullet = "caret-down"; break;
- }
- return <div className="bullet" onClick={onClicked}>{bullet ? <FontAwesomeIcon icon={bullet} /> : ""} </div>;
+ renderBullet() {
+ let docList = Cast(this.resolvedDataDoc[this.fieldKey], listSpec(Doc));
+ let doc = Cast(this.resolvedDataDoc[this.fieldKey], Doc);
+ let isDoc = doc instanceof Doc || docList;
+ return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)}>
+ {<FontAwesomeIcon icon={this._collapsed ? (isDoc ? "caret-square-right" : "caret-right") : (isDoc ? "caret-square-down" : "caret-down")} />}
+ </div>;
}
- @action
- onMouseEnter = () => {
- this._isOver = true;
- }
- @observable _isOver: boolean = false;
- @action
- onMouseLeave = () => {
- this._isOver = false;
+ static loadId = "";
+ editableView = (key: string, style?: string) => (<EditableView
+ oneLine={true}
+ display={"inline"}
+ editing={this.resolvedDataDoc[Id] === TreeView.loadId}
+ contents={StrCast(this.props.document[key])}
+ height={36}
+ fontStyle={style}
+ fontSize={12}
+ GetValue={() => StrCast(this.props.document[key])}
+ SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc)[key] = value) ? true : true}
+ OnFillDown={(value: string) => {
+ Doc.GetProto(this.resolvedDataDoc)[key] = value;
+ let doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ TreeView.loadId = doc[Id];
+ return this.props.addDocument(doc);
+ }}
+ OnTab={() => this.props.indentDocument && this.props.indentDocument()}
+ />)
+
+ @computed get keyList() {
+ let keys = Array.from(Object.keys(this.resolvedDataDoc));
+ if (this.resolvedDataDoc.proto instanceof Doc) {
+ keys.push(...Array.from(Object.keys(this.resolvedDataDoc.proto)));
+ }
+ let keyList: string[] = keys.reduce((l, key) => {
+ let listspec = DocListCast(this.resolvedDataDoc[key]);
+ if (listspec && listspec.length) return [...l, key];
+ return l;
+ }, [] as string[]);
+ keys.map(key => Cast(this.resolvedDataDoc[key], Doc) instanceof Doc && keyList.push(key));
+ if (LinkManager.Instance.getAllRelatedLinks(this.props.document).length > 0) keyList.push("links");
+ if (keyList.indexOf(this.fieldKey) !== -1) {
+ keyList.splice(keyList.indexOf(this.fieldKey), 1);
+ }
+ keyList.splice(0, 0, this.fieldKey);
+ return keyList.filter((item, index) => keyList.indexOf(item) >= index);
}
/**
* 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 editableView = (titleString: string) =>
- (<EditableView
- oneLine={!this._isOver ? true : false}
- display={"inline"}
- contents={titleString}
- height={36}
- GetValue={() => StrCast(this.props.document.title)}
- SetValue={(value: string) => {
- let target = this.props.document.proto ? this.props.document.proto : this.props.document;
- target.title = value;
- return true;
- }}
- />);
- let dataDocs = CollectionDockingView.TopLevel ? Cast(CollectionDockingView.TopLevel.props.Document.data, listSpec(Doc), []) : [];
- let openRight = dataDocs && dataDocs.indexOf(this.props.document) !== -1 ? (null) : (
+ let onItemDown = SetupDrag(reference, () => this.resolvedDataDoc, this.move, this.props.dropAction, this.props.treeViewId, true);
+
+ let headerElements = (
+ <span className="collectionTreeView-keyHeader" key={this._chosenKey}
+ onPointerDown={action(() => {
+ let ind = this.keyList.indexOf(this._chosenKey);
+ ind = (ind + 1) % this.keyList.length;
+ this.__chosenKey = this.keyList[ind];
+ })} >
+ {this._chosenKey}
+ </span>);
+ let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : [];
+ let openRight = dataDocs && dataDocs.indexOf(this.resolvedDataDoc) !== -1 ? (null) : (
<div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}>
<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}>
- {editableView(StrCast(this.props.document.title))}
- {openRight}
+ return <>
+ <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
+ style={{
+ background: BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0",
+ outline: BoolCast(this.props.document.workspaceBrush, false) ? "dashed 1px #06123232" : undefined,
+ pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none"
+ }}
+ >
+ {this.editableView("title")}
{/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */}
- </div >);
+ </div >
+ {headerElements}
+ {openRight}
+ </>;
}
onWorkspaceContextMenu = (e: React.MouseEvent): void => {
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.Create.KVPDocument(this.props.document, { width: 300, height: 300 }), "onRight"), icon: "layer-group" });
+ ContextMenu.Instance.addItem({ description: (BoolCast(this.props.document.embed) ? "Collapse" : "Expand") + " inline", event: () => this.props.document.embed = !BoolCast(this.props.document.embed), icon: "expand" });
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: "Open Tab", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "inTab"), icon: "folder" });
+ ContextMenu.Instance.addItem({ description: "Open Right", event: () => this.props.addDocTab(this.props.document, this.resolvedDataDoc, "onRight"), icon: "caret-square-right" });
+ if (DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).length) {
+ ContextMenu.Instance.addItem({ description: "Focus", event: () => DocumentManager.Instance.getDocumentViews(this.resolvedDataDoc).map(view => view.props.focus(this.props.document, true)), icon: "camera" });
}
- ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+ ContextMenu.Instance.addItem({ description: "Delete Item", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" });
} else {
- ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)) });
+ ContextMenu.Instance.addItem({ description: "Open as Workspace", event: undoBatch(() => MainView.Instance.openWorkspace(this.resolvedDataDoc)), icon: "caret-square-right" });
+ ContextMenu.Instance.addItem({ description: "Delete Workspace", event: undoBatch(() => this.props.deleteDoc(this.props.document)), icon: "trash-alt" });
}
- ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.Create.KVPDocument(this.props.document, { width: 300, height: 300 }); this.props.addDocTab(kvp, this.props.dataDoc ? this.props.dataDoc : kvp, "onRight"); }, icon: "layer-group" });
+ ContextMenu.Instance.displayMenu(e.pageX > 156 ? e.pageX - 156 : 0, e.pageY - 15);
e.stopPropagation();
+ e.preventDefault();
}
}
- onPointerEnter = (e: React.PointerEvent): void => { this.props.document.libraryBrush = true; };
- onPointerLeave = (e: React.PointerEvent): void => { this.props.document.libraryBrush = false; };
-
- render() {
- let bulletType = BulletType.List;
- let contentElement: (JSX.Element | null)[] = [];
- let keys = Array.from(Object.keys(this.props.document));
- if (this.props.document.proto instanceof Doc) {
- keys.push(...Array.from(Object.keys(this.props.document.proto)));
- while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1);
+ @undoBatch
+ 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._collapsed);
+ if (de.data instanceof DragManager.LinkDragData) {
+ let sourceDoc = de.data.linkSourceDocument;
+ let destDoc = this.props.document;
+ DocUtils.MakeLink(sourceDoc, destDoc);
+ e.stopPropagation();
}
- keys.map(key => {
- let docList = DocListCast(this.props.document[key]);
- let doc = Cast(this.props.document[key], Doc);
- if (doc instanceof Doc || docList.length) {
- 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, this.props.addDocTab)}
- </div>
- </ul >);
- } else {
- bulletType = BulletType.Collapsed;
+ if (de.data instanceof DragManager.DocumentDragData) {
+ e.stopPropagation();
+ if (de.data.draggedDocuments[0] === this.props.document) return true;
+ let addDoc = (doc: Doc) => this.props.addDocument(doc, this.resolvedDataDoc, before);
+ if (inside) {
+ let docList = Cast(this.resolvedDataDoc.data, listSpec(Doc));
+ if (docList !== undefined) {
+ addDoc = (doc: Doc) => { docList && docList.push(doc); return true; };
}
}
+ let movedDocs = (de.data.options === this.props.treeViewId ? de.data.draggedDocuments : de.data.droppedDocuments);
+ return (de.data.dropAction || de.data.userDropAction) ?
+ de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.resolvedDataDoc, before) || added, false)
+ : (de.data.moveDocument) ?
+ movedDocs.reduce((added: boolean, d) => de.data.moveDocument(d, this.resolvedDataDoc, addDoc) || added, false)
+ : de.data.droppedDocuments.reduce((added: boolean, d) => this.props.addDocument(d, this.resolvedDataDoc, before), false);
+ }
+ return false;
+ }
+
+ docTransform = () => {
+ let { scale, translateX, translateY } = Utils.GetScreenTransform(this._dref.current!);
+ let outerXf = this.props.outerXf();
+ let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY);
+ let finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]);
+ return finalXf;
+ }
+
+ renderLinks = () => {
+ let ele: JSX.Element[] = [];
+ let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this._chosenKey, doc, addBefore, before);
+ let groups = LinkManager.Instance.getRelatedGroupedLinks(this.props.document);
+ groups.forEach((groupLinkDocs, groupType) => {
+ let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document));
+ ele.push(
+ <div key={"treeviewlink-" + groupType + "subtitle"}>
+ <div className="collectionTreeView-subtitle">{groupType}:</div>
+ {
+ TreeView.GetChildElements(destLinks, this.props.treeViewId, this.props.document, this.props.dataDoc, "treeviewlink-" + groupType, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)
+ }
+ </div>
+ );
});
- return <div className="treeViewItem-container"
- onContextMenu={this.onWorkspaceContextMenu}>
+ return ele;
+ }
+
+ @computed get docBounds() {
+ if (StrCast(this.props.document.type).indexOf(DocTypes.COL) === -1) return undefined;
+ let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc);
+ return Doc.ComputeContentBounds(layoutDoc);
+ }
+ docWidth = () => {
+ let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth);
+ if (aspect) return Math.min(this.props.document[WidthSym](), Math.min(this.MAX_EMBED_HEIGHT / aspect, this.props.panelWidth() - 5));
+ return NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 5) : this.props.panelWidth() - 5;
+ }
+ docHeight = () => {
+ let bounds = this.docBounds;
+ return Math.min(this.MAX_EMBED_HEIGHT, (() => {
+ let aspect = NumCast(this.props.document.nativeHeight) / NumCast(this.props.document.nativeWidth);
+ if (aspect) return this.docWidth() * aspect;
+ if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x);
+ return NumCast(this.props.document.height) ? NumCast(this.props.document.height) : 50;
+ })());
+ }
+ fitToBox = () => {
+ let bounds = this.docBounds!;
+ return [(bounds.x + bounds.r) / 2, (bounds.y + bounds.b) / 2, Math.min(this.docHeight() / (bounds.b - bounds.y), this.docWidth() / (bounds.r - bounds.x))];
+ }
+
+ render() {
+ let contentElement: (JSX.Element | null) = null;
+ let docList = Cast(this.resolvedDataDoc[this._chosenKey], listSpec(Doc));
+ let remDoc = (doc: Doc) => this.remove(doc, this._chosenKey);
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.resolvedDataDoc, this._chosenKey, doc, addBefore, before);
+ let doc = Cast(this.resolvedDataDoc[this._chosenKey], Doc);
+
+ if (!this._collapsed) {
+ if (!this.props.document.embed) {
+ contentElement = <ul key={this._chosenKey + "more"}>
+ {this._chosenKey === "links" ? this.renderLinks() :
+ TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.props.dataDoc, this._chosenKey, addDoc, remDoc, this.move,
+ this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
+ </ul >;
+ } else {
+ let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc);
+ contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id]}>
+ <CollectionSchemaPreview
+ Document={layoutDoc}
+ DataDocument={this.resolvedDataDoc}
+ renderDepth={this.props.renderDepth}
+ fitToBox={this.docBounds && !NumCast(this.props.document.nativeWidth) ? this.fitToBox : undefined}
+ width={this.docWidth}
+ height={this.docHeight}
+ getTransform={this.docTransform}
+ CollectionView={undefined}
+ addDocument={emptyFunction as any}
+ moveDocument={this.props.moveDocument}
+ removeDocument={emptyFunction as any}
+ active={this.props.active}
+ whenActiveChanged={emptyFunction as any}
+ addDocTab={this.props.addDocTab}
+ setPreviewScript={emptyFunction}>
+ </CollectionSchemaPreview>
+ </div>;
+ }
+ }
+ return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onContextMenu={this.onWorkspaceContextMenu}>
<li className="collection-child">
- {this.renderBullet(bulletType)}
- {this.renderTitle()}
- {contentElement}
+ <div className="treeViewItem-header" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}>
+ {this.renderBullet()}
+ {this.renderTitle()}
+ </div>
+ <div className="treeViewItem-border">
+ {contentElement}
+ </div>
</li>
</div>;
}
- public static GetChildElements(docs: Doc[], allowMinimized: boolean, remove: ((doc: Doc) => void), move: DragManager.MoveFunction, dropAction: dropActionType, addDocTab: (doc: Doc, where: string) => void) {
- return docs.filter(child => !child.excludeFromLibrary && (allowMinimized || !child.isMinimized)).map(child =>
- <TreeView document={child} key={child[Id]} deleteDoc={remove} moveDocument={move} dropAction={dropAction} addDocTab={addDocTab} />);
+ public static GetChildElements(
+ docs: Doc[],
+ treeViewId: string,
+ containingCollection: Doc,
+ dataDoc: Doc | undefined,
+ key: string,
+ add: (doc: Doc, relativeTo?: Doc, before?: boolean) => boolean,
+ remove: ((doc: Doc) => boolean),
+ move: DragManager.MoveFunction,
+ dropAction: dropActionType,
+ addDocTab: (doc: Doc, dataDoc: Doc | undefined, where: string) => void,
+ screenToLocalXf: () => Transform,
+ outerXf: () => { translateX: number, translateY: number },
+ active: () => boolean,
+ panelWidth: () => number,
+ renderDepth: number
+ ) {
+ let docList = docs.filter(child => !child.excludeFromLibrary);
+ let rowWidth = () => panelWidth() - 20;
+ return docList.map((child, i) => {
+ let indent = i === 0 ? undefined : () => {
+ if (StrCast(docList[i - 1].layout).indexOf("CollectionView") !== -1) {
+ let fieldKeysub = StrCast(docList[i - 1].layout).split("fieldKey")[1];
+ let fieldKey = fieldKeysub.split("\"")[1];
+ Doc.AddDocToList(docList[i - 1], fieldKey, child);
+ remove(child);
+ }
+ };
+ let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
+ return add(doc, relativeTo ? relativeTo : docList[i], before !== undefined ? before : false);
+ };
+ let rowHeight = () => {
+ let aspect = NumCast(child.nativeWidth, 0) / NumCast(child.nativeHeight, 0);
+ return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym]();
+ };
+ return <TreeView
+ document={child}
+ dataDoc={dataDoc}
+ containingCollection={containingCollection}
+ treeViewId={treeViewId}
+ key={child[Id]}
+ indentDocument={indent}
+ renderDepth={renderDepth}
+ deleteDoc={remove}
+ addDocument={addDocument}
+ panelWidth={rowWidth}
+ panelHeight={rowHeight}
+ moveDocument={move}
+ dropAction={dropAction}
+ addDocTab={addDocTab}
+ ScreenToLocalTransform={screenToLocalXf}
+ outerXf={outerXf}
+ parentKey={key}
+ active={active} />;
+ });
}
}
@observer
export class CollectionTreeView extends CollectionSubView(Document) {
+ private treedropDisposer?: DragManager.DragDropDisposer;
+ private _mainEle?: HTMLDivElement;
+
+ protected createTreeDropTarget = (ele: HTMLDivElement) => {
+ this.treedropDisposer && this.treedropDisposer();
+ if (this._mainEle = ele) {
+ this.treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } });
+ }
+ }
+
+ componentWillUnmount() {
+ this.treedropDisposer && this.treedropDisposer();
+ }
+
@action
- remove = (document: Document) => {
- let children = Cast(this.props.Document.data, listSpec(Doc), []);
- if (children) {
+ remove = (document: Document): boolean => {
+ let children = Cast(this.props.Document[this.props.fieldKey], listSpec(Doc), []);
+ if (children.indexOf(document) !== -1) {
children.splice(children.indexOf(document), 1);
+ return true;
}
+ return false;
}
onContextMenu = (e: React.MouseEvent): void => {
// 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
+ if (!e.isPropagationStopped() && this.props.Document.workspaceLibrary) { // 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)) });
+ e.stopPropagation();
+ e.preventDefault();
+ ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
}
}
- render() {
- let dropAction = StrCast(this.props.Document.dropAction, "alias") as dropActionType;
- if (!this.childDocs) {
- return (null);
+
+ @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; }
+
+ outerXf = () => Utils.GetScreenTransform(this._mainEle!);
+ onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {});
+
+
+ @observable static NotifsCol: Opt<Doc>;
+
+ openNotifsCol = () => {
+ if (CollectionTreeView.NotifsCol && CollectionDockingView.Instance) {
+ CollectionDockingView.Instance.AddRightSplit(CollectionTreeView.NotifsCol, undefined);
}
- let childElements = TreeView.GetChildElements(this.childDocs, false, this.remove, this.props.moveDocument, dropAction, this.props.addDocTab);
+ }
+ @computed get notifsButton() {
+ const length = CollectionTreeView.NotifsCol ? DocListCast(CollectionTreeView.NotifsCol.data).length : 0;
+ const notifsRef = React.createRef<HTMLDivElement>();
+ const dragNotifs = action(() => CollectionTreeView.NotifsCol!);
+ return <div id="toolbar" key="toolbar">
+ <div ref={notifsRef}>
+ <button className="toolbar-button round-button" title="Notifs"
+ onClick={this.openNotifsCol} onPointerDown={CollectionTreeView.NotifsCol ? SetupDrag(notifsRef, dragNotifs) : emptyFunction}>
+ <FontAwesomeIcon icon={faBell} size="sm" />
+ </button>
+ <div className="main-notifs-badge" style={length > 0 ? { "display": "initial" } : { "display": "none" }}>
+ {length}
+ </div>
+ </div>
+ </div >;
+ }
+ @computed get clearButton() {
+ return <div id="toolbar" key="toolbar">
+ <div >
+ <button className="toolbar-button round-button" title="Notifs"
+ onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}>
+ <FontAwesomeIcon icon={faTrash} size="sm" />
+ </button>
+ </div>
+ </div >;
+ }
+
- return (
+ render() {
+ let dropAction = StrCast(this.props.Document.dropAction) as dropActionType;
+ let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.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);
+
+ return !this.childDocs ? (null) : (
<div id="body" className="collectionTreeView-dropTarget"
- style={{ borderRadius: "inherit" }}
+ style={{ overflow: "auto" }}
onContextMenu={this.onContextMenu}
- onWheel={(e: React.WheelEvent) => this.props.isSelected() && e.stopPropagation()}
- onDrop={(e: React.DragEvent) => this.onDrop(e, {})} ref={this.createDropTarget}>
- <div className="coll-title">
- <EditableView
- contents={this.props.Document.title}
- display={"inline"}
- height={72}
- GetValue={() => StrCast(this.props.Document.title)}
- SetValue={(value: string) => {
- let target = this.props.Document.proto ? this.props.Document.proto : this.props.Document;
- target.title = value;
- return true;
- }} />
- </div>
- <ul className="no-indent">
- {childElements}
+ onWheel={(e: React.WheelEvent) => (e.target as any).scrollHeight > (e.target as any).clientHeight && e.stopPropagation()}
+ onDrop={this.onTreeDrop}
+ ref={this.createTreeDropTarget}>
+ <EditableView
+ contents={this.resolvedDataDoc.title}
+ display={"block"}
+ height={72}
+ GetValue={() => StrCast(this.resolvedDataDoc.title)}
+ SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true}
+ OnFillDown={(value: string) => {
+ Doc.GetProto(this.props.Document).title = value;
+ let doc = Docs.Create.FreeformDocument([], { title: "", x: 0, y: 0, width: 100, height: 25, templates: new List<string>([Templates.Title.Layout]) });
+ TreeView.loadId = doc[Id];
+ Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true);
+ }} />
+ {this.props.Document.workspaceLibrary ? this.notifsButton : (null)}
+ {this.props.Document.allowClear ? this.clearButton : (null)}
+ <ul className="no-indent" style={{ width: "max-content" }} >
+ {
+ TreeView.GetChildElements(this.childDocs, this.props.Document[Id], this.props.Document, this.props.DataDoc, this.props.fieldKey, addDoc, this.remove,
+ moveDoc, dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth)
+ }
</ul>
</div >
);
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index ccbac9915..446f104d0 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -12,7 +12,7 @@ import { Id } from "../../../new_fields/FieldSymbols";
import { VideoBox } from "../nodes/VideoBox";
import { NumCast, Cast, StrCast } from "../../../new_fields/Types";
import { VideoField } from "../../../new_fields/URLField";
-import { SearchBox } from "../SearchBox";
+import { SearchBox } from "../search/SearchBox";
import { DocServer } from "../../DocServer";
import { Docs, DocUtils } from "../../documents/Documents";
@@ -97,7 +97,7 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
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.Util.prepend(returnedFilename);
+ let url = DocServer.prepend(returnedFilename);
let imageSummary = Docs.Create.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-"
@@ -122,7 +122,6 @@ export class CollectionVideoView extends React.Component<FieldViewProps> {
}
render() {
- trace();
return (
<CollectionBaseView {...this.props} className="collectionVideoView-cont" onContextMenu={this.onContextMenu}>
{this.subView}
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx
index 68eefab4c..e500e5c70 100644
--- a/src/client/views/collections/CollectionView.tsx
+++ b/src/client/views/collections/CollectionView.tsx
@@ -2,8 +2,10 @@ 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 { Doc } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
import { CurrentUserUtils } from '../../../server/authentication/models/current_user_utils';
+import { Docs } from '../../documents/Documents';
import { undoBatch } from '../../util/UndoManager';
import { ContextMenu } from "../ContextMenu";
import { ContextMenuProps } from '../ContextMenuItem';
@@ -29,7 +31,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
private SubView = (type: CollectionViewType, renderProps: CollectionRenderProps) => {
let props = { ...this.props, ...renderProps };
- switch (type) {
+ switch (this.isAnnotationOverlay ? CollectionViewType.Freeform : type) {
case CollectionViewType.Schema: return (<CollectionSchemaView {...props} CollectionView={this} />);
case CollectionViewType.Docking: return (<CollectionDockingView {...props} CollectionView={this} />);
case CollectionViewType.Tree: return (<CollectionTreeView {...props} CollectionView={this} />);
@@ -41,7 +43,7 @@ export class CollectionView extends React.Component<FieldViewProps> {
return (null);
}
- get isAnnotationOverlay() { return this.props.fieldKey && this.props.fieldKey === "annotations"; } // bcz: ? Why do we need to compare Id's?
+ get isAnnotationOverlay() { return this.props.fieldKey === "annotations" || this.props.fieldExt === "annotations"; }
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
@@ -54,6 +56,16 @@ export class CollectionView extends React.Component<FieldViewProps> {
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 });
+ ContextMenu.Instance.addItem({
+ description: "Apply Template", event: undoBatch(() => {
+ let otherdoc = new Doc();
+ otherdoc.width = 100;
+ otherdoc.height = 50;
+ Doc.GetProto(otherdoc).title = "applied(" + this.props.Document.title + ")";
+ Doc.GetProto(otherdoc).layout = Doc.MakeDelegate(this.props.Document);
+ this.props.addDocTab && this.props.addDocTab(otherdoc, undefined, "onRight");
+ }), icon: "project-diagram"
+ });
}
}
diff --git a/src/client/views/collections/DockedFrameRenderer.tsx b/src/client/views/collections/DockedFrameRenderer.tsx
deleted file mode 100644
index 1e7c5661b..000000000
--- a/src/client/views/collections/DockedFrameRenderer.tsx
+++ /dev/null
@@ -1,116 +0,0 @@
-import 'golden-layout/src/css/goldenlayout-base.css';
-import 'golden-layout/src/css/goldenlayout-dark-theme.css';
-import { action, observable, reaction, Lambda, IReactionDisposer } from "mobx";
-import { observer } from "mobx-react";
-import Measure, { ContentRect } from "react-measure";
-import { Doc, Field, Opt, DocListCast } from "../../../new_fields/Doc";
-import { FieldId } from "../../../new_fields/RefField";
-import { Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { emptyFunction, returnTrue, Utils } from "../../../Utils";
-import { DocServer } from "../../DocServer";
-import { Transform } from '../../util/Transform';
-import { DocumentView } from "../nodes/DocumentView";
-import "./CollectionDockingView.scss";
-import { SubCollectionViewProps } from "./CollectionSubView";
-import React = require("react");
-import { CollectionViewType } from './CollectionBaseView';
-import { Id } from '../../../new_fields/FieldSymbols';
-import { CollectionDockingView } from './CollectionDockingView';
-
-interface DockedFrameProps {
- documentId: FieldId;
- glContainer: any;
- glEventHub: any;
- parent: CollectionDockingView;
-}
-
-@observer
-export class DockedFrameRenderer extends React.Component<DockedFrameProps> {
- _mainCont = React.createRef<HTMLDivElement>();
- @observable private _panelWidth = 0;
- @observable private _panelHeight = 0;
- @observable private _document: Opt<Doc>;
- private get parentProps(): SubCollectionViewProps {
- return this.props.parent.props;
- }
-
- get _stack(): any {
- let parent = this.props.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));
- }
-
- nativeWidth = () => NumCast(this._document!.nativeWidth, this._panelWidth);
- nativeHeight = () => NumCast(this._document!.nativeHeight, this._panelHeight);
- contentScaling = () => {
- const nativeH = this.nativeHeight();
- const nativeW = this.nativeWidth();
- let wscale = this._panelWidth / nativeW;
- return wscale * nativeH > this._panelHeight ? this._panelHeight / nativeH : wscale;
- }
-
- ScreenToLocalTransform = () => {
- 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 this.parentProps.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 (location === "onRight") {
- CollectionDockingView.AddRightSplit(doc);
- } else {
- CollectionDockingView.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) scale(${this.scaleToFitMultiplier}, ${this.scaleToFitMultiplier})` }}>
- <DocumentView key={this._document[Id]} Document={this._document}
- bringToFront={emptyFunction}
- addDocument={undefined}
- removeDocument={undefined}
- ContentScaling={this.contentScaling}
- PanelWidth={this.nativeWidth}
- PanelHeight={this.nativeHeight}
- ScreenToLocalTransform={this.ScreenToLocalTransform}
- isTopMost={true}
- selectOnLoad={false}
- parentActive={returnTrue}
- whenActiveChanged={emptyFunction}
- focus={emptyFunction}
- addDocTab={this.addDocTab}
- ContainingCollectionView={undefined} />
- </div >);
- }
-
- render() {
- let theContent = this.content;
- return !this._document ? (null) :
- <Measure offset onResize={action((r: any) => { this._panelWidth = r.offset.width; this._panelHeight = r.offset.height; })}>
- {({ measureRef }) => <div ref={measureRef}> {theContent} </div>}
- </Measure>;
- }
-} \ No newline at end of file
diff --git a/src/client/views/collections/ParentDocumentSelector.tsx b/src/client/views/collections/ParentDocumentSelector.tsx
index 95183bd2c..c0f489cd8 100644
--- a/src/client/views/collections/ParentDocumentSelector.tsx
+++ b/src/client/views/collections/ParentDocumentSelector.tsx
@@ -9,7 +9,7 @@ import { CollectionDockingView } from "./CollectionDockingView";
import { NumCast } from "../../../new_fields/Types";
import { CollectionViewType } from "./CollectionBaseView";
-type SelectorProps = { Document: Doc, addDocTab(doc: Doc, location: string): void };
+type SelectorProps = { Document: Doc, addDocTab(doc: Doc, dataDoc: Doc | undefined, location: string): void };
@observer
export class SelectorContextMenu extends React.Component<SelectorProps> {
@observable private _docs: { col: Doc, target: Doc }[] = [];
@@ -23,14 +23,14 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
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);
+ const { docs } = await SearchUtil.Search(`data_l:"${this.props.Document[Id]}"`, true);
const map: Map<Doc, Doc> = new Map;
- const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search(`data_l:"${doc[Id]}"`, true)));
+ const allDocs = await Promise.all(aliases.map(doc => SearchUtil.Search(`data_l:"${doc[Id]}"`, true).then(result => result.docs)));
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.TopLevel.props.Document)).map(doc => ({ col: doc, target: this.props.Document }));
- this._otherDocs = Array.from(map.entries()).filter(entry => !Doc.AreProtosEqual(entry[0], CollectionDockingView.TopLevel.props.Document)).map(([col, target]) => ({ col, target }));
+ 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 }));
});
}
@@ -43,7 +43,7 @@ export class SelectorContextMenu extends React.Component<SelectorProps> {
col.panX = newPanX;
col.panY = newPanY;
}
- this.props.addDocTab(col, "inTab");
+ this.props.addDocTab(col, undefined, "inTab"); // bcz: dataDoc?
};
}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
index 7a0fd2b31..fc5212edd 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.scss
@@ -9,6 +9,7 @@
opacity: 0.5;
transform: translate(10000px,10000px);
pointer-events: all;
+ cursor: pointer;
}
.collectionfreeformlinkview-linkText {
stroke: rgb(0,0,0);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinkView.tsx
index 301b769af..b546d1b78 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;
@@ -26,18 +25,18 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
let y1 = NumCast(a.y) + (BoolCast(a.isMinimized, false) ? 5 : a[HeightSym]() / 2);
let x2 = NumCast(b.x) + (BoolCast(b.isMinimized, false) ? 5 : b[WidthSym]() / 2);
let y2 = NumCast(b.y) + (BoolCast(b.isMinimized, false) ? 5 : b[HeightSym]() / 2);
- this.props.LinkDocs.map(l => {
- let width = l[WidthSym]();
- l.x = (x1 + x2) / 2 - width / 2;
- l.y = (y1 + y2) / 2 + 10;
- if (!this.props.removeDocument(l)) this.props.addDocument(l, false);
- });
+ // this.props.LinkDocs.map(l => {
+ // let width = l[WidthSym]();
+ // l.x = (x1 + x2) / 2 - width / 2;
+ // l.y = (y1 + y2) / 2 + 10;
+ // if (!this.props.removeDocument(l)) this.props.addDocument(l, false);
+ // });
e.stopPropagation();
e.preventDefault();
}
}
render() {
- let l = this.props.LinkDocs;
+ // 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) / NumCast(a.zoomBasis, 1) / 2);
@@ -45,12 +44,13 @@ export class CollectionFreeFormLinkView extends React.Component<CollectionFreeFo
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 = "";
- this.props.LinkDocs.map(l => text += StrCast(l.title) + "(" + StrCast(l.linkDescription) + "), ");
- text = text.substr(0, text.length - 2);
+ // 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="linkLine" className="collectionfreeformlinkview-linkLine"
- style={{ strokeWidth: `${2 * l.length / 2}` }}
+ style={{ strokeWidth: `${2 * 1 / 2}` }}
x1={`${x1}`} y1={`${y1}`}
x2={`${x2}`} y2={`${y2}`} />
{/* <circle key="linkCircle" className="collectionfreeformlinkview-linkCircle"
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
index a43c5f241..2d94f1b8e 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx
@@ -1,75 +1,72 @@
-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/FieldSymbols";
@observer
export class CollectionFreeFormLinksView extends React.Component<CollectionViewProps> {
_brushReactionDisposer?: IReactionDisposer;
componentDidMount() {
- this._brushReactionDisposer = reaction(
- () => {
- let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
- return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) };
- },
- () => {
- let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
- let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
- views.forEach((dstDoc, i) => {
- views.forEach((srcDoc, j) => {
- let dstTarg = dstDoc;
- let srcTarg = srcDoc;
- let x1 = NumCast(srcDoc.x);
- let x2 = NumCast(dstDoc.x);
- 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 => {
- let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined;
- return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false;
- });
- let brushAction = (field: (Doc | Promise<Doc>)[]) => {
- let found = findBrush(field);
- if (found !== -1) {
- console.log("REMOVE BRUSH " + srcTarg.title + " " + dstTarg.title);
- field.splice(found, 1);
- }
- };
- if (Math.abs(x1 + x1w - x2) < 20) {
- let linkDoc: Doc = new Doc();
- linkDoc.title = "Histogram Brush";
- linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title);
- linkDoc.brushingDocs = new List([dstTarg, srcTarg]);
+ // this._brushReactionDisposer = reaction(
+ // () => {
+ // let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
+ // return { doclist: doclist ? doclist : [], xs: doclist.map(d => d.x) };
+ // },
+ // () => {
+ // let doclist = DocListCast(this.props.Document[this.props.fieldKey]);
+ // let views = doclist ? doclist.filter(doc => StrCast(doc.backgroundLayout).indexOf("istogram") !== -1) : [];
+ // views.forEach((dstDoc, i) => {
+ // views.forEach((srcDoc, j) => {
+ // let dstTarg = dstDoc;
+ // let srcTarg = srcDoc;
+ // let x1 = NumCast(srcDoc.x);
+ // let x2 = NumCast(dstDoc.x);
+ // 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 => {
+ // let bdocs = brush instanceof Doc ? Cast(brush.brushingDocs, listSpec(Doc), []) : undefined;
+ // return bdocs && bdocs.length && ((bdocs[0] === dstTarg && bdocs[1] === srcTarg)) ? true : false;
+ // });
+ // let brushAction = (field: (Doc | Promise<Doc>)[]) => {
+ // let found = findBrush(field);
+ // if (found !== -1) {
+ // field.splice(found, 1);
+ // }
+ // };
+ // if (Math.abs(x1 + x1w - x2) < 20) {
+ // let linkDoc: Doc = new Doc();
+ // linkDoc.title = "Histogram Brush";
+ // linkDoc.linkDescription = "Brush between " + StrCast(srcTarg.title) + " and " + StrCast(dstTarg.Title);
+ // linkDoc.brushingDocs = new List([dstTarg, srcTarg]);
- brushAction = (field: (Doc | Promise<Doc>)[]) => {
- if (findBrush(field) === -1) {
- console.log("ADD BRUSH " + srcTarg.title + " " + dstTarg.title);
- field.push(linkDoc);
- }
- };
- }
- 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), []);
- brushAction(dstBrushDocs);
- brushAction(srcBrushDocs);
- }
- });
- });
- });
+ // brushAction = (field: (Doc | Promise<Doc>)[]) => {
+ // if (findBrush(field) === -1) {
+ // field.push(linkDoc);
+ // }
+ // };
+ // }
+ // 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), []);
+ // brushAction(dstBrushDocs);
+ // brushAction(srcBrushDocs);
+ // }
+ // });
+ // });
+ // });
}
componentWillUnmount() {
if (this._brushReactionDisposer) {
@@ -98,6 +95,7 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
let connections = DocumentManager.Instance.LinkedDocumentViews.reduce((drawnPairs, connection) => {
let srcViews = this.documentAnchors(connection.a);
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 => {
@@ -110,25 +108,21 @@ export class CollectionFreeFormLinksView extends React.Component<CollectionViewP
}
return match || found;
}, false)) {
- console.log("A" + possiblePair.a[Id] + " B" + possiblePair.b[Id] + " L" + connection.l[Id]);
- drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] })
+ drawnPairs.push({ a: possiblePair.a, b: possiblePair.b, l: [connection.l] });
}
});
return drawnPairs;
}, [] as { a: Doc, b: Doc, l: Doc[] }[]);
- 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} />;
- });
+ return connections.map(c => <CollectionFreeFormLinkView key={c.l.reduce((p, l) => p + l[Id], "")} A={c.a} B={c.b} LinkDocs={c.l}
+ removeDocument={this.props.removeDocument} addDocument={this.props.addDocument} />);
}
render() {
return (
<div className="collectionfreeformlinksview-container">
- <svg className="collectionfreeformlinksview-svgCanvas">
+ {/* <svg className="collectionfreeformlinksview-svgCanvas">
{this.uniqueConnections}
- </svg>
+ </svg> */}
{this.props.children}
</div>
);
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
index 2838b7905..3193f5624 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx
@@ -1,15 +1,13 @@
-import { computed } from "mobx";
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 { List } from "../../../../new_fields/List";
-import { Cast } from "../../../../new_fields/Types";
-import { listSpec } from "../../../../new_fields/Schema";
-import * as mobxUtils from 'mobx-utils';
@observer
export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> {
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
index e10ba9d7e..ccf261c95 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss
@@ -1,7 +1,7 @@
@import "../../globalCssVariables";
.collectionfreeformview-ease {
- position: absolute;
+ position: inherit;
top: 0;
left: 0;
width: 100%;
@@ -25,8 +25,9 @@
height: 100%;
width: 100%;
}
+
>.jsx-parser {
- z-index:0;
+ z-index: 0;
}
//nested freeform views
@@ -40,24 +41,27 @@
border-radius: $border-radius;
box-sizing: border-box;
position: absolute;
+
.marqueeView {
overflow: hidden;
}
+
top: 0;
left: 0;
width: 100%;
height: 100%;
}
-
+
.collectionfreeformview-overlay {
.collectionfreeformview>.jsx-parser {
position: inherit;
height: 100%;
}
-
+
>.jsx-parser {
- z-index:0;
+ position: absolute;
+ z-index: 0;
}
.formattedTextBox-cont {
@@ -71,9 +75,11 @@
box-sizing: border-box;
position:absolute;
z-index: -1;
+
.marqueeView {
overflow: hidden;
}
+
top: 0;
left: 0;
width: 100%;
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index cd613e6ab..1777c287c 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,16 +1,25 @@
-import { action, computed, trace } from "mobx";
+import { action, computed } from "mobx";
import { observer } from "mobx-react";
-import { emptyFunction, returnFalse, returnOne } from "../../../../Utils";
+import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast } 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";
import { COLLECTION_BORDER_WIDTH } from "../../../views/globalCssVariables.scss";
+import { ContextMenu } from "../../ContextMenu";
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 PDFMenu from "../../pdf/PDFMenu";
import { CollectionSubView } from "../CollectionSubView";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
@@ -18,19 +27,18 @@ 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 { InkField, StrokeData } from "../../../../new_fields/InkField";
-import { HistoryUtil } from "../../../util/History";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { DocServer } from "../../../DocServer";
+import { ScriptField } from "../../../../new_fields/ScriptField";
+import { OverlayView, OverlayElementOptions } from "../../OverlayView";
+import { ScriptBox } from "../../ScriptBox";
+import { CompileScript } from "../../../util/Scripting";
+
export const panZoomSchema = createSchema({
panX: "number",
panY: "number",
- scale: "number"
+ scale: "number",
+ arrangeScript: ScriptField,
+ arrangeInit: ScriptField,
});
type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>;
@@ -46,11 +54,11 @@ 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"; }
+ public get isAnnotationOverlay() { return this.props.fieldKey === "annotations" || this.props.fieldExt === "annotations"; }
private get borderWidth() { return this.isAnnotationOverlay ? 0 : COLLECTION_BORDER_WIDTH; }
- private panX = () => this.Document.panX || 0;
- private panY = () => this.Document.panY || 0;
- private zoomScaling = () => this.Document.scale || 1;
+ private panX = () => this.props.fitToBox ? this.props.fitToBox()[0] : this.Document.panX || 0;
+ private panY = () => this.props.fitToBox ? this.props.fitToBox()[1] : this.Document.panY || 0;
+ private zoomScaling = () => this.props.fitToBox ? this.props.fitToBox()[2] : this.Document.scale || 1;
private centeringShiftX = () => !this.nativeWidth ? this._pwidth / 2 : 0; // shift so pan position is at center of window for non-overlay collections
private centeringShiftY = () => !this.nativeHeight ? this._pheight / 2 : 0;// shift so pan position is at center of window for non-overlay collections
private getTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1).translate(-this.centeringShiftX(), -this.centeringShiftY()).transform(this.getLocalTransform());
@@ -81,31 +89,45 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@undoBatch
@action
drop = (e: Event, de: DragManager.DropEvent) => {
- if (super.drop(e, de) && de.data instanceof DragManager.DocumentDragData) {
- if (de.data.droppedDocuments.length) {
- let dragDoc = de.data.droppedDocuments[0];
- let zoom = NumCast(dragDoc.zoomBasis, 1);
- let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
- let x = xp - de.data.xOffset / zoom;
- let y = yp - de.data.yOffset / zoom;
- let dropX = NumCast(de.data.droppedDocuments[0].x);
- let dropY = NumCast(de.data.droppedDocuments[0].y);
- de.data.droppedDocuments.forEach(d => {
- d.x = x + NumCast(d.x) - dropX;
- d.y = y + NumCast(d.y) - dropY;
- if (!NumCast(d.width)) {
- d.width = 300;
- }
- if (!NumCast(d.height)) {
- let nw = NumCast(d.nativeWidth);
- let nh = NumCast(d.nativeHeight);
- d.height = nw && nh ? nh / nw * NumCast(d.width) : 300;
- }
- this.bringToFront(d);
- });
- SelectionManager.ReselectAll();
+ if (super.drop(e, de)) {
+ if (de.data instanceof DragManager.DocumentDragData) {
+ if (de.data.droppedDocuments.length) {
+ let dragDoc = de.data.droppedDocuments[0];
+ let zoom = NumCast(dragDoc.zoomBasis, 1);
+ let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+ let x = xp - de.data.xOffset / zoom;
+ let y = yp - de.data.yOffset / zoom;
+ let dropX = NumCast(de.data.droppedDocuments[0].x);
+ let dropY = NumCast(de.data.droppedDocuments[0].y);
+ de.data.droppedDocuments.forEach(d => {
+ d.x = x + NumCast(d.x) - dropX;
+ d.y = y + NumCast(d.y) - dropY;
+ if (!NumCast(d.width)) {
+ d.width = 300;
+ }
+ if (!NumCast(d.height)) {
+ let nw = NumCast(d.nativeWidth);
+ let nh = NumCast(d.nativeHeight);
+ d.height = nw && nh ? nh / nw * NumCast(d.width) : 300;
+ }
+ this.bringToFront(d);
+ });
+ }
+ }
+ else if (de.data instanceof DragManager.AnnotationDragData) {
+ if (de.data.dropDocument) {
+ let dragDoc = de.data.dropDocument;
+ let zoom = NumCast(dragDoc.zoomBasis, 1);
+ let [xp, yp] = this.getTransform().transformPoint(de.x, de.y);
+ let x = xp - de.data.xOffset / zoom;
+ let y = yp - de.data.yOffset / zoom;
+ let dropX = NumCast(de.data.dropDocument.x);
+ let dropY = NumCast(de.data.dropDocument.y);
+ dragDoc.x = x + NumCast(dragDoc.x) - dropX;
+ dragDoc.y = y + NumCast(dragDoc.y) - dropY;
+ this.bringToFront(dragDoc);
+ }
}
- return true;
}
return false;
}
@@ -135,6 +157,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
let docs = this.childDocs || [];
let [dx, dy] = this.getTransform().transformDirection(e.clientX - this._lastX, e.clientY - this._lastY);
if (!this.isAnnotationOverlay) {
+ PDFMenu.Instance.fadeOut(true);
let minx = docs.length ? NumCast(docs[0].x) : 0;
let maxx = docs.length ? NumCast(docs[0].width) / NumCast(docs[0].zoomBasis, 1) + minx : minx;
let miny = docs.length ? NumCast(docs[0].y) : 0;
@@ -147,7 +170,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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]]);
- let ink = Cast(this.props.Document.ink, InkField);
+ let ink = Cast(this.extensionDoc.ink, InkField);
if (ink && ink.inkData) {
ink.inkData.forEach((value: StrokeData, key: string) => {
let bounds = InkingCanvas.StrokeRect(value);
@@ -175,14 +198,18 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
onPointerWheel = (e: React.WheelEvent): void => {
+ if (BoolCast(this.props.Document.lockedPosition)) return;
// if (!this.props.active()) {
// return;
// }
+ if (this.props.Document.type === "pdf") {
+ return;
+ }
let childSelected = this.childDocs.some(doc => {
var dv = DocumentManager.Instance.getDocumentView(doc);
return dv && SelectionManager.IsSelected(dv) ? true : false;
});
- if (!this.props.isSelected() && !childSelected && !this.props.isTopMost) {
+ if (!this.props.isSelected() && !childSelected && this.props.renderDepth > 0) {
return;
}
e.stopPropagation();
@@ -201,7 +228,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
} else {
// if (modes[e.deltaMode] === 'pixels') coefficient = 50;
// else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height??
- let deltaScale = (1 - (e.deltaY / coefficient));
+ let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1;
if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) {
deltaScale = 1 / this.zoomScaling();
}
@@ -218,35 +245,24 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@action
setPan(panX: number, panY: number) {
+ if (BoolCast(this.props.Document.lockedPosition)) return;
this.props.Document.panTransformType = "None";
var scale = this.getLocalTransform().inverse().Scale;
const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX));
const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY));
this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX;
this.props.Document.panY = this.isAnnotationOverlay ? newPanY : panY;
+ // this.props.Document.panX = panX;
+ // this.props.Document.panY = panY;
+ if (this.props.Document.scrollY) {
+ this.props.Document.scrollY = panY;
+ }
}
@action
onDrop = (e: React.DragEvent): void => {
var pt = this.getTransform().transformPoint(e.pageX, e.pageY);
- let html = e.dataTransfer.getData("text/html");
- if (html && html.indexOf(document.location.origin)) { // prosemirror text containing link to dash document
- e.stopPropagation();
- e.preventDefault();
- let start = html.indexOf(window.location.origin);
- let path = html.substr(start, html.length - start);
- let docid = path.substr(0, path.indexOf("\">")).replace(DocServer.Util.prepend("/doc/"), "").split("?")[0];
- DocServer.getRefField(docid).then(f => {
- if (f instanceof Doc) {
- f.x = pt[0];
- f.y = pt[1];
- (f instanceof Doc) && this.props.addDocument(f, false);
- }
- });
- return;
- } else {
- super.onDrop(e, { x: pt[0], y: pt[1] });
- }
+ super.onDrop(e, { x: pt[0], y: pt[1] });
}
onDragOver = (): void => {
@@ -262,7 +278,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
doc.zIndex = docs.length + 1;
}
- focusDocument = (doc: Doc) => {
+ focusDocument = (doc: Doc, willZoom: boolean) => {
const panX = this.Document.panX;
const panY = this.Document.panY;
const id = this.Document[Id];
@@ -289,22 +305,80 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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);
+ if (willZoom) {
+ this.setScaleToZoom(doc);
+ }
+
+ }
+
+ setScaleToZoom = (doc: Doc) => {
+ let p = this.props;
+ let PanelHeight = p.PanelHeight();
+ let panelWidth = p.PanelWidth();
+
+ let docHeight = NumCast(doc.height);
+ let docWidth = NumCast(doc.width);
+ let targetHeight = 0.5 * PanelHeight;
+ let targetWidth = 0.5 * panelWidth;
+
+ let maxScaleX: number = targetWidth / docWidth;
+ let maxScaleY: number = targetHeight / docHeight;
+ let maxApplicableScale = Math.min(maxScaleX, maxScaleY);
+ this.Document.scale = maxApplicableScale;
}
+ zoomToScale = (scale: number) => {
+ this.Document.scale = scale;
+ }
- getDocumentViewProps(document: Doc): DocumentViewProps {
+ getScale = () => {
+ if (this.Document.scale) {
+ return this.Document.scale;
+ }
+ return 1;
+ }
+
+
+ getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps {
+ let resolvedDataDoc = this.props.DataDoc !== this.props.Document ? this.props.DataDoc : undefined;
+ let layoutDoc = Doc.expandTemplateLayout(childDocLayout, resolvedDataDoc);
+ return {
+ DataDoc: resolvedDataDoc !== layoutDoc && resolvedDataDoc ? resolvedDataDoc : undefined,
+ Document: layoutDoc,
+ addDocument: this.props.addDocument,
+ removeDocument: this.props.removeDocument,
+ moveDocument: this.props.moveDocument,
+ ScreenToLocalTransform: this.getTransform,
+ renderDepth: this.props.renderDepth + 1,
+ selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
+ PanelWidth: layoutDoc[WidthSym],
+ PanelHeight: layoutDoc[HeightSym],
+ ContentScaling: returnOne,
+ ContainingCollectionView: this.props.CollectionView,
+ focus: this.focusDocument,
+ parentActive: this.props.active,
+ whenActiveChanged: this.props.whenActiveChanged,
+ bringToFront: this.bringToFront,
+ addDocTab: this.props.addDocTab,
+ zoomToScale: this.zoomToScale,
+ getScale: this.getScale
+ };
+ }
+ getDocumentViewProps(layoutDoc: Doc): DocumentViewProps {
return {
- Document: document,
+ DataDoc: this.props.DataDoc,
+ Document: this.props.Document,
addDocument: this.props.addDocument,
removeDocument: this.props.removeDocument,
moveDocument: this.props.moveDocument,
ScreenToLocalTransform: this.getTransform,
- isTopMost: false,
- selectOnLoad: document[Id] === this._selectOnLoaded,
- PanelWidth: document[WidthSym],
- PanelHeight: document[HeightSym],
+ renderDepth: this.props.renderDepth + 1,
+ selectOnLoad: layoutDoc[Id] === this._selectOnLoaded,
+ PanelWidth: layoutDoc[WidthSym],
+ PanelHeight: layoutDoc[HeightSym],
ContentScaling: returnOne,
ContainingCollectionView: this.props.CollectionView,
focus: this.focusDocument,
@@ -312,19 +386,41 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
whenActiveChanged: this.props.whenActiveChanged,
bringToFront: this.bringToFront,
addDocTab: this.props.addDocTab,
+ zoomToScale: this.zoomToScale,
+ getScale: this.getScale
};
}
+ getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, width?: number, height?: number, state?: any } {
+ const result = script.script.run(params);
+ if (!result.success) {
+ return {};
+ }
+ return result.result === undefined ? {} : result.result;
+ }
+
@computed.struct
get views() {
let curPage = FieldValue(this.Document.curPage, -1);
- let docviews = this.childDocs.reduce((prev, doc) => {
+ const initScript = this.Document.arrangeInit;
+ const script = this.Document.arrangeScript;
+ let state: any = undefined;
+ const docs = this.childDocs;
+ if (initScript) {
+ const initResult = initScript.script.run({ docs, collection: this.Document });
+ if (initResult.success) {
+ state = initResult.result;
+ }
+ }
+ let docviews = docs.reduce((prev, doc) => {
if (!(doc instanceof Doc)) return prev;
var page = NumCast(doc.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)} />);
+ const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : {};
+ state = pos.state === undefined ? state : pos.state;
+ prev.push(<CollectionFreeFormDocumentView key={doc[Id]} x={pos.x} y={pos.y} width={pos.width} height={pos.height} {...this.getChildDocumentViewProps(doc)} />);
}
}
return prev;
@@ -340,17 +436,76 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY));
}
+ onContextMenu = () => {
+ ContextMenu.Instance.addItem({
+ description: "Arrange contents in grid",
+ event: async () => {
+ const docs = await DocListCastAsync(this.Document[this.props.fieldKey]);
+ if (docs) {
+ let startX = this.Document.panX || 0;
+ let x = startX;
+ let y = this.Document.panY || 0;
+ let i = 0;
+ const width = Math.max(...docs.map(doc => NumCast(doc.width)));
+ const height = Math.max(...docs.map(doc => NumCast(doc.height)));
+ for (const doc of docs) {
+ doc.x = x;
+ doc.y = y;
+ x += width + 20;
+ if (++i === 6) {
+ i = 0;
+ x = startX;
+ y += height + 20;
+ }
+ }
+ }
+ }
+ });
+ ContextMenu.Instance.addItem({
+ description: "Add freeform arrangement",
+ event: () => {
+ let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+ let overlayDisposer: () => void = emptyFunction;
+ const script = this.Document[key];
+ let originalText: string | undefined = undefined;
+ if (script) originalText = script.script.originalScript;
+ let scriptingBox = <ScriptBox initialText={originalText} onCancel={overlayDisposer} onSave={(text, onError) => {
+ const script = CompileScript(text, {
+ params,
+ requiredType,
+ typecheck: false
+ });
+ if (!script.compiled) {
+ onError(script.errors.map(error => error.messageText).join("\n"));
+ return;
+ }
+ const docs = DocListCast(this.Document[this.props.fieldKey]);
+ docs.map(d => d.transition = "transform 1s");
+ this.Document[key] = new ScriptField(script);
+ overlayDisposer();
+ setTimeout(() => docs.map(d => d.transition = undefined), 1200);
+ }} />;
+ overlayDisposer = OverlayView.Instance.addElement(scriptingBox, options);
+ };
+ addOverlay("arrangeInit", { x: 400, y: 100, width: 400, height: 300 }, { collection: "Doc", docs: "Doc[]" }, undefined);
+ addOverlay("arrangeScript", { x: 400, y: 500, width: 400, height: 300 }, { doc: "Doc", index: "number", collection: "Doc", state: "any", docs: "Doc[]" }, "{x: number, y: number, width?: number, height?: number}");
+ }
+ });
+ }
+
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";
+
+ if (this.props.fieldExt) Doc.UpdateDocumentExtensionForField(this.extensionDoc, this.props.fieldKey);
return (
<div className={containerName} ref={this.createDropTarget} onWheel={this.onPointerWheel}
style={{ borderRadius: "inherit" }}
- onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} >
+ onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onDrop.bind(this)} onDragOver={this.onDragOver} onContextMenu={this.onContextMenu}>
<MarqueeView container={this} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} isSelected={this.props.isSelected}
addDocument={this.addDocument} removeDocument={this.props.removeDocument} addLiveTextDocument={this.addLiveTextBox}
getContainerTransform={this.getContainerTransform} getTransform={this.getTransform}>
@@ -358,14 +513,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
easing={easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}>
<CollectionFreeFormLinksView {...this.props} key="freeformLinks">
- <InkingCanvas getScreenTransform={this.getTransform} Document={this.props.Document} >
+ <InkingCanvas getScreenTransform={this.getTransform} Document={this.extensionDoc} inkFieldKey={this.props.fieldExt ? "ink" : this.props.fieldKey + "_ink"} >
{this.childViews}
</InkingCanvas>
</CollectionFreeFormLinksView>
<CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" />
</CollectionFreeFormViewPannableContents>
</MarqueeView>
- <CollectionFreeFormOverlayView {...this.getDocumentViewProps(this.props.Document)} {...this.props} />
+ <CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} />
</div>
);
}
@@ -375,7 +530,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
@computed get overlayView() {
return (<DocumentContentsView {...this.props} layoutKey={"overlayLayout"}
- isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+ renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
}
render() {
return this.overlayView;
@@ -385,8 +540,9 @@ class CollectionFreeFormOverlayView extends React.Component<DocumentViewProps &
@observer
class CollectionFreeFormBackgroundView extends React.Component<DocumentViewProps & { isSelected: () => boolean }> {
@computed get backgroundView() {
+ let props = this.props;
return (<DocumentContentsView {...this.props} layoutKey={"backgroundLayout"}
- isTopMost={this.props.isTopMost} isSelected={this.props.isSelected} select={emptyFunction} />);
+ renderDepth={this.props.renderDepth} isSelected={this.props.isSelected} select={emptyFunction} />);
}
render() {
return this.props.Document.backgroundLayout ? this.backgroundView : (null);
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
index 6e8ec8662..9fc2e44fb 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss
@@ -21,6 +21,6 @@
white-space:nowrap;
}
.marquee-legend::after {
- content: "Press: c (collection), s (summary), r (replace) or Delete"
+ content: "Press: c (collection), s (summary), or Delete"
}
} \ No newline at end of file
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 07a58ed64..a4a6881f8 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -1,27 +1,24 @@
import * as htmlToImage from "html-to-image";
-import { action, computed, observable, trace } from "mobx";
+import { action, computed, observable } from "mobx";
import { observer } from "mobx-react";
+import { Doc, FieldResult } 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 { 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 { SearchBox } from "../../SearchBox";
-import { DocServer } from "../../../DocServer";
-import { Id } from "../../../../new_fields/FieldSymbols";
-import { CollectionView } from "../CollectionView";
-import { CollectionViewType } from "../CollectionBaseView";
interface MarqueeViewProps {
getContainerTransform: () => Transform;
@@ -94,8 +91,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this.pasteTable(ns, x, y);
}
});
- } else {
+ } else if (!e.ctrlKey) {
let newBox = Docs.Create.TextDocument({ width: 200, height: 100, x: x, y: y, title: "-typed text-" });
+ newBox.proto!.autoHeight = true;
this.props.addLiveTextDocument(newBox);
}
e.stopPropagation();
@@ -147,6 +145,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
this._downY = this._lastY = e.pageY;
this._commandExecuted = false;
PreviewCursor.Visible = false;
+ this.cleanupInteractions(true);
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);
@@ -182,14 +181,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
@action
onPointerUp = (e: PointerEvent): void => {
+ // console.log("pointer up!");
if (this._visible) {
+ // console.log("visible");
let mselect = this.marqueeSelect();
if (!e.shiftKey) {
SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
}
this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
}
+ //console.log("invisible");
this.cleanupInteractions(true);
+
if (e.altKey) {
e.preventDefault();
}
@@ -221,6 +224,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
return { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
}
+ get ink() {
+ let container = this.props.container.Document;
+ let containerKey = this.props.container.props.fieldKey;
+ return Cast(container[containerKey + "_ink"], InkField);
+ }
+
+ set ink(value: InkField | undefined) {
+ let container = Doc.GetProto(this.props.container.Document);
+ let containerKey = this.props.container.props.fieldKey;
+ container[containerKey + "_ink"] = value;
+ }
+
@undoBatch
@action
marqueeCommand = async (e: KeyboardEvent) => {
@@ -232,15 +247,14 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
e.stopPropagation();
(e as any).propagationIsStopped = true;
this.marqueeSelect().map(d => this.props.removeDocument(d));
- let ink = Cast(this.props.container.props.Document.ink, InkField);
- if (ink) {
- this.marqueeInkDelete(ink.inkData);
+ if (this.ink) {
+ this.marqueeInkDelete(this.ink.inkData);
}
SelectionManager.DeselectAll();
this.cleanupInteractions(false);
e.stopPropagation();
}
- if (e.key === "c" || e.key === "s" || e.key === "S" || e.key === "e" || e.key === "p") {
+ if (e.key === "c" || e.key === "s" || e.key === "S") {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
@@ -256,23 +270,18 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
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);
+ let inkData = this.ink ? this.ink.inkData : undefined;
let newCollection = Docs.Create.FreeformDocument(selected, {
x: bounds.left,
y: bounds.top,
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" || e.key === "S" ? "-summary-" : e.key === "p" ? "-summary-" : "a nested collection",
+ width: bounds.width,
+ height: bounds.height,
+ title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection",
});
- newCollection.zoomBasis = zoomBasis;
+ newCollection.data_ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined;
this.marqueeInkDelete(inkData);
if (e.key === "s") {
@@ -295,39 +304,25 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
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.Create.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.Util.prepend(returnedFilename);
- let imageSummary = Docs.Create.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];
- 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);
+ 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.Create.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;
+ //this.props.addDocument(newCollection, false);
+ summary.proto!.summarizedDocs = new List<Doc>(selected);
+ summary.proto!.maximizeLocation = "inTab"; // or "inPlace", or "onRight"
+
+ this.props.addLiveTextDocument(summary);
}
else {
this.props.addDocument(newCollection, false);
- SelectionManager.DeselectAll();
this.props.selectDocuments([newCollection]);
}
this.cleanupInteractions(false);
@@ -363,7 +358,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
let idata = new Map();
ink.forEach((value: StrokeData, key: string, map: any) =>
!InkingCanvas.IntersectStrokeRect(value, this.Bounds) && idata.set(key, value));
- Doc.SetOnPrototype(this.props.container.props.Document, "ink", new InkField(idata));
+ this.ink = new InkField(idata);
}
}