diff options
| author | monikahedman <monika_hedman@brown.edu> | 2019-08-23 11:04:56 -0400 |
|---|---|---|
| committer | monikahedman <monika_hedman@brown.edu> | 2019-08-23 11:04:56 -0400 |
| commit | dd4227a125c0cd679f6437fab85b1cd772a34f78 (patch) | |
| tree | 20f8b37248c3bbcdf68c3e6207f312d54798621b /src/client/views/collections | |
| parent | 1fb290bcc1c46214cfd553f31c1282d2694530ea (diff) | |
| parent | 20f7d2dca1c115c84f6ac89981ef1e3c7c9a2757 (diff) | |
pulled from master
Diffstat (limited to 'src/client/views/collections')
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; } |
