aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authorSam Wilkins <samwilkins333@gmail.com>2019-07-08 11:11:01 -0400
committerSam Wilkins <samwilkins333@gmail.com>2019-07-08 11:11:01 -0400
commit6145a7ef060aeb1b815b2da7acba4d8f9d2e2bdc (patch)
treeb63ceff150fe9e5f7383402a99da8d8f8f31cb05 /src/client/views/collections
parent5ce2d59b4544a7473a910c121daf0c34d1546214 (diff)
parent400c525875af607dd76d7ec46949fedc418caabf (diff)
merged with master
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionBaseView.tsx2
-rw-r--r--src/client/views/collections/CollectionDockingView.tsx9
-rw-r--r--src/client/views/collections/CollectionSchemaView.tsx21
-rw-r--r--src/client/views/collections/CollectionStackingView.scss14
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx106
-rw-r--r--src/client/views/collections/CollectionSubView.tsx10
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx103
-rw-r--r--src/client/views/collections/CollectionVideoView.tsx1
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx82
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.scss2
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx20
11 files changed, 283 insertions, 87 deletions
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx
index 879898018..e4f9b5058 100644
--- a/src/client/views/collections/CollectionBaseView.tsx
+++ b/src/client/views/collections/CollectionBaseView.tsx
@@ -64,7 +64,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> {
active = (): boolean => {
var isSelected = this.props.isSelected();
- return isSelected || this._isChildActive || this.props.renderDepth === 0;
+ return isSelected || this._isChildActive || this.props.renderDepth === 0 || BoolCast(this.props.Document.excludeFromLibrary);
}
//TODO should this be observable?
diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx
index 8724216f5..e0270fab3 100644
--- a/src/client/views/collections/CollectionDockingView.tsx
+++ b/src/client/views/collections/CollectionDockingView.tsx
@@ -27,6 +27,7 @@ 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
@@ -240,6 +241,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
// 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 });
@@ -249,6 +251,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
}
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);
@@ -414,6 +417,9 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp
if (doc instanceof Doc) {
let theDoc = doc;
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();
@@ -455,6 +461,9 @@ 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 offset onResize={this.onResize}>
{({ measureRef }) =>
diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx
index b0d46953c..568949efb 100644
--- a/src/client/views/collections/CollectionSchemaView.tsx
+++ b/src/client/views/collections/CollectionSchemaView.tsx
@@ -334,7 +334,6 @@ 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}
@@ -388,7 +387,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}>
@@ -404,6 +402,7 @@ interface CollectionSchemaPreviewProps {
Document?: Doc;
DataDocument?: Doc;
childDocs?: Doc[];
+ fitToBox?: () => number[];
renderDepth: number;
width: () => number;
height: () => number;
@@ -462,16 +461,32 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre
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() {
let input = this.props.previewScript === undefined ? (null) :
<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)`, height: "100%" }}>
+ <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}
diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss
index 034a09eaa..bc733f152 100644
--- a/src/client/views/collections/CollectionStackingView.scss
+++ b/src/client/views/collections/CollectionStackingView.scss
@@ -16,7 +16,7 @@
align-items: center;
}
- .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid{
+ .collectionStackingView-masonrySingle, .collectionStackingView-masonryGrid {
width:100%;
height:100%;
position: absolute;
@@ -25,7 +25,17 @@
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 {
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index b10907937..aea74321e 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -1,6 +1,6 @@
import React = require("react");
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
-import { action, computed, IReactionDisposer, reaction } from "mobx";
+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";
@@ -10,7 +10,8 @@ import { ContextMenu } from "../ContextMenu";
import { CollectionSchemaPreview } from "./CollectionSchemaView";
import "./CollectionStackingView.scss";
import { CollectionSubView } from "./CollectionSubView";
-import { resolve } from "bluebird";
+import { undoBatch } from "../../util/UndoManager";
+import { DragManager } from "../../util/DragManager";
@observer
export class CollectionStackingView extends CollectionSubView(doc => doc) {
@@ -23,6 +24,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@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);
@@ -33,14 +35,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
}
componentDidMount() {
this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])],
- () => {
- if (this.singleColumn) {
- let children = this.childDocs.filter(d => !d.isMinimized);
- this.props.Document.height = children.reduce((height, d, i) =>
- height + this.singleColDocHeight(d) + (i === children.length - 1 ? this.yMargin : this.gridGap)
- , this.yMargin);
- }
- }, { fireImmediately: true });
+ () => 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();
@@ -48,14 +46,14 @@ 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) => {
@@ -65,17 +63,15 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
@computed
get singleColumnChildren() {
- let children = this.childDocs.filter(d => !d.isMinimized);
- return children.map((d, i) => {
+ return this.filteredChildren.map((d, i) => {
let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
- let dref = React.createRef<HTMLDivElement>();
- let dxf = () => this.getDocTransform(layoutDoc, dref.current!).scale(this.columnWidth / d[WidthSym]());
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={{ width: width(), height: height() }} >
+ style={{ width: width(), display: "inline-block", marginTop: gap, height: `${height() / (this.props.Document[HeightSym]() - 2 * this.yMargin) * 100}%` }} >
<CollectionSchemaPreview
Document={layoutDoc}
DataDocument={d !== this.props.DataDoc ? this.props.DataDoc : undefined}
@@ -96,15 +92,24 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
</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, i) => {
+ 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!).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}
@@ -166,13 +171,64 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
});
}
}
+
+ @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 cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.childDocs.filter(d => !d.isMinimized).length,
+ 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" ref={this.createRef} onContextMenu={this.onContextMenu} 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: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`,
diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx
index 8cb2e1beb..bf17088ae 100644
--- a/src/client/views/collections/CollectionSubView.tsx
+++ b/src/client/views/collections/CollectionSubView.tsx
@@ -53,6 +53,11 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
//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 Cast((BoolCast(this.props.Document.isTemplate) ? this.extensionDoc : this.props.Document)[this.props.fieldExt ? this.props.fieldExt : this.props.fieldKey], listSpec(Doc));
+ }
@action
protected async setCursorPosition(position: [number, number]) {
@@ -97,6 +102,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
return added;
}
else if (de.data instanceof DragManager.AnnotationDragData) {
+ e.stopPropagation();
return this.props.addDocument(de.data.dropDocument);
}
return false;
@@ -104,7 +110,7 @@ export function CollectionSubView<T>(schemaCtor: (doc: Doc) => T) {
@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;
@@ -213,7 +219,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.tsx b/src/client/views/collections/CollectionTreeView.tsx
index f5dd76b71..c83a2d2c6 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -1,15 +1,15 @@
import { library } from '@fortawesome/fontawesome-svg-core';
-import { faAngleRight, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
+import { faAngleRight, faCamera, faExpand, faBell, faCaretDown, faCaretRight, faCaretSquareDown, faCaretSquareRight, faTrashAlt } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';
-import { action, computed, observable } from "mobx";
+import { action, computed, observable, trace } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCast, HeightSym, WidthSym } from '../../../new_fields/Doc';
+import { Doc, DocListCast, HeightSym, WidthSym, Opt } from '../../../new_fields/Doc';
import { Id } from '../../../new_fields/FieldSymbols';
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 } from '../../documents/Documents';
+import { Docs, DocUtils, DocTypes } from '../../documents/Documents';
import { DocumentManager } from '../../util/DocumentManager';
import { DragManager, dropActionType, SetupDrag } from "../../util/DragManager";
import { SelectionManager } from '../../util/SelectionManager';
@@ -50,6 +50,9 @@ export interface TreeViewProps {
library.add(faTrashAlt);
library.add(faAngleRight);
+library.add(faBell);
+library.add(faCamera);
+library.add(faExpand);
library.add(faCaretDown);
library.add(faCaretRight);
library.add(faCaretSquareDown);
@@ -65,6 +68,7 @@ class TreeView extends React.Component<TreeViewProps> {
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;
@computed get fieldKey() {
@@ -152,22 +156,15 @@ class TreeView extends React.Component<TreeViewProps> {
</div>;
}
- titleClicked = (e: React.MouseEvent) => {
- if (this._collapsed) return false;
- else {
- this.props.document.embed = !BoolCast(this.props.document.embed);
- return true;
- }
- }
static loadId = "";
editableView = (key: string, style?: string) => (<EditableView
oneLine={true}
display={"inline"}
editing={this.resolvedDataDoc[Id] === TreeView.loadId}
contents={StrCast(this.props.document[key])}
- onClick={this.titleClicked}
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) => {
@@ -221,7 +218,8 @@ class TreeView extends React.Component<TreeViewProps> {
return <>
<div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown}
style={{
- background: BoolCast(this.props.document.protoBrush, false) ? "#06123232" : BoolCast(this.props.document.libraryBrush, false) ? "#06121212" : "0",
+ 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"
}}
>
@@ -235,20 +233,22 @@ class TreeView extends React.Component<TreeViewProps> {
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.resolvedDataDoc)) });
- ContextMenu.Instance.addItem({ description: "Open Fields", event: () => { let kvp = Docs.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.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, 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)) });
+ 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.addItem({ description: "Open Fields", event: () => { let kvp = Docs.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();
}
}
@@ -266,6 +266,8 @@ class TreeView extends React.Component<TreeViewProps> {
e.stopPropagation();
}
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));
@@ -273,7 +275,6 @@ class TreeView extends React.Component<TreeViewProps> {
addDoc = (doc: Doc) => { docList && docList.push(doc); return true; };
}
}
- e.stopPropagation();
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)
@@ -312,13 +313,37 @@ class TreeView extends React.Component<TreeViewProps> {
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);
- let docWidth = () => NumCast(this.props.document.nativeWidth) ? Math.min(this.props.document[WidthSym](), this.props.panelWidth() - 5) : this.props.panelWidth() - 5;
+
if (!this._collapsed) {
if (!this.props.document.embed) {
contentElement = <ul key={this._chosenKey + "more"}>
@@ -328,13 +353,14 @@ class TreeView extends React.Component<TreeViewProps> {
</ul >;
} else {
let layoutDoc = Doc.expandTemplateLayout(this.props.document, this.props.dataDoc);
- contentElement = <div ref={this._dref} style={{ display: "inline-block", height: layoutDoc[HeightSym]() }} key={this.props.document[Id]}>
+ 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}
- width={docWidth}
- height={layoutDoc[HeightSym]}
+ 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}
@@ -445,9 +471,12 @@ export class CollectionTreeView extends CollectionSubView(Document) {
}
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);
}
}
@@ -456,6 +485,31 @@ export class CollectionTreeView extends CollectionSubView(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);
+ }
+ }
+ @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 >;
+ }
+
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);
@@ -480,6 +534,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
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)}
<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,
diff --git a/src/client/views/collections/CollectionVideoView.tsx b/src/client/views/collections/CollectionVideoView.tsx
index c1a6ca44e..1984965ba 100644
--- a/src/client/views/collections/CollectionVideoView.tsx
+++ b/src/client/views/collections/CollectionVideoView.tsx
@@ -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/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 169dbe540..7a22b7ec3 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,6 +1,6 @@
import { action, computed } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc";
+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";
@@ -27,12 +27,18 @@ import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
import v5 = require("uuid/v5");
+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]>;
@@ -50,9 +56,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
@computed get nativeHeight() { return this.Document.nativeHeight || 0; }
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.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 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());
@@ -243,16 +249,12 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
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.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;
- this.props.Document.panY = panY;
- }
- else {
-
- this.props.Document.panY = panY;
}
}
@@ -388,16 +390,36 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
};
}
+ 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.getChildDocumentViewProps(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;
@@ -438,6 +460,36 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}
}
});
+ ContextMenu.Instance.addItem({
+ description: "Add freeform arrangement",
+ event: () => {
+ let addOverlay = (key: "arrangeScript" | "arrangeInit", options: OverlayElementOptions, params?: Record<string, string>, requiredType?: string) => {
+ let overlayDisposer: () => void;
+ 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 = () => [
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 2a78cda2f..4850c6218 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -44,14 +44,12 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
_commandExecuted = false;
@action
- cleanupInteractions = (all: boolean = false, rem_keydown: boolean = true) => {
+ cleanupInteractions = (all: boolean = false) => {
if (all) {
document.removeEventListener("pointerup", this.onPointerUp, true);
document.removeEventListener("pointermove", this.onPointerMove, true);
}
- if (rem_keydown) {
- document.removeEventListener("keydown", this.marqueeCommand, true);
- }
+ document.removeEventListener("keydown", this.marqueeCommand, true);
this._visible = false;
}
@@ -191,12 +189,9 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
SelectionManager.DeselectAll(mselect.length ? undefined : this.props.container.props.Document);
}
this.props.selectDocuments(mselect.length ? mselect : [this.props.container.props.Document]);
- mselect.length ? this.cleanupInteractions(true, false) : this.cleanupInteractions(true);
- }
- else {
- //console.log("invisible");
- this.cleanupInteractions(true);
}
+ //console.log("invisible");
+ this.cleanupInteractions(true);
if (e.altKey) {
e.preventDefault();
@@ -248,7 +243,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
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" || e.key === "e") {
this._commandExecuted = true;
e.stopPropagation();
e.preventDefault();
@@ -266,18 +261,17 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
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 newCollection = Docs.FreeformDocument(selected, {
x: bounds.left,
y: bounds.top,
panX: 0,
panY: 0,
- borderRounding: e.key === "e" ? -1 : undefined,
+ borderRounding: e.key === "e" ? "100%" : undefined,
backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white",
width: bounds.width,
height: bounds.height,
ink: inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined,
- title: e.key === "s" || e.key === "S" ? "-summary-" : e.key === "p" ? "-summary-" : "a nested collection",
+ title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection",
});
this.marqueeInkDelete(inkData);