aboutsummaryrefslogtreecommitdiff
path: root/src/client/views/collections
diff options
context:
space:
mode:
authormonikahedman <monika_hedman@brown.edu>2019-08-23 11:04:56 -0400
committermonikahedman <monika_hedman@brown.edu>2019-08-23 11:04:56 -0400
commitdd4227a125c0cd679f6437fab85b1cd772a34f78 (patch)
tree20f8b37248c3bbcdf68c3e6207f312d54798621b /src/client/views/collections
parent1fb290bcc1c46214cfd553f31c1282d2694530ea (diff)
parent20f7d2dca1c115c84f6ac89981ef1e3c7c9a2757 (diff)
pulled from master
Diffstat (limited to 'src/client/views/collections')
-rw-r--r--src/client/views/collections/CollectionStackingView.tsx13
-rw-r--r--src/client/views/collections/CollectionStackingViewFieldColumn.tsx4
-rw-r--r--src/client/views/collections/CollectionTreeView.tsx89
-rw-r--r--src/client/views/collections/CollectionViewChromes.scss73
-rw-r--r--src/client/views/collections/CollectionViewChromes.tsx101
-rw-r--r--src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx59
-rw-r--r--src/client/views/collections/collectionFreeForm/MarqueeView.tsx20
7 files changed, 293 insertions, 66 deletions
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx
index c74c60d8f..4ab656744 100644
--- a/src/client/views/collections/CollectionStackingView.tsx
+++ b/src/client/views/collections/CollectionStackingView.tsx
@@ -287,15 +287,18 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) {
masonryChildren(docs: Doc[]) {
this._docXfs.length = 0;
return docs.map((d, i) => {
+ const pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, d);
+ if (!pair.layout || pair.data instanceof Promise) {
+ return (null);
+ }
let dref = React.createRef<HTMLDivElement>();
- let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc);
let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1);
- let height = () => this.getDocHeight(layoutDoc);
- let dxf = () => this.getDocTransform(layoutDoc, dref.current!);
+ let height = () => this.getDocHeight(pair.layout);
+ let dxf = () => this.getDocTransform(pair.layout!, dref.current!);
let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap);
this._docXfs.push({ dxf: dxf, width: width, height: height });
- return <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
- {this.getDisplayDoc(layoutDoc, d, dxf, width)}
+ return !pair.layout ? (null) : <div className="collectionStackingView-masonryDoc" key={d[Id]} ref={dref} style={{ gridRowEnd: `span ${rowSpan}` }} >
+ {this.getDisplayDoc(pair.layout, pair.data, dxf, width)}
</div>;
});
}
diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
index cc8476548..2536eff00 100644
--- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
+++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx
@@ -83,9 +83,9 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC
return (null);
}
let width = () => Math.min(d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? d[WidthSym]() : Number.MAX_VALUE, parent.columnWidth / parent.numGroupColumns);
- let height = () => parent.getDocHeight(pair!.layout);
+ let height = () => parent.getDocHeight(pair.layout);
let dref = React.createRef<HTMLDivElement>();
- let dxf = () => this.getDocTransform(pair!.layout, dref.current!);
+ let dxf = () => this.getDocTransform(pair.layout!, dref.current!);
this.props.parent._docXfs.push({ dxf: dxf, width: width, height: height });
let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap);
let style = parent.isStackingView ? { width: width(), margin: "auto", marginTop: i === 0 ? 0 : parent.gridGap, height: height() } : { gridRowEnd: `span ${rowSpan}` };
diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx
index ebd385743..7e1aacd5d 100644
--- a/src/client/views/collections/CollectionTreeView.tsx
+++ b/src/client/views/collections/CollectionTreeView.tsx
@@ -28,6 +28,7 @@ import "./CollectionTreeView.scss";
import React = require("react");
import { ComputedField, ScriptField } from '../../../new_fields/ScriptField';
import { KeyValueBox } from '../nodes/KeyValueBox';
+import { ContextMenuProps } from '../ContextMenuItem';
export interface TreeViewProps {
@@ -49,6 +50,8 @@ export interface TreeViewProps {
treeViewId: string;
parentKey: string;
active: () => boolean;
+ showHeaderFields: () => boolean;
+ preventTreeViewOpen: boolean;
}
library.add(faTrashAlt);
@@ -65,7 +68,12 @@ library.add(faArrowsAltH);
library.add(faPlus, faMinus);
@observer
/**
- * Component that takes in a document prop and a boolean whether it's collapsed or not.
+ * Renders a treeView of a collection of documents
+ *
+ * special fields:
+ * treeViewOpen : flag denoting whether the documents sub-tree (contents) is visible or hidden
+ * preventTreeViewOpen : ignores the treeViewOpen flag (for allowing a view to not be slaved to other views of the document)
+ * treeViewExpandedView : name of field whose contents are being displayed as the document's subtree
*/
class TreeView extends React.Component<TreeViewProps> {
static loadId = "";
@@ -73,7 +81,9 @@ class TreeView extends React.Component<TreeViewProps> {
private _treedropDisposer?: DragManager.DragDropDisposer;
private _dref = React.createRef<HTMLDivElement>();
get defaultExpandedView() { return this.childDocs ? this.fieldKey : "fields"; }
- @observable _collapsed: boolean = true;
+ @observable _overrideTreeViewOpen = false; // override of the treeViewOpen field allowing the display state to be independent of the document's state
+ @computed get treeViewOpen() { return (BoolCast(this.props.document.treeViewOpen) && !this.props.preventTreeViewOpen) || this._overrideTreeViewOpen; }
+ set treeViewOpen(c: boolean) { if (this.props.preventTreeViewOpen) this._overrideTreeViewOpen = c; else this.props.document.treeViewOpen = c; }
@computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, this.defaultExpandedView); }
@computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); }
@computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; }
@@ -146,7 +156,7 @@ class TreeView extends React.Component<TreeViewProps> {
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);
+ let inside = x[0] > bounds[0] + 75;
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";
@@ -163,20 +173,21 @@ class TreeView extends React.Component<TreeViewProps> {
fontStyle={style}
fontSize={12}
GetValue={() => StrCast(this.props.document[key])}
- SetValue={(value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true}
- OnFillDown={(value: string) => {
+ SetValue={undoBatch((value: string) => (Doc.GetProto(this.dataDoc)[key] = value) ? true : true)}
+ OnFillDown={undoBatch((value: string) => {
Doc.GetProto(this.dataDoc)[key] = value;
let doc = this.props.document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.document.detailedLayout)) : undefined;
if (!doc) 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()}
+ })}
+ OnTab={() => { TreeView.loadId = ""; this.props.indentDocument && this.props.indentDocument(); }}
/>)
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
if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) {
+ ContextMenu.Instance.addItem({ description: "Pin to Presentation", event: () => this.props.pinToPres(this.props.document), icon: "tv" });
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.dataDoc).length) {
@@ -200,7 +211,7 @@ class TreeView extends React.Component<TreeViewProps> {
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);
+ let inside = x[0] > bounds[0] + 75 || (!before && this.treeViewOpen);
if (de.data instanceof DragManager.LinkDragData) {
let sourceDoc = de.data.linkSourceDocument;
let destDoc = this.props.document;
@@ -256,14 +267,14 @@ class TreeView extends React.Component<TreeViewProps> {
let rows: JSX.Element[] = [];
for (let key of Object.keys(ids).slice().sort()) {
let contents = doc[key];
- let contentElement: JSX.Element[] | JSX.Element = [];
+ let contentElement: (JSX.Element | null)[] | JSX.Element = [];
if (contents instanceof Doc || Cast(contents, listSpec(Doc))) {
let remDoc = (doc: Doc) => this.remove(doc, key);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending));
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending, true));
contentElement = TreeView.GetChildElements(contents instanceof Doc ? [contents] :
DocListCast(contents), this.props.treeViewId, doc, undefined, key, addDoc, remDoc, this.move,
- this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth);
+ this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen);
} else {
contentElement = <EditableView
key="editableView"
@@ -288,14 +299,14 @@ class TreeView extends React.Component<TreeViewProps> {
const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined;
if (expandKey !== undefined) {
let remDoc = (doc: Doc) => this.remove(doc, expandKey);
- let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending));
+ let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, expandKey, doc, addBefore, before, !BoolCast(this.props.document.stackingHeadersSortDescending, true));
let docs = expandKey === "links" ? this.childLinks : this.childDocs;
return <ul key={expandKey + "more"}>
{!docs ? (null) :
TreeView.GetChildElements(docs as Doc[], this.props.treeViewId, this.props.document.layout as Doc,
this.resolvedDataDoc, expandKey, addDoc, remDoc, this.move,
this.props.dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
- this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)}
+ this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth, this.props.showHeaderFields, this.props.preventTreeViewOpen)}
</ul >;
} else if (this.treeViewExpandedView === "fields") {
return <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}>
@@ -329,8 +340,8 @@ class TreeView extends React.Component<TreeViewProps> {
@computed
get renderBullet() {
- return <div className="bullet" onClick={action(() => this._collapsed = !this._collapsed)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
- {<FontAwesomeIcon icon={this._collapsed ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
+ return <div className="bullet" onClick={action(() => this.treeViewOpen = !this.treeViewOpen)} style={{ color: StrCast(this.props.document.color, "black"), opacity: 0.4 }}>
+ {<FontAwesomeIcon icon={!this.treeViewOpen ? (this.childDocs ? "caret-square-right" : "caret-right") : (this.childDocs ? "caret-square-down" : "caret-down")} />}
</div>;
}
/**
@@ -344,13 +355,13 @@ class TreeView extends React.Component<TreeViewProps> {
let headerElements = (
<span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView}
onPointerDown={action(() => {
- if (!this._collapsed) {
+ if (this.treeViewOpen) {
this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" :
this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" :
this.treeViewExpandedView === "layout" && this.props.document.links ? "links" :
this.childDocs ? this.fieldKey : "fields";
}
- this._collapsed = false;
+ this.treeViewOpen = true;
})}>
{this.treeViewExpandedView}
</span>);
@@ -368,7 +379,7 @@ class TreeView extends React.Component<TreeViewProps> {
}} >
{this.editableView("title")}
</div >
- {headerElements}
+ {this.props.showHeaderFields() ? headerElements : (null)}
{openRight}
</>;
}
@@ -381,13 +392,13 @@ class TreeView extends React.Component<TreeViewProps> {
{this.renderTitle}
</div>
<div className="treeViewItem-border">
- {this._collapsed ? (null) : this.renderContent}
+ {!this.treeViewOpen ? (null) : this.renderContent}
</div>
</li>
</div>;
}
public static GetChildElements(
- docs: Doc[],
+ docList: Doc[],
treeViewId: string,
containingCollection: Doc,
dataDoc: Doc | undefined,
@@ -402,8 +413,11 @@ class TreeView extends React.Component<TreeViewProps> {
outerXf: () => { translateX: number, translateY: number },
active: () => boolean,
panelWidth: () => number,
- renderDepth: number
+ renderDepth: number,
+ showHeaderFields: () => boolean,
+ preventTreeViewOpen: boolean
) {
+ let docs = docList.filter(child => !child.excludeFromLibrary && child.opacity !== 0);
let viewSpecScript = Cast(containingCollection.viewSpecScript, ScriptField);
if (viewSpecScript) {
let script = viewSpecScript.script;
@@ -418,7 +432,7 @@ class TreeView extends React.Component<TreeViewProps> {
});
}
- let descending = BoolCast(containingCollection.stackingHeadersSortDescending);
+ let descending = BoolCast(containingCollection.stackingHeadersSortDescending, true);
docs.slice().sort(function (a, b): 1 | -1 {
let descA = descending ? b : a;
let descB = descending ? a : b;
@@ -448,11 +462,14 @@ class TreeView extends React.Component<TreeViewProps> {
}
let indent = i === 0 ? undefined : () => {
- if (StrCast(docs[i - 1].layout).indexOf("CollectionView") !== -1) {
+ if (StrCast(docs[i - 1].layout).indexOf("fieldKey") !== -1) {
let fieldKeysub = StrCast(docs[i - 1].layout).split("fieldKey")[1];
let fieldKey = fieldKeysub.split("\"")[1];
- Doc.AddDocToList(docs[i - 1], fieldKey, child);
- remove(child);
+ if (fieldKey && Cast(docs[i - 1][fieldKey], listSpec(Doc)) !== undefined) {
+ Doc.AddDocToList(docs[i - 1], fieldKey, child);
+ docs[i - 1].treeViewOpen = true;
+ remove(child);
+ }
}
};
let addDocument = (doc: Doc, relativeTo?: Doc, before?: boolean) => {
@@ -481,7 +498,9 @@ class TreeView extends React.Component<TreeViewProps> {
ScreenToLocalTransform={screenToLocalXf}
outerXf={outerXf}
parentKey={key}
- active={active} />;
+ active={active}
+ showHeaderFields={showHeaderFields}
+ preventTreeViewOpen={preventTreeViewOpen} />;
});
}
}
@@ -523,6 +542,10 @@ export class CollectionTreeView extends CollectionSubView(Document) {
e.stopPropagation();
e.preventDefault();
ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15);
+ } else {
+ let layoutItems: ContextMenuProps[] = [];
+ layoutItems.push({ description: this.props.Document.preventTreeViewOpen ? "Persist Treeview State" : "Abandon Treeview State", event: () => this.props.Document.preventTreeViewOpen = !this.props.Document.preventTreeViewOpen, icon: "paint-brush" });
+ ContextMenu.Instance.addItem({ description: "Treeview Options ...", subitems: layoutItems, icon: "eye" });
}
}
outerXf = () => Utils.GetScreenTransform(this._mainEle!);
@@ -561,7 +584,7 @@ export class CollectionTreeView extends CollectionSubView(Document) {
render() {
Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey);
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, !BoolCast(this.props.Document.stackingHeadersSortDescending));
+ let addDoc = (doc: Doc, relativeTo?: Doc, before?: boolean) => Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, relativeTo, before, false, false, !BoolCast(this.props.Document.stackingHeadersSortDescending, true));
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"
@@ -575,20 +598,22 @@ export class CollectionTreeView extends CollectionSubView(Document) {
display={"block"}
height={72}
GetValue={() => StrCast(this.resolvedDataDoc.title)}
- SetValue={(value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true}
- OnFillDown={(value: string) => {
+ SetValue={undoBatch((value: string) => (Doc.GetProto(this.resolvedDataDoc).title = value) ? true : true)}
+ OnFillDown={undoBatch((value: string) => {
Doc.GetProto(this.props.Document).title = value;
let doc = this.props.Document.detailedLayout instanceof Doc ? Doc.ApplyTemplate(Doc.GetProto(this.props.Document.detailedLayout)) : undefined;
if (!doc) 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, !BoolCast(this.props.Document.stackingHeadersSortDescending));
- }} />
+ Doc.AddDocToList(this.props.Document, this.props.fieldKey, doc, this.childDocs.length ? this.childDocs[0] : undefined, true, false, false, !BoolCast(this.props.Document.stackingHeadersSortDescending, true));
+ })} />
{this.props.Document.workspaceLibrary ? this.renderNotifsButton : (null)}
{this.props.Document.allowClear ? this.renderClearButton : (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.pinToPres, this.props.ScreenToLocalTransform, this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth)
+ moveDoc, dropAction, this.props.addDocTab, this.props.pinToPres, this.props.ScreenToLocalTransform,
+ this.outerXf, this.props.active, this.props.PanelWidth, this.props.renderDepth, () => this.props.Document.chromeStatus !== "disabled",
+ BoolCast(this.props.Document.preventTreeViewOpen))
}
</ul>
</div >
diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss
index f39bd877a..64411b5fe 100644
--- a/src/client/views/collections/CollectionViewChromes.scss
+++ b/src/client/views/collections/CollectionViewChromes.scss
@@ -64,7 +64,7 @@
font-size: 75%;
background: rgb(238, 238, 238);
height: 100%;
- width: 150px;
+ width: 75px;
}
.collectionViewBaseChrome-viewSpecsMenu {
@@ -234,4 +234,75 @@
margin-left: 50px;
}
}
+}
+
+
+.commandEntry-outerDiv {
+ display: flex;
+ flex-direction: column;
+ width: 165px;
+ height: 40px;
+}
+.commandEntry-inputArea {
+ display:flex;
+ flex-direction: row;
+ width: 150px;
+ margin: auto 0 auto auto;
+}
+
+.react-autosuggest__container {
+ position: relative;
+ width: 100%;
+ margin-left: 5px;
+ margin-right: 5px;
+}
+
+.react-autosuggest__input {
+ border: 1px solid #aaa;
+ border-radius: 4px;
+ width: 100%;
+}
+
+.react-autosuggest__input--focused {
+ outline: none;
+}
+
+.react-autosuggest__input--open {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+}
+
+.react-autosuggest__suggestions-container {
+ display: none;
+}
+
+.react-autosuggest__suggestions-container--open {
+ display: block;
+ position: fixed;
+ overflow-y: auto;
+ max-height: 400px;
+ width: 180px;
+ border: 1px solid #aaa;
+ background-color: #fff;
+ font-family: Helvetica, sans-serif;
+ font-weight: 300;
+ font-size: 16px;
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
+ z-index: 2;
+}
+
+.react-autosuggest__suggestions-list {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+}
+
+.react-autosuggest__suggestion {
+ cursor: pointer;
+ padding: 10px 20px;
+}
+
+.react-autosuggest__suggestion--highlighted {
+ background-color: #ddd;
} \ No newline at end of file
diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx
index 9631243c0..333baf288 100644
--- a/src/client/views/collections/CollectionViewChromes.tsx
+++ b/src/client/views/collections/CollectionViewChromes.tsx
@@ -8,7 +8,7 @@ import { List } from "../../../new_fields/List";
import { listSpec } from "../../../new_fields/Schema";
import { ScriptField } from "../../../new_fields/ScriptField";
import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types";
-import { Utils } from "../../../Utils";
+import { Utils, emptyFunction } from "../../../Utils";
import { DragManager } from "../../util/DragManager";
import { CompileScript } from "../../util/Scripting";
import { undoBatch } from "../../util/UndoManager";
@@ -18,7 +18,9 @@ import { DocLike } from "../MetadataEntryMenu";
import { CollectionViewType } from "./CollectionBaseView";
import { CollectionView } from "./CollectionView";
import "./CollectionViewChromes.scss";
+import * as Autosuggest from 'react-autosuggest';
import KeyRestrictionRow from "./KeyRestrictionRow";
+import { Docs } from "../../documents/Documents";
const datepicker = require('js-datepicker');
interface CollectionViewChromeProps {
@@ -43,6 +45,8 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
@observable private _dateWithinValue: string = "";
@observable private _dateValue: Date | string = "";
@observable private _keyRestrictions: [JSX.Element, string][] = [];
+ @observable private suggestions: string[] = [];
+ _commandRef = React.createRef<HTMLInputElement>();
@computed private get filterValue() { return Cast(this.props.CollectionView.props.Document.viewSpecScript, ScriptField); }
private _picker: any;
@@ -276,7 +280,12 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
protected drop(e: Event, de: DragManager.DropEvent): boolean {
if (de.data instanceof DragManager.DocumentDragData) {
if (de.data.draggedDocuments.length) {
- this.props.CollectionView.props.Document.childLayout = de.data.draggedDocuments[0];
+ if (this._currentKey === "Set Template") {
+ this.props.CollectionView.props.Document.childLayout = de.data.draggedDocuments[0];
+ }
+ if (this._currentKey === "Set Content") {
+ Doc.GetProto(this.props.CollectionView.props.Document).data = new List<Doc>(de.data.draggedDocuments);
+ }
e.stopPropagation();
return true;
}
@@ -297,13 +306,74 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
datePickerRef = (node: HTMLInputElement) => {
if (node) {
- this._picker = datepicker("#" + node.id, {
- disabler: (date: Date) => date > new Date(),
- onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
- dateSelected: new Date()
- });
+ try {
+ this._picker = datepicker("#" + node.id, {
+ disabler: (date: Date) => date > new Date(),
+ onSelect: (instance: any, date: Date) => runInAction(() => this._dateValue = date),
+ dateSelected: new Date()
+ });
+ } catch (e) {
+ console.log("date picker exception:" + e);
+ }
}
}
+
+ @observable private _currentKey: string = "";
+ @observable _allCommands: string[] = ["Set Template", "Set Content"];
+ private autosuggestRef = React.createRef<Autosuggest>();
+
+ renderSuggestion = (suggestion: string) => {
+ return <p>{suggestion}</p>;
+ }
+ getSuggestionValue = (suggestion: string) => suggestion;
+
+ @action
+ onKeyChange = (e: React.ChangeEvent, { newValue }: { newValue: string }) => {
+ this._currentKey = newValue;
+ }
+ onSuggestionFetch = async ({ value }: { value: string }) => {
+ const sugg = await this.getKeySuggestions(value);
+ runInAction(() => this.suggestions = sugg);
+ }
+ @action
+ onSuggestionClear = () => {
+ this.suggestions = [];
+ }
+ getKeySuggestions = async (value: string): Promise<string[]> => {
+ return this._allCommands.filter(c => c.indexOf(value) !== -1);
+ }
+
+ autoSuggestDown = (e: React.PointerEvent) => {
+ e.stopPropagation();
+ }
+ dragCommandDown = (e: React.PointerEvent) => {
+ let de = new DragManager.DocumentDragData([this.props.CollectionView.props.Document], [undefined]);
+ DragManager.StartDocumentDrag([this._commandRef.current!], de, e.clientX, e.clientY, {
+ finishDrag: (dropData: { [id: string]: any }) => {
+ let bd = Docs.Create.ButtonDocument({ width: 150, height: 50, title: this._currentKey });
+ let script = `getProto(target).data = copyField(this.source);`;
+ let compiled = CompileScript(script, {
+ params: { doc: Doc.name },
+ capturedVariables: { target: this.props.CollectionView.props.Document },
+ typecheck: false,
+ editable: true
+ });
+ if (compiled.compiled) {
+ let scriptField = new ScriptField(compiled);
+ bd.onClick = scriptField;
+ }
+ dropData.droppedDocuments = [bd];
+ },
+ handlers: {
+ dragComplete: action(() => {
+ }),
+ },
+ hideSource: false
+ });
+ e.preventDefault();
+ e.stopPropagation();
+ }
+
render() {
let collapsed = this.props.CollectionView.props.Document.chromeStatus !== "enabled";
return (
@@ -333,7 +403,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
</select>
<div className="collectionViewBaseChrome-viewSpecs" style={{ display: collapsed ? "none" : "grid" }}>
<input className="collectionViewBaseChrome-viewSpecsInput"
- placeholder="FILTER DOCUMENTS"
+ placeholder="FILTER"
value={this.filterValue ? this.filterValue.script.originalScript === "return true" ? "" : this.filterValue.script.originalScript : ""}
onChange={(e) => { }}
onPointerDown={this.openViewSpecs}
@@ -384,8 +454,19 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro
</div>
</div>
</div>
- <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} style={{}}>
- TEMPLATE
+ <div className="collectionViewBaseChrome-template" ref={this.createDropTarget} >
+ <div className="commandEntry-outerDiv" ref={this._commandRef} onPointerDown={this.dragCommandDown}>
+ <div className="commandEntry-inputArea" onPointerDown={this.autoSuggestDown} >
+ <Autosuggest inputProps={{ value: this._currentKey, onChange: this.onKeyChange }}
+ getSuggestionValue={this.getSuggestionValue}
+ suggestions={this.suggestions}
+ alwaysRenderSuggestions={true}
+ renderSuggestion={this.renderSuggestion}
+ onSuggestionsFetchRequested={this.onSuggestionFetch}
+ onSuggestionsClearRequested={this.onSuggestionClear}
+ ref={this.autosuggestRef} />
+ </div>
+ </div>
</div>
</div>
{this.subChrome()}
diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
index 3be6aa3d3..224e8047d 100644
--- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
+++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx
@@ -1,17 +1,18 @@
import { library } from "@fortawesome/fontawesome-svg-core";
import { faEye } from "@fortawesome/free-regular-svg-icons";
-import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faChalkboard, faBraille } from "@fortawesome/free-solid-svg-icons";
-import { action, computed, observable, IReactionDisposer, reaction } from "mobx";
+import { faBraille, faChalkboard, faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons";
+import { action, computed, IReactionDisposer, observable, reaction } from "mobx";
import { observer } from "mobx-react";
-import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast, FieldResult, Field, Opt } from "../../../../new_fields/Doc";
+import { Doc, DocListCastAsync, Field, FieldResult, HeightSym, Opt, WidthSym } from "../../../../new_fields/Doc";
import { Id } from "../../../../new_fields/FieldSymbols";
import { InkField, StrokeData } from "../../../../new_fields/InkField";
import { createSchema, makeInterface } from "../../../../new_fields/Schema";
import { ScriptField } from "../../../../new_fields/ScriptField";
import { BoolCast, Cast, FieldValue, NumCast, StrCast } from "../../../../new_fields/Types";
-import { emptyFunction, returnOne, Utils, returnFalse, returnEmptyString } from "../../../../Utils";
+import { emptyFunction, returnEmptyString, returnOne, Utils } from "../../../../Utils";
import { CognitiveServices } from "../../../cognitive_services/CognitiveServices";
-import { DocServer } from "../../../DocServer";
+import { Docs } from "../../../documents/Documents";
+import { DocumentType } from "../../../documents/DocumentTypes";
import { DocumentManager } from "../../../util/DocumentManager";
import { DragManager } from "../../../util/DragManager";
import { HistoryUtil } from "../../../util/History";
@@ -29,15 +30,14 @@ import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView";
import { pageSchema } from "../../nodes/ImageBox";
import { OverlayElementOptions, OverlayView } from "../../OverlayView";
import PDFMenu from "../../pdf/PDFMenu";
-import { CollectionSubView } from "../CollectionSubView";
import { ScriptBox } from "../../ScriptBox";
+import { CollectionSubView } from "../CollectionSubView";
import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView";
import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors";
import "./CollectionFreeFormView.scss";
import { MarqueeView } from "./MarqueeView";
import React = require("react");
-import { Docs } from "../../../documents/Documents";
-import { DocumentType } from "../../../documents/DocumentTypes";
+import { DocServer } from "../../../DocServer";
library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard);
@@ -345,9 +345,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
}, -1);
if (cluster !== -1) {
let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster);
+
+ // hacky way to get a list of DocumentViews in the current view given a list of Documents in the current view
+ let prevSelected = SelectionManager.SelectedDocuments();
this.selectDocuments(eles);
let clusterDocs = SelectionManager.SelectedDocuments();
SelectionManager.DeselectAll();
+ prevSelected.map(dv => SelectionManager.SelectDoc(dv, true));
+
let de = new DragManager.DocumentDragData(eles, eles.map(d => undefined));
de.moveDocument = this.props.moveDocument;
const [left, top] = clusterDocs[0].props.ScreenToLocalTransform().scale(clusterDocs[0].props.ContentScaling()).inverse().transformPoint(0, 0);
@@ -818,6 +823,36 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
onContextMenu = (e: React.MouseEvent) => {
let layoutItems: ContextMenuProps[] = [];
+ layoutItems.push({
+ description: "Import document", icon: "upload", event: () => {
+ const input = document.createElement("input");
+ input.type = "file";
+ input.accept = ".zip";
+ input.onchange = async _e => {
+ const files = input.files;
+ if (!files) return;
+ const file = files[0];
+ let formData = new FormData();
+ formData.append('file', file);
+ formData.append('remap', "true");
+ const upload = Utils.prepend("/uploadDoc");
+ const response = await fetch(upload, { method: "POST", body: formData });
+ const json = await response.json();
+ if (json === "error") {
+ return;
+ }
+ const doc = await DocServer.GetRefField(json);
+ if (!doc || !(doc instanceof Doc)) {
+ return;
+ }
+ const [x, y] = this.props.ScreenToLocalTransform().transformPoint(e.pageX, e.pageY);
+ doc.x = x, doc.y = y;
+ this.props.addDocument &&
+ this.props.addDocument(doc, false);
+ };
+ input.click();
+ }
+ });
layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" });
layoutItems.push({ description: "reset view", event: () => { this.props.Document.panX = this.props.Document.panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" });
layoutItems.push({
@@ -835,12 +870,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) {
icon: !this.props.Document.useClusters ? "chalkboard" : "chalkboard"
});
layoutItems.push({ description: "Arrange contents in grid", event: this.arrangeContents, icon: "table" });
- ContextMenu.Instance.addItem({ description: "Layout...", subitems: layoutItems, icon: "compass" });
-
- let existingAnalyze = ContextMenu.Instance.findByDescription("Analyzers...");
- let analyzers: ContextMenuProps[] = existingAnalyze && "subitems" in existingAnalyze ? existingAnalyze.subitems : [];
- analyzers.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
- !existingAnalyze && ContextMenu.Instance.addItem({ description: "Analyzers...", subitems: analyzers, icon: "hand-point-right" });
+ layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" });
+ ContextMenu.Instance.addItem({ description: "Freeform Options ...", subitems: layoutItems, icon: "eye" });
}
diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
index 4d318c02c..e70d526c5 100644
--- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
+++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx
@@ -374,7 +374,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
marqueeSelect(selectBackgrounds: boolean = true) {
let selRect = this.Bounds;
let selection: Doc[] = [];
- this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => {
+ this.props.activeDocuments().filter(doc => !doc.isBackground && doc.z === undefined).map(doc => {
var x = NumCast(doc.x);
var y = NumCast(doc.y);
var w = NumCast(doc.width);
@@ -384,7 +384,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
});
if (!selection.length && selectBackgrounds) {
- this.props.activeDocuments().map(doc => {
+ this.props.activeDocuments().filter(doc => doc.z === undefined).map(doc => {
var x = NumCast(doc.x);
var y = NumCast(doc.y);
var w = NumCast(doc.width);
@@ -394,6 +394,22 @@ export class MarqueeView extends React.Component<MarqueeViewProps>
}
});
}
+ if (!selection.length) {
+ let left = this._downX < this._lastX ? this._downX : this._lastX;
+ let top = this._downY < this._lastY ? this._downY : this._lastY;
+ let topLeft = this.props.getContainerTransform().transformPoint(left, top);
+ let size = this.props.getContainerTransform().transformDirection(this._lastX - this._downX, this._lastY - this._downY);
+ let otherBounds = { left: topLeft[0], top: topLeft[1], width: Math.abs(size[0]), height: Math.abs(size[1]) };
+ this.props.activeDocuments().filter(doc => doc.z !== undefined).map(doc => {
+ var x = NumCast(doc.x);
+ var y = NumCast(doc.y);
+ var w = NumCast(doc.width);
+ var h = NumCast(doc.height);
+ if (this.intersectRect({ left: x, top: y, width: w, height: h }, otherBounds)) {
+ selection.push(doc);
+ }
+ });
+ }
return selection;
}