diff options
| author | monikahedman <monika_hedman@brown.edu> | 2019-08-08 15:03:03 -0400 |
|---|---|---|
| committer | monikahedman <monika_hedman@brown.edu> | 2019-08-08 15:03:03 -0400 |
| commit | 8f951d9110d096d665af6fbd295902ef8d3574e8 (patch) | |
| tree | 37cc6881cbf93aeea5deae53a6415d6607377edc /src/client/views/collections | |
| parent | 030af1b9112cd12383abcd7f35142cc382ea4d6a (diff) | |
| parent | 316c241d72fb83aad5f2bf9b143c317fdc906654 (diff) | |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into checkbox
Diffstat (limited to 'src/client/views/collections')
14 files changed, 734 insertions, 496 deletions
diff --git a/src/client/views/collections/CollectionBaseView.tsx b/src/client/views/collections/CollectionBaseView.tsx index c595a4c56..cad87ebcc 100644 --- a/src/client/views/collections/CollectionBaseView.tsx +++ b/src/client/views/collections/CollectionBaseView.tsx @@ -22,6 +22,24 @@ export enum CollectionViewType { Masonry } +export namespace CollectionViewType { + + const stringMapping = new Map<string, CollectionViewType>([ + ["invalid", CollectionViewType.Invalid], + ["freeform", CollectionViewType.Freeform], + ["schema", CollectionViewType.Schema], + ["docking", CollectionViewType.Docking], + ["tree", CollectionViewType.Tree], + ["stacking", CollectionViewType.Stacking], + ["masonry", CollectionViewType.Masonry] + ]); + + export const valueOf = (value: string) => { + return stringMapping.get(value.toLowerCase()); + }; + +} + export interface CollectionRenderProps { addDocument: (document: Doc, allowDuplicates?: boolean) => boolean; removeDocument: (document: Doc) => boolean; @@ -81,7 +99,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { addDocument(doc: Doc, allowDuplicates: boolean = false): boolean { var curPage = NumCast(this.props.Document.curPage, -1); Doc.GetProto(doc).page = curPage; - if (curPage >= 0) { + if (this.props.fieldExt) { // bcz: fieldExt !== undefined means this is an overlay layer Doc.GetProto(doc).annotationOn = this.props.Document; } allowDuplicates = true; @@ -108,8 +126,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { let value = Cast(targetDataDoc[targetField], listSpec(Doc), []); let index = value.reduce((p, v, i) => (v instanceof Doc && v[Id] === doc[Id]) ? i : p, -1); PromiseValue(Cast(doc.annotationOn, Doc)).then(annotationOn => - annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined) - ); + annotationOn === this.dataDoc.Document && (doc.annotationOn = undefined)); if (index !== -1) { value.splice(index, 1); @@ -147,7 +164,7 @@ export class CollectionBaseView extends React.Component<CollectionViewProps> { <div id="collectionBaseView" style={{ pointerEvents: this.props.Document.isBackground ? "none" : "all", - boxShadow: `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}` + boxShadow: this.props.Document.isBackground ? undefined : `#9c9396 ${StrCast(this.props.Document.boxShadow, "0.2vw 0.2vw 0.8vw")}` }} className={this.props.className || "collectionView-cont"} onContextMenu={this.props.onContextMenu} ref={this.props.contentRef}> diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 588102f01..77b698a07 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -10,7 +10,7 @@ import { Id } from '../../../new_fields/FieldSymbols'; import { FieldId } from "../../../new_fields/RefField"; import { listSpec } from "../../../new_fields/Schema"; import { Cast, NumCast, StrCast, BoolCast } from "../../../new_fields/Types"; -import { emptyFunction, returnTrue, Utils, returnOne } from "../../../Utils"; +import { emptyFunction, returnTrue, Utils, returnOne, returnEmptyString } from "../../../Utils"; import { DocServer } from "../../DocServer"; import { DocumentManager } from '../../util/DocumentManager'; import { DragLinksAsDocuments, DragManager } from "../../util/DragManager"; @@ -18,7 +18,6 @@ import { SelectionManager } from '../../util/SelectionManager'; import { Transform } from '../../util/Transform'; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocumentView } from "../nodes/DocumentView"; -import { CollectionViewType } from './CollectionBaseView'; import "./CollectionDockingView.scss"; import { SubCollectionViewProps } from "./CollectionSubView"; import { ParentDocSelector } from './ParentDocumentSelector'; @@ -410,10 +409,10 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp tab.reactComponents = [dragSpan, upDiv]; tab.element.append(dragSpan); tab.element.append(upDiv); - tab.reactionDisposer = reaction(() => [doc.title], - () => { - tab.titleElement[0].textContent = doc.title; - }, { fireImmediately: true }); + tab.reactionDisposer = reaction(() => [doc.title, Doc.IsBrushedDegree(doc)], () => { + tab.titleElement[0].textContent = doc.title, { fireImmediately: true }; + tab.titleElement[0].style.outline = `${["transparent", "white", "white"][Doc.IsBrushedDegree(doc)]} ${["none", "dashed", "solid"][Doc.IsBrushedDegree(doc)]} 1px`; + }); //TODO why can't this just be doc instead of the id? tab.titleElement[0].DashDocId = tab.contentItem.config.props.documentId; } @@ -421,9 +420,7 @@ export class CollectionDockingView extends React.Component<SubCollectionViewProp tab.titleElement[0].Tab = tab; tab.closeElement.off('click') //unbind the current click handler .click(async function () { - if (tab.reactionDisposer) { - tab.reactionDisposer(); - } + tab.reactionDisposer && tab.reactionDisposer(); let doc = await DocServer.GetRefField(tab.contentItem.config.props.documentId); if (doc instanceof Doc) { let theDoc = doc; @@ -511,7 +508,7 @@ interface DockedFrameProps { } @observer export class DockedFrameRenderer extends React.Component<DockedFrameProps> { - _mainCont = React.createRef<HTMLDivElement>(); + _mainCont: HTMLDivElement | undefined = undefined; @observable private _panelWidth = 0; @observable private _panelHeight = 0; @observable private _document: Opt<Doc>; @@ -551,6 +548,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { private onActiveContentItemChanged() { if (this.props.glContainer.tab) { this._isActive = this.props.glContainer.tab.isActive; + !this._isActive && this._document && Doc.UnBrushDoc(this._document); // bcz: bad -- trying to simulate a pointer leave event when a new tab is opened up on top of an existing one. } } @@ -569,9 +567,9 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { } ScreenToLocalTransform = () => { - if (this._mainCont.current && this._mainCont.current.children) { - let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.current.children[0].firstChild as HTMLElement); - scale = Utils.GetScreenTransform(this._mainCont.current).scale; + if (this._mainCont && this._mainCont!.children) { + let { scale, translateX, translateY } = Utils.GetScreenTransform(this._mainCont.children[0].firstChild as HTMLElement); + scale = Utils.GetScreenTransform(this._mainCont).scale; return CollectionDockingView.Instance.props.ScreenToLocalTransform().translate(-translateX, -translateY).scale(1 / this.contentScaling() / scale); } return Transform.Identity(); @@ -607,6 +605,7 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { parentActive={returnTrue} whenActiveChanged={emptyFunction} focus={emptyFunction} + backgroundColor={returnEmptyString} addDocTab={this.addDocTab} ContainingCollectionView={undefined} zoomToScale={emptyFunction} @@ -615,7 +614,13 @@ export class DockedFrameRenderer extends React.Component<DockedFrameProps> { @computed get content() { return ( - <div className="collectionDockingView-content" ref={this._mainCont} + <div className="collectionDockingView-content" ref={action((ref: HTMLDivElement) => { + this._mainCont = ref; + if (ref) { + this._panelWidth = Number(getComputedStyle(ref).width!.replace("px", "")); + this._panelHeight = Number(getComputedStyle(ref).height!.replace("px", "")); + } + })} style={{ transform: `translate(${this.previewPanelCenteringOffset}px, 0px)` }}> {this.docView} </div >); diff --git a/src/client/views/collections/CollectionPDFView.tsx b/src/client/views/collections/CollectionPDFView.tsx index 70010819a..8eda4d9ee 100644 --- a/src/client/views/collections/CollectionPDFView.tsx +++ b/src/client/views/collections/CollectionPDFView.tsx @@ -1,66 +1,31 @@ -import { action, IReactionDisposer, observable, reaction } from "mobx"; +import { computed } from "mobx"; import { observer } from "mobx-react"; -import { WidthSym, HeightSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; -import { NumCast } from "../../../new_fields/Types"; import { emptyFunction } from "../../../Utils"; import { ContextMenu } from "../ContextMenu"; import { FieldView, FieldViewProps } from "../nodes/FieldView"; +import { PDFBox } from "../nodes/PDFBox"; import { CollectionBaseView, CollectionRenderProps, CollectionViewType } from "./CollectionBaseView"; import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; import "./CollectionPDFView.scss"; import React = require("react"); -import { PDFBox } from "../nodes/PDFBox"; @observer export class CollectionPDFView extends React.Component<FieldViewProps> { - private _pdfBox?: PDFBox; - private _reactionDisposer?: IReactionDisposer; - private _buttonTray: React.RefObject<HTMLDivElement>; - - constructor(props: FieldViewProps) { - super(props); - - this._buttonTray = React.createRef(); - } - - componentDidMount() { - this._reactionDisposer = reaction( - () => NumCast(this.props.Document.scrollY), - () => { - this.props.Document.panY = NumCast(this.props.Document.scrollY); - }, - { fireImmediately: true } - ); - } - - componentWillUnmount() { - this._reactionDisposer && this._reactionDisposer(); - } - public static LayoutString(fieldKey: string = "data", fieldExt: string = "annotations") { return FieldView.LayoutString(CollectionPDFView, fieldKey, fieldExt); } - @observable _inThumb = false; - private set curPage(value: number) { this._pdfBox && this._pdfBox.GotoPage(value); } - private get curPage() { return NumCast(this.props.Document.curPage, -1); } - private get numPages() { return NumCast(this.props.Document.numPages); } - @action onPageBack = () => this._pdfBox && this._pdfBox.BackPage(); - @action onPageForward = () => this._pdfBox && this._pdfBox.ForwardPage(); + private _pdfBox?: PDFBox; + private _buttonTray: React.RefObject<HTMLDivElement> = React.createRef(); - nativeWidth = () => NumCast(this.props.Document.nativeWidth); - nativeHeight = () => NumCast(this.props.Document.nativeHeight); - private get uIButtons() { - let ratio = (this.curPage - 1) / this.numPages * 100; + @computed + get uIButtons() { return ( <div className="collectionPdfView-buttonTray" ref={this._buttonTray} key="tray" style={{ height: "100%" }}> - <button className="collectionPdfView-backward" onClick={this.onPageBack}>{"<"}</button> - <button className="collectionPdfView-forward" onClick={this.onPageForward}>{">"}</button> - {/* <div className="collectionPdfView-slider" onPointerDown={this.onThumbDown} style={{ top: 60, left: -20, width: 50, height: `calc(100% - 80px)` }} > - <div className="collectionPdfView-thumb" onPointerDown={this.onThumbDown} style={{ top: `${ratio}%`, width: 50, height: 50 }} /> - </div> */} + <button className="collectionPdfView-backward" onClick={() => this._pdfBox && this._pdfBox.BackPage()}>{"<"}</button> + <button className="collectionPdfView-forward" onClick={() => this._pdfBox && this._pdfBox.ForwardPage()}>{">"}</button> </div> ); } @@ -73,20 +38,16 @@ export class CollectionPDFView extends React.Component<FieldViewProps> { setPdfBox = (pdfBox: PDFBox) => { this._pdfBox = pdfBox; }; - - private subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { - let props = { ...this.props, ...renderProps }; - return ( - <> - <CollectionFreeFormView {...props} setPdfBox={this.setPdfBox} CollectionView={this} chromeCollapsed={true} /> - {renderProps.active() ? this.uIButtons : (null)} - </> - ); + subView = (_type: CollectionViewType, renderProps: CollectionRenderProps) => { + return (<> + <CollectionFreeFormView {...this.props} {...renderProps} setPdfBox={this.setPdfBox} CollectionView={this} chromeCollapsed={true} /> + {renderProps.active() ? this.uIButtons : (null)} + </>); } render() { return ( - <CollectionBaseView {...this.props} className={`collectionPdfView-cont${this._inThumb ? "-dragging" : ""}`} onContextMenu={this.onContextMenu}> + <CollectionBaseView {...this.props} className={"collectionPdfView-cont"} onContextMenu={this.onContextMenu}> {this.subView} </CollectionBaseView> ); diff --git a/src/client/views/collections/CollectionSchemaCells.tsx b/src/client/views/collections/CollectionSchemaCells.tsx index 4ff65b277..7e3061354 100644 --- a/src/client/views/collections/CollectionSchemaCells.tsx +++ b/src/client/views/collections/CollectionSchemaCells.tsx @@ -175,11 +175,11 @@ export class CollectionSchemaCell extends React.Component<CellProps> { }; let onPointerEnter = (e: React.PointerEvent): void => { if (e.buttons === 1 && SelectionManager.GetIsDragging() && (type === "document" || type === undefined)) { - dragRef!.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; + dragRef.current!.className = "collectionSchemaView-cellContainer doc-drag-over"; } }; let onPointerLeave = (e: React.PointerEvent): void => { - dragRef!.current!.className = "collectionSchemaView-cellContainer"; + dragRef.current!.className = "collectionSchemaView-cellContainer"; }; let contents: any = "incorrect type"; diff --git a/src/client/views/collections/CollectionSchemaHeaders.tsx b/src/client/views/collections/CollectionSchemaHeaders.tsx index dfd65770e..d24f63fbb 100644 --- a/src/client/views/collections/CollectionSchemaHeaders.tsx +++ b/src/client/views/collections/CollectionSchemaHeaders.tsx @@ -13,7 +13,7 @@ import { faFile } from "@fortawesome/free-regular-svg-icons"; import { SchemaHeaderField, RandomPastel, PastelSchemaPalette } from "../../../new_fields/SchemaHeaderField"; import { undoBatch } from "../../util/UndoManager"; -library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile, faSortAmountDown, faSortAmountUp, faTimes); +library.add(faPlus, faFont, faHashtag, faAlignJustify, faCheckSquare, faToggleOn, faFile as any, faSortAmountDown, faSortAmountUp, faTimes); export interface HeaderProps { keyValue: SchemaHeaderField; diff --git a/src/client/views/collections/CollectionSchemaView.tsx b/src/client/views/collections/CollectionSchemaView.tsx index 9efd0d3ec..ebfa737be 100644 --- a/src/client/views/collections/CollectionSchemaView.tsx +++ b/src/client/views/collections/CollectionSchemaView.tsx @@ -6,7 +6,7 @@ import { action, computed, observable, trace, untracked } from "mobx"; import { observer } from "mobx-react"; import ReactTable, { CellInfo, ComponentPropsGetterR, Column, RowInfo, ResizedChangeFunction, Resize } from "react-table"; import "react-table/react-table.css"; -import { emptyFunction, returnOne } from "../../../Utils"; +import { emptyFunction, returnOne, returnEmptyString } from "../../../Utils"; import { Doc, DocListCast, Field, Opt } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; @@ -50,7 +50,7 @@ const columnTypes: Map<string, ColumnType> = new Map([ ["title", ColumnType.String], ["x", ColumnType.Number], ["y", ColumnType.Number], ["width", ColumnType.Number], ["height", ColumnType.Number], ["nativeWidth", ColumnType.Number], ["nativeHeight", ColumnType.Number], ["isPrototype", ColumnType.Boolean], - ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["libraryBrush", ColumnType.Boolean], ["zIndex", ColumnType.Number] + ["page", ColumnType.Number], ["curPage", ColumnType.Number], ["zIndex", ColumnType.Number] ]); @observer @@ -303,14 +303,13 @@ export class SchemaTable extends React.Component<SchemaTableProps> { return resized; }, [] as { "id": string, "value": number }[]); } - - @computed get sorted(): { "id": string, "desc": boolean }[] { + @computed get sorted(): { "id": string, "desc"?: true }[] { return this.columns.reduce((sorted, shf) => { if (shf.desc) { sorted.push({ "id": shf.heading, "desc": shf.desc }); } return sorted; - }, [] as { "id": string, "desc": boolean }[]); + }, [] as { "id": string, "desc"?: true }[]); } @computed get borderWidth() { return Number(COLLECTION_BORDER_WIDTH); } @@ -974,14 +973,16 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre 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%" }}> + return (<div className="collectionSchemaView-previewRegion" + style={{ width: this.props.width(), height: this.props.height() }}> {!this.props.Document || !this.props.width ? (null) : ( <div className="collectionSchemaView-previewDoc" style={{ transform: `translate(${this.centeringOffset}px, 0px)`, borderRadius: this.borderRounding, display: "inline", - height: "100%" + height: this.props.height(), + width: this.props.width() }}> <DocumentView DataDoc={this.props.DataDocument} @@ -999,6 +1000,7 @@ export class CollectionSchemaPreview extends React.Component<CollectionSchemaPre PanelHeight={this.PanelHeight} ContainingCollectionView={this.props.CollectionView} focus={emptyFunction} + backgroundColor={returnEmptyString} parentActive={this.props.active} whenActiveChanged={this.props.whenActiveChanged} bringToFront={emptyFunction} diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 015955816..271ad2d58 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -9,6 +9,10 @@ overflow-y: auto; flex-wrap: wrap; transition: top .5s; + .collectionSchemaView-previewDoc { + height: 100%; + position: absolute; + } .collectionStackingView-docView-container { width: 45%; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 9741b9e89..4a751c84c 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -1,26 +1,25 @@ import React = require("react"); import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, IReactionDisposer, reaction, untracked, observable, runInAction } from "mobx"; +import { CursorProperty } from "csstype"; +import { action, computed, IReactionDisposer, observable, reaction, runInAction } from "mobx"; import { observer } from "mobx-react"; -import { Doc, HeightSym, WidthSym, DocListCast } from "../../../new_fields/Doc"; +import Switch from 'rc-switch'; +import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; -import { BoolCast, NumCast, Cast, StrCast } from "../../../new_fields/Types"; -import { emptyFunction, Utils, returnTrue } from "../../../Utils"; -import { CollectionSchemaPreview } from "./CollectionSchemaView"; -import "./CollectionStackingView.scss"; -import { CollectionSubView, SubCollectionViewProps } from "./CollectionSubView"; -import { undoBatch } from "../../util/UndoManager"; -import { DragManager } from "../../util/DragManager"; +import { List } from "../../../new_fields/List"; +import { listSpec } from "../../../new_fields/Schema"; +import { SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { BoolCast, Cast, NumCast, StrCast } from "../../../new_fields/Types"; +import { emptyFunction } from "../../../Utils"; import { DocumentType } from "../../documents/Documents"; +import { DragManager } from "../../util/DragManager"; import { Transform } from "../../util/Transform"; -import { CursorProperty } from "csstype"; -import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; -import { listSpec } from "../../../new_fields/Schema"; -import { SchemaHeaderField, RandomPastel } from "../../../new_fields/SchemaHeaderField"; -import { List } from "../../../new_fields/List"; +import { undoBatch } from "../../util/UndoManager"; import { EditableView } from "../EditableView"; -import { CollectionViewProps } from "./CollectionBaseView"; -import Switch from 'rc-switch'; +import { CollectionSchemaPreview } from "./CollectionSchemaView"; +import "./CollectionStackingView.scss"; +import { CollectionStackingViewFieldColumn } from "./CollectionStackingViewFieldColumn"; +import { CollectionSubView } from "./CollectionSubView"; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { @@ -32,13 +31,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { _columnStart: number = 0; @observable private cursor: CursorProperty = "grab"; get sectionHeaders() { return Cast(this.props.Document.sectionHeaders, listSpec(SchemaHeaderField)); } - @computed get chromeCollapsed() { return this.props.chromeCollapsed; } @computed get xMargin() { return NumCast(this.props.Document.xMargin, 2 * this.gridGap); } @computed get yMargin() { return NumCast(this.props.Document.yMargin, 2 * this.gridGap); } @computed get gridGap() { return NumCast(this.props.Document.gridGap, 10); } @computed get singleColumn() { return BoolCast(this.props.Document.singleColumn, true); } @computed get columnWidth() { return this.singleColumn ? (this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin) : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); } @computed get filteredChildren() { return this.childDocs.filter(d => !d.isMinimized); } + @computed get sectionFilter() { return this.singleColumn ? StrCast(this.props.Document.sectionFilter) : ""; } get layoutDoc() { // if this document's layout field contains a document (ie, a rendering template), then we will use that @@ -46,35 +45,32 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return this.props.Document.layout instanceof Doc ? this.props.Document.layout : this.props.Document; } + get Sections() { - let sectionFilter = StrCast(this.props.Document.sectionFilter); - let sectionHeaders = this.sectionHeaders; - if (!sectionHeaders) { - this.props.Document.sectionHeaders = sectionHeaders = new List(); - } - let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []])); - if (sectionFilter) { - this.filteredChildren.map(d => { - let sectionValue = (d[sectionFilter] ? d[sectionFilter] : `NO ${sectionFilter.toUpperCase()} VALUE`) as object; - // the next five lines ensures that floating point rounding errors don't create more than one section -syip - let parsed = parseInt(sectionValue.toString()); - let castedSectionValue: any = sectionValue; - if (!isNaN(parsed)) { - castedSectionValue = parsed; - } + if (!this.sectionFilter) return new Map<SchemaHeaderField, Doc[]>(); - // look for if header exists already - let existingHeader = sectionHeaders!.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${sectionFilter.toUpperCase()} VALUE`)); - if (existingHeader) { - fields.get(existingHeader)!.push(d); - } - else { - let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${sectionFilter.toUpperCase()} VALUE`); - fields.set(newSchemaHeader, [d]); - sectionHeaders!.push(newSchemaHeader); - } - }); + if (this.sectionHeaders === undefined) { + this.props.Document.sectionHeaders = new List<SchemaHeaderField>(); } + const sectionHeaders = this.sectionHeaders!; + let fields = new Map<SchemaHeaderField, Doc[]>(sectionHeaders.map(sh => [sh, []] as [SchemaHeaderField, []])); + this.filteredChildren.map(d => { + let sectionValue = (d[this.sectionFilter] ? d[this.sectionFilter] : `NO ${this.sectionFilter.toUpperCase()} VALUE`) as object; + // the next five lines ensures that floating point rounding errors don't create more than one section -syip + let parsed = parseInt(sectionValue.toString()); + let castedSectionValue = !isNaN(parsed) ? parsed : sectionValue; + + // look for if header exists already + let existingHeader = sectionHeaders.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`)); + if (existingHeader) { + fields.get(existingHeader)!.push(d); + } + else { + let newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.sectionFilter.toUpperCase()} VALUE`); + fields.set(newSchemaHeader, [d]); + sectionHeaders.push(newSchemaHeader); + } + }); return fields; } @@ -94,10 +90,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { // reset section headers when a new filter is inputted this._sectionFilterDisposer = reaction( - () => StrCast(this.props.Document.sectionFilter), - () => { - this.props.Document.sectionHeaders = new List(); - } + () => this.sectionFilter, + () => this.props.Document.sectionHeaders = new List() ); } componentWillUnmount() { @@ -184,8 +178,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { - let targInd = -1; let where = [de.x, de.y]; + let targInd = -1; if (de.data instanceof DragManager.DocumentDragData) { this._docXfs.map((cd, i) => { let pos = cd.dxf().inverse().transformPoint(-2 * this.gridGap, -2 * this.gridGap); @@ -231,8 +225,9 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } }); } + headings = () => Array.from(this.Sections.keys()); section = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { - let key = StrCast(this.props.Document.sectionFilter); + let key = this.sectionFilter; let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; let types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]); if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { @@ -243,7 +238,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return <CollectionStackingViewFieldColumn key={heading ? heading.heading : ""} cols={cols} - headings={() => Array.from(this.Sections.keys())} + headings={this.headings} heading={heading ? heading.heading : ""} headingObject={heading} docList={docList} @@ -252,16 +247,13 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { createDropTarget={this.createDropTarget} screenToLocalTransform={this.props.ScreenToLocalTransform} />; - } @action addGroup = (value: string) => { - if (value) { - if (this.sectionHeaders) { - this.sectionHeaders.push(new SchemaHeaderField(value)); - return true; - } + if (value && this.sectionHeaders) { + this.sectionHeaders.push(new SchemaHeaderField(value)); + return true; } return false; } @@ -274,11 +266,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } onToggle = (checked: Boolean) => { - if (checked) { - this.props.CollectionView.props.Document.chromeStatus = 'collapsed'; - } else { - this.props.CollectionView.props.Document.chromeStatus = 'view-mode'; - } + this.props.CollectionView.props.Document.chromeSatus = checked ? "collapsed" : "view-mode"; } render() { @@ -294,14 +282,10 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { return ( <div className="collectionStackingView" ref={this.createRef} onDrop={this.onDrop.bind(this)} onWheel={(e: React.WheelEvent) => e.stopPropagation()} > - {/* {sectionFilter as boolean ? [ - ["width > height", this.filteredChildren.filter(f => f[WidthSym]() >= 1 + f[HeightSym]())], - ["width = height", this.filteredChildren.filter(f => Math.abs(f[WidthSym]() - f[HeightSym]()) < 1)], - ["height > width", this.filteredChildren.filter(f => f[WidthSym]() + 1 <= f[HeightSym]())]]. */} - {this.props.Document.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). - map(section => this.section(section[0], section[1])) : + {this.sectionFilter ? Array.from(this.Sections.entries()).sort(this.sortFunc). + map((section: [SchemaHeaderField, Doc[]]) => this.section(section[0], section[1])) : this.section(undefined, this.filteredChildren)} - {(this.props.Document.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ? + {(this.sectionFilter && (this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled')) ? <div key={`${this.props.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: (this.columnWidth / (headings.length + ((this.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? 1 : 0))) - 10, marginTop: 10 }}> <EditableView {...editableViewProps} /> diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index b1e6eada0..24bd24d11 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -25,7 +25,6 @@ import { CollectionSchemaPreview } from './CollectionSchemaView'; import { CollectionSubView } from "./CollectionSubView"; import "./CollectionTreeView.scss"; import React = require("react"); -import { LinkManager } from '../../util/LinkManager'; import { ComputedField } from '../../../new_fields/ScriptField'; import { KeyValueBox } from '../nodes/KeyValueBox'; @@ -67,36 +66,31 @@ library.add(faPlus, faMinus); * Component that takes in a document prop and a boolean whether it's collapsed or not. */ class TreeView extends React.Component<TreeViewProps> { + static loadId = ""; private _header?: React.RefObject<HTMLDivElement> = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef<HTMLDivElement>(); - @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "data"); } - @computed get MAX_EMBED_HEIGHT() { return NumCast(this.props.document.maxEmbedHeight, 300); } + get defaultExpandedView() { return this.childDocs ? this.fieldKey : "fields"; } @observable _collapsed: boolean = true; - + @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; } @computed get fieldKey() { - let target = this.props.document; - let keys = Array.from(Object.keys(target)); // bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set - if (target.proto instanceof Doc) { - let arr = Array.from(Object.keys(target.proto));// bcz: Argh -- make untracked to avoid this rerunning whenever 'libraryBrush' is set - keys.push(...arr); - while (keys.indexOf("proto") !== -1) keys.splice(keys.indexOf("proto"), 1); - } - let keyList: string[] = []; - keys.map(key => { - let docList = Cast(this.dataDoc[key], listSpec(Doc)); - if (docList && docList.length > 0) { - keyList.push(key); - } - }); - let layout = StrCast(this.props.document.layout); - if (layout.indexOf("fieldKey={\"") !== -1 && layout.indexOf("fieldExt=") === -1) { - return layout.split("fieldKey={\"")[1].split("\"")[0]; - } - return keyList.length ? keyList[0] : "data"; + let splits = StrCast(this.props.document.layout).split("fieldKey={\""); + return splits.length > 1 ? splits[1].split("\"")[0] : "data"; + } + @computed get childDocs() { + let layout = this.props.document.layout instanceof Doc ? this.props.document.layout : undefined; + return (this.props.dataDoc ? Cast(this.props.dataDoc[this.fieldKey], listSpec(Doc)) : undefined) || + (layout ? Cast(layout[this.fieldKey], listSpec(Doc)) : undefined) || + Cast(this.props.document[this.fieldKey], listSpec(Doc)); + } + @computed get childLinks() { + let layout = this.props.document.layout instanceof Doc ? this.props.document.layout : undefined; + return (this.props.dataDoc ? Cast(this.props.dataDoc.links, listSpec(Doc)) : undefined) || + (layout instanceof Doc ? Cast(layout.links, listSpec(Doc)) : undefined) || + Cast(this.props.document.links, listSpec(Doc)); } - - @computed get dataDoc() { return this.resolvedDataDoc ? this.resolvedDataDoc : this.props.document; } @computed get resolvedDataDoc() { if (this.props.dataDoc === undefined && this.props.document.layout instanceof Doc) { // if there is no dataDoc (ie, we're not rendering a template layout), but this document @@ -104,34 +98,48 @@ class TreeView extends React.Component<TreeViewProps> { // this document as the data document for the layout. return this.props.document; } - return this.props.dataDoc ? this.props.dataDoc : undefined; + return this.props.dataDoc; + } + @computed get boundsOfCollectionDocument() { + return StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1 ? undefined : + Doc.ComputeContentBounds(DocListCast(this.props.document.data)); } - protected createTreeDropTarget = (ele: HTMLDivElement) => { - this._treedropDisposer && this._treedropDisposer(); - if (ele) { - this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } }); + @undoBatch delete = () => this.props.deleteDoc(this.dataDoc); + @undoBatch openRight = () => this.props.addDocTab(this.props.document, undefined, "onRight"); + @undoBatch indent = () => this.props.addDocument(this.props.document) && this.delete(); + @undoBatch move = (doc: Doc, target: Doc, addDoc: (doc: Doc) => boolean) => { + return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc); + } + @undoBatch @action remove = (document: Document, key: string): boolean => { + let children = Cast(this.dataDoc[key], listSpec(Doc), []); + if (children.indexOf(document) !== -1) { + children.splice(children.indexOf(document), 1); + return true; } + return false; } - @undoBatch delete = () => this.props.deleteDoc(this.dataDoc); - @undoBatch openRight = async () => this.props.addDocTab(this.props.document, undefined, "onRight"); + protected createTreeDropTarget = (ele: HTMLDivElement) => { + this._treedropDisposer && this._treedropDisposer(); + ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.treeDrop.bind(this) } })); + } onPointerDown = (e: React.PointerEvent) => e.stopPropagation(); onPointerEnter = (e: React.PointerEvent): void => { - this.props.active() && (this.props.document.libraryBrush = true); + this.props.active() && Doc.BrushDoc(this.dataDoc); if (e.buttons === 1 && SelectionManager.GetIsDragging()) { this._header!.current!.className = "treeViewItem-header"; document.addEventListener("pointermove", this.onDragMove, true); } } onPointerLeave = (e: React.PointerEvent): void => { - this.props.document.libraryBrush = false; + Doc.UnBrushDoc(this.dataDoc); this._header!.current!.className = "treeViewItem-header"; document.removeEventListener("pointermove", this.onDragMove, true); } onDragMove = (e: PointerEvent): void => { - this.props.document.libraryBrush = false; + Doc.UnBrushDoc(this.dataDoc); let x = this.props.ScreenToLocalTransform().transformPoint(e.clientX, e.clientY); let rect = this._header!.current!.getBoundingClientRect(); let bounds = this.props.ScreenToLocalTransform().transformPoint(rect.left, rect.top + rect.height / 2); @@ -144,34 +152,6 @@ class TreeView extends React.Component<TreeViewProps> { e.stopPropagation(); } - @action - remove = (document: Document, key: string): boolean => { - let children = Cast(this.dataDoc[key], listSpec(Doc), []); - if (children.indexOf(document) !== -1) { - children.splice(children.indexOf(document), 1); - return true; - } - return false; - } - - @action - move: DragManager.MoveFunction = (doc: Doc, target: Doc, addDoc) => { - return this.props.document !== target && this.props.deleteDoc(doc) && addDoc(doc); - } - @action - indent = () => this.props.addDocument(this.props.document) && this.delete() - - renderBullet() { - let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc)); - let doc = Cast(this.dataDoc[this.fieldKey], Doc); - let isDoc = doc instanceof Doc || docList; - let c; - 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 ? (isDoc ? "caret-square-right" : "caret-right") : (isDoc ? "caret-square-down" : "caret-down")} />} - </div>; - } - - static loadId = ""; editableView = (key: string, style?: string) => (<EditableView oneLine={true} display={"inline"} @@ -192,43 +172,6 @@ class TreeView extends React.Component<TreeViewProps> { OnTab={() => this.props.indentDocument && this.props.indentDocument()} />) - /** - * Renders the EditableView title element for placement into the tree. - */ - renderTitle() { - let reference = React.createRef<HTMLDivElement>(); - let onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true); - - let headerElements = ( - <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} - onPointerDown={action(() => { - this.props.document.treeViewExpandedView = this.treeViewExpandedView === "data" ? "fields" : - this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : "data"; - this._collapsed = false; - })}> - {this.treeViewExpandedView} - </span>); - let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : []; - let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : ( - <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}> - <FontAwesomeIcon icon="angle-right" size="lg" /> - </div>); - return <> - <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown} - style={{ - background: BoolCast(this.props.document.libraryBrush) ? "#06121212" : "0", - outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, - pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none" - }} - > - {this.editableView("title")} - {/* {<div className="delete-button" onClick={this.delete}><FontAwesomeIcon icon="trash-alt" size="xs" /></div>} */} - </div > - {headerElements} - {openRight} - </>; - } - onWorkspaceContextMenu = (e: React.MouseEvent): void => { if (!e.isPropagationStopped()) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 if (NumCast(this.props.document.viewType) !== CollectionViewType.Docking) { @@ -289,39 +232,6 @@ class TreeView extends React.Component<TreeViewProps> { let finalXf = this.props.ScreenToLocalTransform().translate(offset[0], offset[1]); return finalXf; } - - renderLinks = () => { - let ele: JSX.Element[] = []; - let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.props.document, this.fieldKey, doc, addBefore, before); - let groups = LinkManager.Instance.getRelatedGroupedLinks(this.props.document); - groups.forEach((groupLinkDocs, groupType) => { - // let destLinks = groupLinkDocs.map(d => LinkManager.Instance.getOppositeAnchor(d, this.props.document)); - let destLinks: Doc[] = []; - groupLinkDocs.forEach((doc) => { - let opp = LinkManager.Instance.getOppositeAnchor(doc, this.props.document); - if (opp) { - destLinks.push(opp); - } - }); - ele.push( - <div key={"treeviewlink-" + groupType + "subtitle"}> - <div className="collectionTreeView-subtitle">{groupType}:</div> - { - TreeView.GetChildElements(destLinks, this.props.treeViewId, this.props.document, this.props.dataDoc, "treeviewlink-" + groupType, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth) - } - </div> - ); - }); - return ele; - } - - @computed get boundsOfCollectionDocument() { - if (StrCast(this.props.document.type).indexOf(DocumentType.COL) === -1) return undefined; - let layoutDoc = this.props.document; - return Doc.ComputeContentBounds(DocListCast(layoutDoc.data)); - } 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)); @@ -337,39 +247,29 @@ class TreeView extends React.Component<TreeViewProps> { })()); } - noOverlays = (doc: Doc) => ({ title: "", caption: "" }); - - expandedField = (doc?: Doc) => { - if (!doc) return <div />; - let realDoc = doc; - + expandedField = (doc: Doc) => { let ids: { [key: string]: string } = {}; - Object.keys(doc).forEach(key => { - if (!(key in ids) && realDoc[key] !== ComputedField.undefined) { - ids[key] = key; - } - }); + doc && Object.keys(doc).forEach(key => !(key in ids) && doc[key] !== ComputedField.undefined && (ids[key] = key)); let rows: JSX.Element[] = []; for (let key of Object.keys(ids).sort()) { - let contents = realDoc[key] ? realDoc[key] : undefined; + let contents = doc[key]; let contentElement: JSX.Element[] | JSX.Element = []; if (contents instanceof Doc || Cast(contents, listSpec(Doc))) { - let docList = contents; let remDoc = (doc: Doc) => this.remove(doc, key); let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before); - contentElement = key === "links" ? this.renderLinks() : - TreeView.GetChildElements(docList instanceof Doc ? [docList] : DocListCast(docList), this.props.treeViewId, realDoc, undefined, key, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); + 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.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth); } else { contentElement = <EditableView key="editableView" - contents={contents ? contents.toString() : "null"} + contents={contents !== undefined ? contents.toString() : "null"} height={13} fontSize={12} - GetValue={() => Field.toKeyValueString(realDoc, key)} - SetValue={(value: string) => KeyValueBox.SetField(realDoc, key, value)} />; + GetValue={() => Field.toKeyValueString(doc, key)} + SetValue={(value: string) => KeyValueBox.SetField(doc, key, value)} />; } rows.push(<div style={{ display: "flex" }} key={key}> <span style={{ fontWeight: "bold" }}>{key + ":"}</span> @@ -380,56 +280,105 @@ class TreeView extends React.Component<TreeViewProps> { return rows; } - render() { - let contentElement: (JSX.Element | null) = null; - let docList = Cast(this.dataDoc[this.fieldKey], listSpec(Doc)); - let remDoc = (doc: Doc) => this.remove(doc, this.fieldKey); - let addDoc = (doc: Doc, addBefore?: Doc, before?: boolean) => Doc.AddDocToList(this.dataDoc, this.fieldKey, doc, addBefore, before); + noOverlays = (doc: Doc) => ({ title: "", caption: "" }); - if (!this._collapsed) { - if (this.treeViewExpandedView === "data") { - let doc = Cast(this.props.document[this.fieldKey], Doc); - contentElement = <ul key={this.fieldKey + "more"}> - {this.fieldKey === "links" ? this.renderLinks() : - TreeView.GetChildElements(doc instanceof Doc ? [doc] : DocListCast(docList), this.props.treeViewId, this.props.document, this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move, - this.props.dropAction, this.props.addDocTab, this.props.ScreenToLocalTransform, this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)} - </ul >; - } else if (this.treeViewExpandedView === "fields") { - contentElement = <ul><div ref={this._dref} style={{ display: "inline-block" }} key={this.props.document[Id] + this.props.document.title}> - {this.expandedField(this.dataDoc)} - </div></ul>; - } else { - let layoutDoc = this.props.document; - contentElement = <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}> - <CollectionSchemaPreview - Document={layoutDoc} - DataDocument={this.resolvedDataDoc} - renderDepth={this.props.renderDepth} - showOverlays={this.noOverlays} - fitToBox={this.boundsOfCollectionDocument !== undefined} - width={this.docWidth} - height={this.docHeight} - getTransform={this.docTransform} - CollectionView={undefined} - addDocument={emptyFunction as any} - moveDocument={this.props.moveDocument} - removeDocument={emptyFunction as any} - active={this.props.active} - whenActiveChanged={emptyFunction as any} - addDocTab={this.props.addDocTab} - setPreviewScript={emptyFunction}> - </CollectionSchemaPreview> - </div>; - } + @computed get renderContent() { + 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); + 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.ScreenToLocalTransform, + this.props.outerXf, this.props.active, this.props.panelWidth, this.props.renderDepth)} + </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}> + {this.dataDoc ? this.expandedField(this.dataDoc) : (null)} + </div></ul>; + } else { + let layoutDoc = this.props.document; + return <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}> + <CollectionSchemaPreview + Document={layoutDoc} + DataDocument={this.resolvedDataDoc} + renderDepth={this.props.renderDepth} + showOverlays={this.noOverlays} + fitToBox={this.boundsOfCollectionDocument !== undefined} + width={this.docWidth} + height={this.docHeight} + getTransform={this.docTransform} + CollectionView={undefined} + addDocument={emptyFunction as any} + moveDocument={this.props.moveDocument} + removeDocument={emptyFunction as any} + active={this.props.active} + whenActiveChanged={emptyFunction as any} + addDocTab={this.props.addDocTab} + setPreviewScript={emptyFunction}> + </CollectionSchemaPreview> + </div>; } + } + + @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")} />} + </div>; + } + /** + * Renders the EditableView title element for placement into the tree. + */ + @computed + get renderTitle() { + let reference = React.createRef<HTMLDivElement>(); + let onItemDown = SetupDrag(reference, () => this.dataDoc, this.move, this.props.dropAction, this.props.treeViewId, true); + + let headerElements = ( + <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} + onPointerDown={action(() => { + if (!this._collapsed) { + 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.treeViewExpandedView} + </span>); + let dataDocs = CollectionDockingView.Instance ? Cast(CollectionDockingView.Instance.props.Document[this.fieldKey], listSpec(Doc), []) : []; + let openRight = dataDocs && dataDocs.indexOf(this.dataDoc) !== -1 ? (null) : ( + <div className="treeViewItem-openRight" onPointerDown={this.onPointerDown} onClick={this.openRight}> + <FontAwesomeIcon icon="angle-right" size="lg" /> + </div>); + return <> + <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown} + style={{ + background: Doc.IsBrushed(this.props.document) ? "#06121212" : "0", + outline: BoolCast(this.props.document.workspaceBrush) ? "dashed 1px #06123232" : undefined, + pointerEvents: this.props.active() || SelectionManager.GetIsDragging() ? "all" : "none" + }} > + {this.editableView("title")} + </div > + {headerElements} + {openRight} + </>; + } + + render() { return <div className="treeViewItem-container" ref={this.createTreeDropTarget} onContextMenu={this.onWorkspaceContextMenu}> <li className="collection-child"> <div className="treeViewItem-header" ref={this._header} onPointerEnter={this.onPointerEnter} onPointerLeave={this.onPointerLeave}> - {this.renderBullet()} - {this.renderTitle()} + {this.renderBullet} + {this.renderTitle} </div> <div className="treeViewItem-border"> - {contentElement} + {this._collapsed ? (null) : this.renderContent} </div> </li> </div>; @@ -454,6 +403,8 @@ class TreeView extends React.Component<TreeViewProps> { let docList = docs.filter(child => !child.excludeFromLibrary); let rowWidth = () => panelWidth() - 20; return docList.map((child, i) => { + let pair = Doc.GetLayoutDataDocPair(containingCollection, dataDoc, key, child); + let indent = i === 0 ? undefined : () => { if (StrCast(docList[i - 1].layout).indexOf("CollectionView") !== -1) { let fieldKeysub = StrCast(docList[i - 1].layout).split("fieldKey")[1]; @@ -470,8 +421,8 @@ class TreeView extends React.Component<TreeViewProps> { return aspect ? Math.min(child[WidthSym](), rowWidth()) / aspect : child[HeightSym](); }; return <TreeView - document={child} - dataDoc={dataDoc} + document={pair.layout} + dataDoc={pair.data} containingCollection={containingCollection} treeViewId={treeViewId} key={child[Id]} @@ -497,7 +448,9 @@ export class CollectionTreeView extends CollectionSubView(Document) { private treedropDisposer?: DragManager.DragDropDisposer; private _mainEle?: HTMLDivElement; - @computed get chromeCollapsed() { return this.props.chromeCollapsed; } + @observable static NotifsCol: Opt<Doc>; + + @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } protected createTreeDropTarget = (ele: HTMLDivElement) => { this.treedropDisposer && this.treedropDisposer(); @@ -529,21 +482,15 @@ export class CollectionTreeView extends CollectionSubView(Document) { ContextMenu.Instance.displayMenu(e.pageX - 15, e.pageY - 15); } } - - @computed get resolvedDataDoc() { return BoolCast(this.props.Document.isTemplate) && this.props.DataDoc ? this.props.DataDoc : this.props.Document; } - outerXf = () => Utils.GetScreenTransform(this._mainEle!); onTreeDrop = (e: React.DragEvent) => this.onDrop(e, {}); - - - @observable static NotifsCol: Opt<Doc>; - openNotifsCol = () => { if (CollectionTreeView.NotifsCol && CollectionDockingView.Instance) { CollectionDockingView.Instance.AddRightSplit(CollectionTreeView.NotifsCol, undefined); } } - @computed get notifsButton() { + + @computed get renderNotifsButton() { const length = CollectionTreeView.NotifsCol ? DocListCast(CollectionTreeView.NotifsCol.data).length : 0; const notifsRef = React.createRef<HTMLDivElement>(); const dragNotifs = action(() => CollectionTreeView.NotifsCol!); @@ -559,18 +506,15 @@ export class CollectionTreeView extends CollectionSubView(Document) { </div> </div >; } - @computed get clearButton() { + @computed get renderClearButton() { return <div id="toolbar" key="toolbar"> - <div > - <button className="toolbar-button round-button" title="Notifs" - onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}> - <FontAwesomeIcon icon={faTrash} size="sm" /> - </button> - </div> + <button className="toolbar-button round-button" title="Notifs" + onClick={undoBatch(action(() => Doc.GetProto(this.props.Document)[this.props.fieldKey] = undefined))}> + <FontAwesomeIcon icon={faTrash} size="sm" /> + </button> </div >; } - render() { Doc.UpdateDocumentExtensionForField(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey); let dropAction = StrCast(this.props.Document.dropAction) as dropActionType; @@ -596,8 +540,8 @@ 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)} - {this.props.Document.allowClear ? this.clearButton : (null)} + {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, diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 57dc5879b..7a402798e 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -1,6 +1,6 @@ import { library } from '@fortawesome/fontawesome-svg-core'; import { faEye } from '@fortawesome/free-regular-svg-icons'; -import { faColumns, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree } from '@fortawesome/free-solid-svg-icons'; +import { faColumns, faEllipsisV, faFingerprint, faImage, faProjectDiagram, faSignature, faSquare, faTh, faThList, faTree, faCopy } from '@fortawesome/free-solid-svg-icons'; import { action, IReactionDisposer, observable, reaction, runInAction } from 'mobx'; import { observer } from "mobx-react"; import * as React from 'react'; @@ -20,16 +20,7 @@ import { CollectionTreeView } from "./CollectionTreeView"; import { CollectionViewBaseChrome } from './CollectionViewChromes'; export const COLLECTION_BORDER_WIDTH = 2; -library.add(faTh); -library.add(faTree); -library.add(faSquare); -library.add(faProjectDiagram); -library.add(faSignature); -library.add(faThList); -library.add(faFingerprint); -library.add(faColumns); -library.add(faEllipsisV); -library.add(faImage, faEye); +library.add(faTh, faTree, faSquare, faProjectDiagram, faSignature, faThList, faFingerprint, faColumns, faEllipsisV, faImage, faEye as any, faCopy); @observer export class CollectionView extends React.Component<FieldViewProps> { @@ -95,7 +86,12 @@ export class CollectionView extends React.Component<FieldViewProps> { onContextMenu = (e: React.MouseEvent): void => { if (!this.isAnnotationOverlay && !e.isPropagationStopped() && this.props.Document[Id] !== CurrentUserUtils.MainDocId) { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 let subItems: ContextMenuProps[] = []; - subItems.push({ description: "Freeform", event: () => this.props.Document.viewType = CollectionViewType.Freeform, icon: "signature" }); + subItems.push({ + description: "Freeform", event: () => { + this.props.Document.viewType = CollectionViewType.Freeform; + delete this.props.Document.usePivotLayout; + }, icon: "signature" + }); if (CollectionBaseView.InSafeMode()) { ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document.viewType = CollectionViewType.Invalid, icon: "project-diagram" }); } @@ -106,6 +102,7 @@ export class CollectionView extends React.Component<FieldViewProps> { switch (this.props.Document.viewType) { case CollectionViewType.Freeform: { subItems.push({ description: "Custom", icon: "fingerprint", event: CollectionFreeFormView.AddCustomLayout(this.props.Document, this.props.fieldKey) }); + subItems.push({ description: "Pivot", icon: "copy", event: () => this.props.Document.usePivotLayout = true }); break; } } diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 1b2561953..52c47e7e8 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -3,7 +3,7 @@ import { CollectionView } from "./CollectionView"; import "./CollectionViewChromes.scss"; import { CollectionViewType } from "./CollectionBaseView"; import { undoBatch } from "../../util/UndoManager"; -import { action, observable, runInAction, computed, IObservable, IObservableValue } from "mobx"; +import { action, observable, runInAction, computed, IObservable, IObservableValue, reaction, autorun } from "mobx"; import { observer } from "mobx-react"; import { Doc, DocListCast } from "../../../new_fields/Doc"; import { DocLike } from "../MetadataEntryMenu"; @@ -187,6 +187,36 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro } } + private get document() { + return this.props.CollectionView.props.Document; + } + + private get pivotKey() { + return StrCast(this.document.pivotField); + } + + private set pivotKey(value: string) { + this.document.pivotField = value; + } + + @observable private pivotKeyDisplay = this.pivotKey; + getPivotInput = () => { + if (!this.document.usePivotLayout) { + return (null); + } + return (<input className="collectionViewBaseChrome-viewSpecsInput" + placeholder="PIVOT ON..." + value={this.pivotKeyDisplay} + onChange={action((e: React.ChangeEvent<HTMLInputElement>) => this.pivotKeyDisplay = e.currentTarget.value)} + onKeyPress={action((e: React.KeyboardEvent<HTMLInputElement>) => { + let value = e.currentTarget.value; + if (e.which === 13) { + this.pivotKey = value; + this.pivotKeyDisplay = ""; + } + })} />); + } + render() { return ( <div className="collectionViewChrome-cont" style={{ top: this._collapsed ? -70 : 0 }}> @@ -219,6 +249,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro value={this.filterValue ? this.filterValue.script.originalScript : ""} onChange={(e) => { }} onPointerDown={this.openViewSpecs} /> + {this.getPivotInput()} <div className="collectionViewBaseChrome-viewSpecsMenu" onPointerDown={this.openViewSpecs} style={{ diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index cca199afa..c4311fa52 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -46,6 +46,7 @@ border-radius: inherit; box-sizing: border-box; position: absolute; + overflow: hidden; .marqueeView { overflow: hidden; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 33a584c6f..ba8dcff98 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,15 +1,15 @@ import { library } from "@fortawesome/fontawesome-svg-core"; import { faEye } from "@fortawesome/free-regular-svg-icons"; -import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload } from "@fortawesome/free-solid-svg-icons"; -import { action, computed } from "mobx"; +import { faCompass, faCompressArrowsAlt, faExpandArrowsAlt, faPaintBrush, faTable, faUpload, faChalkboard, faBraille } from "@fortawesome/free-solid-svg-icons"; +import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCastAsync, HeightSym, WidthSym } from "../../../../new_fields/Doc"; +import { Doc, DocListCastAsync, HeightSym, WidthSym, DocListCast, FieldResult, Field, Opt } 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 } from "../../../../Utils"; +import { emptyFunction, returnOne, Utils, returnFalse, returnEmptyString } from "../../../../Utils"; import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { DocServer } from "../../../DocServer"; import { DocumentManager } from "../../../util/DocumentManager"; @@ -29,16 +29,18 @@ import { DocumentViewProps, positionSchema } from "../../nodes/DocumentView"; import { pageSchema } from "../../nodes/ImageBox"; import { OverlayElementOptions, OverlayView } from "../../OverlayView"; import PDFMenu from "../../pdf/PDFMenu"; -import { ScriptBox } from "../../ScriptBox"; import { CollectionSubView } from "../CollectionSubView"; +import { ScriptBox } from "../../ScriptBox"; import { CollectionFreeFormLinksView } from "./CollectionFreeFormLinksView"; import { CollectionFreeFormRemoteCursors } from "./CollectionFreeFormRemoteCursors"; import "./CollectionFreeFormView.scss"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); -import v5 = require("uuid/v5"); +import { DocumentType, Docs } from "../../../documents/Documents"; +import { RouteStore } from "../../../../server/RouteStore"; +import { string, number, elementType } from "prop-types"; -library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload); +library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); export const panZoomSchema = createSchema({ panX: "number", @@ -48,6 +50,139 @@ export const panZoomSchema = createSchema({ arrangeInit: ScriptField, }); +export interface ViewDefBounds { + x: number; + y: number; + z?: number; + width: number; + height: number; +} + +export interface ViewDefResult { + ele: JSX.Element; + bounds?: ViewDefBounds; +} + +export namespace PivotView { + + export interface PivotData { + type: string; + text: string; + x: number; + y: number; + width: number; + height: number; + fontSize: number; + } + + export const elements = (target: CollectionFreeFormView) => { + let collection = target.Document; + const field = StrCast(collection.pivotField) || "title"; + const width = NumCast(collection.pivotWidth) || 200; + + const groups = new Map<FieldResult<Field>, Doc[]>(); + + for (const doc of target.childDocs) { + const val = doc[field]; + if (val === undefined) continue; + + const l = groups.get(val); + if (l) { + l.push(doc); + } else { + groups.set(val, [doc]); + } + + } + + let minSize = Infinity; + + groups.forEach((val, key) => { + minSize = Math.min(minSize, val.length); + }); + + const numCols = NumCast(collection.pivotNumColumns) || Math.ceil(Math.sqrt(minSize)); + const fontSize = NumCast(collection.pivotFontSize); + + const docMap = new Map<Doc, ViewDefBounds>(); + const groupNames: PivotData[] = []; + + let x = 0; + groups.forEach((val, key) => { + let y = 0; + let xCount = 0; + groupNames.push({ + type: "text", + text: String(key), + x, + y: width + 50, + width: width * 1.25 * numCols, + height: 100, fontSize: fontSize + }); + for (const doc of val) { + docMap.set(doc, { + x: x + xCount * width * 1.25, + y: -y, + width, + height: width + }); + xCount++; + if (xCount >= numCols) { + xCount = 0; + y += width * 1.25; + } + } + x += width * 1.25 * (numCols + 1); + }); + + let elements = target.viewDefsToJSX(groupNames); + let curPage = FieldValue(target.Document.curPage, -1); + + let docViews = target.childDocs.filter(doc => doc instanceof Doc).reduce((prev, doc) => { + var page = NumCast(doc.page, -1); + if ((Math.abs(Math.round(page) - Math.round(curPage)) < 3) || page === -1) { + let minim = BoolCast(doc.isMinimized); + if (minim === undefined || !minim) { + let defaultPosition = (): ViewDefBounds => { + return { + x: NumCast(doc.x), + y: NumCast(doc.y), + z: NumCast(doc.z), + width: NumCast(doc.width), + height: NumCast(doc.height) + }; + }; + const pos = docMap.get(doc) || defaultPosition(); + prev.push({ + ele: ( + <CollectionFreeFormDocumentView + key={doc[Id]} + x={pos.x} + y={pos.y} + width={pos.width} + height={pos.height} + {...target.getChildDocumentViewProps(doc)} + />), + bounds: { + x: pos.x, + y: pos.y, + z: pos.z, + width: NumCast(pos.width), + height: NumCast(pos.height) + } + }); + } + } + return prev; + }, elements); + + target.resetSelectOnLoaded(); + + return docViews; + }; + +} + type PanZoomDocument = makeInterface<[typeof panZoomSchema, typeof positionSchema, typeof pageSchema]>; const PanZoomDocument = makeInterface(panZoomSchema, positionSchema, pageSchema); @@ -67,7 +202,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ComputeContentBounds(boundsList: { x: number, y: number, width: number, height: number }[]) { let bounds = boundsList.reduce((bounds, b) => { var [sptX, sptY] = [b.x, b.y]; - let [bptX, bptY] = [sptX + b.width, sptY + b.height]; + let [bptX, bptY] = [sptX + NumCast(b.width, 1), sptY + NumCast(b.height, 1)]; return { x: Math.min(sptX, bounds.x), y: Math.min(sptY, bounds.y), r: Math.max(bptX, bounds.r), b: Math.max(bptY, bounds.b) @@ -77,7 +212,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @computed get contentBounds() { - let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds).map(e => e.bounds!)) : undefined; + let bounds = this.fitToBox && !this.isAnnotationOverlay ? this.ComputeContentBounds(this.elements.filter(e => e.bounds && !e.bounds.z).map(e => e.bounds!)) : undefined; let res = { panX: bounds ? (bounds.x + bounds.r) / 2 : this.Document.panX || 0, panY: bounds ? (bounds.y + bounds.b) / 2 : this.Document.panY || 0, @@ -98,6 +233,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private centeringShiftX = () => !this.nativeWidth && !this.isAnnotationOverlay ? this._pwidth / 2 / this.parentScaling : 0; // shift so pan position is at center of window for non-overlay collections private centeringShiftY = () => !this.nativeHeight && !this.isAnnotationOverlay ? this._pheight / 2 / this.parentScaling : 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()); + private getTransformOverlay = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth + 1, -this.borderWidth + 1); private getContainerTransform = (): Transform => this.props.ScreenToLocalTransform().translate(-this.borderWidth, -this.borderWidth); private getLocalTransform = (): Transform => Transform.Identity().scale(1 / this.zoomScaling()).translate(this.panX(), this.panY()); private addLiveTextBox = (newBox: Doc) => { @@ -107,6 +243,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { private addDocument = (newBox: Doc, allowDuplicates: boolean) => { this.props.addDocument(newBox, false); this.bringToFront(newBox); + this.updateClusters(); return true; } private selectDocuments = (docs: Doc[]) => { @@ -126,17 +263,38 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return Doc.resolvedFieldDataDoc(this.props.DataDoc ? this.props.DataDoc : this.props.Document, this.props.fieldKey, "true"); } - + intersectRect(r1: { left: number, top: number, width: number, height: number }, + r2: { left: number, top: number, width: number, height: number }) { + return !(r2.left > r1.left + r1.width || r2.left + r2.width < r1.left || r2.top > r1.top + r1.height || r2.top + r2.height < r1.top); + } + _clusterDistance = 75; + boundsOverlap(doc: Doc, doc2: Doc) { + var x2 = NumCast(doc2.x) - this._clusterDistance; + var y2 = NumCast(doc2.y) - this._clusterDistance; + var w2 = NumCast(doc2.width) + this._clusterDistance; + var h2 = NumCast(doc2.height) + this._clusterDistance; + var x = NumCast(doc.x) - this._clusterDistance; + var y = NumCast(doc.y) - this._clusterDistance; + var w = NumCast(doc.width) + this._clusterDistance; + var h = NumCast(doc.height) + this._clusterDistance; + if (doc.z === doc2.z && this.intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 })) { + return true; + } + return false; + } @undoBatch @action drop = (e: Event, de: DragManager.DropEvent) => { let xf = this.getTransform(); + let xfo = this.getTransformOverlay(); + let [xp, yp] = xf.transformPoint(de.x, de.y); + let [xpo, ypo] = xfo.transformPoint(de.x, de.y); if (super.drop(e, de)) { if (de.data instanceof DragManager.DocumentDragData) { if (de.data.droppedDocuments.length) { - let [xp, yp] = xf.transformPoint(de.x, de.y); - let x = xp - de.data.xOffset; - let y = yp - de.data.yOffset; + let z = NumCast(de.data.draggedDocuments[0].z); + let x = (z ? xpo : xp) - de.data.xOffset; + let y = (z ? ypo : yp) - de.data.yOffset; let dropX = NumCast(de.data.droppedDocuments[0].x); let dropY = NumCast(de.data.droppedDocuments[0].y); de.data.droppedDocuments.forEach(d => { @@ -152,12 +310,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this.bringToFront(d); }); + + this.updateClusters(); } } else if (de.data instanceof DragManager.AnnotationDragData) { if (de.data.dropDocument) { let dragDoc = de.data.dropDocument; - let [xp, yp] = this.getTransform().transformPoint(de.x, de.y); let x = xp - de.data.xOffset; let y = yp - de.data.yOffset; let dropX = NumCast(de.data.dropDocument.x); @@ -173,6 +332,87 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return false; } + tryDragCluster(e: PointerEvent) { + let probe = this.getTransform().transformPoint(e.clientX, e.clientY); + let cluster = this.childDocs.reduce((cluster, cd) => { + let cx = NumCast(cd.x) - this._clusterDistance; + let cy = NumCast(cd.y) - this._clusterDistance; + let cw = NumCast(cd.width) + 2 * this._clusterDistance; + let ch = NumCast(cd.height) + 2 * this._clusterDistance; + if (!cd.z && this.intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 })) { + return NumCast(cd.cluster); + } + return cluster; + }, -1); + if (cluster !== -1) { + let eles = this.childDocs.filter(cd => NumCast(cd.cluster) === cluster); + this.selectDocuments(eles); + let clusterDocs = SelectionManager.SelectedDocuments(); + SelectionManager.DeselectAll(); + 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); + const [xoff, yoff] = this.getTransform().transformDirection(e.x - left, e.y - top); + de.dropAction = e.ctrlKey || e.altKey ? "alias" : undefined; + de.xOffset = xoff; + de.yOffset = yoff; + DragManager.StartDocumentDrag(clusterDocs.map(v => v.ContentDiv!), de, e.clientX, e.clientY, { + handlers: { dragComplete: action(emptyFunction) }, + hideSource: !de.dropAction + }); + return true; + } + + return false; + } + @observable sets: (Doc[])[] = []; + @action + updateClusters() { + this.sets.length = 0; + this.childDocs.map(c => { + let included = []; + for (let i = 0; i < this.sets.length; i++) { + for (let member of this.sets[i]) { + if (this.boundsOverlap(c, member)) { + included.push(i); + break; + } + } + } + if (included.length === 0) { + this.sets.push([c]); + } else if (included.length === 1) { + this.sets[included[0]].push(c); + } else { + this.sets[included[0]].push(c); + for (let s = 1; s < included.length; s++) { + this.sets[included[0]].push(...this.sets[included[s]]); + this.sets[included[s]].length = 0; + } + } + }); + this.sets.map((set, i) => set.map(member => member.cluster = i)); + } + + getClusterColor = (doc: Doc) => { + if (this.props.Document.useClusters) { + let cluster = NumCast(doc.cluster); + if (this.sets.length <= cluster) { + setTimeout(() => this.updateClusters(), 0); + return; + } + let set = this.sets.length > cluster ? this.sets[cluster] : undefined; + let colors = ["#da42429e", "#31ea318c", "#8c4000", "#4a7ae2c4", "#d809ff", "#ff7601", "#1dffff", "yellow", "#1b8231f2", "#000000ad"]; + let clusterColor = colors[cluster % colors.length]; + set && set.filter(s => !s.isBackground).map(s => + s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); + set && set.filter(s => s.isBackground).map(s => + s.backgroundColor && s.backgroundColor !== s.defaultBackgroundColor && (clusterColor = StrCast(s.backgroundColor))); + return clusterColor; + } + return ""; + } + @action onPointerDown = (e: React.PointerEvent): void => { if (e.button === 0 && !e.shiftKey && !e.altKey && (!this.isAnnotationOverlay || this.zoomScaling() !== 1) && this.props.active()) { @@ -193,6 +433,13 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerMove = (e: PointerEvent): void => { if (!e.cancelBubble) { + if (this.props.Document.useClusters && this.tryDragCluster(e)) { + e.stopPropagation(); // doesn't actually stop propagation since all our listeners are listening to events on 'document' however it does mark the event as cancelBubble=true which we test for in the move event handlers + e.preventDefault(); + document.removeEventListener("pointermove", this.onPointerMove); + document.removeEventListener("pointerup", this.onPointerUp); + return; + } let x = this.Document.panX || 0; let y = this.Document.panY || 0; let docs = this.childDocs || []; @@ -224,10 +471,10 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this._pheight / this.zoomScaling()); let panelwidth = panelDim[0]; let panelheight = panelDim[1]; - // if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2; - // if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2; - // if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2; - // if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2; + if (ranges[0][0] - dx > (this.panX() + panelwidth / 2)) x = ranges[0][1] + panelwidth / 2; + if (ranges[0][1] - dx < (this.panX() - panelwidth / 2)) x = ranges[0][0] - panelwidth / 2; + if (ranges[1][0] - dy > (this.panY() + panelheight / 2)) y = ranges[1][1] + panelheight / 2; + if (ranges[1][1] - dy < (this.panY() - panelheight / 2)) y = ranges[1][0] - panelheight / 2; } this.setPan(x - dx, y - dy); this._lastX = e.pageX; @@ -240,12 +487,11 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { @action onPointerWheel = (e: React.WheelEvent): void => { if (BoolCast(this.props.Document.lockedPosition)) return; - // if (!this.props.active()) { - // return; - // } - if (this.props.Document.type === "pdf") { + if (!e.ctrlKey && this.props.Document.scrollHeight !== undefined) { // things that can scroll vertically should do that instead of zooming + e.stopPropagation(); return; } + let childSelected = this.childDocs.some(doc => { var dv = DocumentManager.Instance.getDocumentView(doc); return dv && SelectionManager.IsSelected(dv) ? true : false; @@ -254,21 +500,20 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { return; } e.stopPropagation(); - const coefficient = 1000; - - if (e.ctrlKey) { - let deltaScale = (1 - (e.deltaY / coefficient)); - let nw = this.nativeWidth * deltaScale; - let nh = this.nativeHeight * deltaScale; - if (nw && nh) { - this.props.Document.nativeWidth = nw; - this.props.Document.nativeHeight = nh; - } - e.stopPropagation(); - e.preventDefault(); - } else { - // if (modes[e.deltaMode] === 'pixels') coefficient = 50; - // else if (modes[e.deltaMode] === 'lines') coefficient = 1000; // This should correspond to line-height?? + + // bcz: this changes the nativewidth/height, but ImageBox will just revert it back to its defaults. need more logic to fix. + // if (e.ctrlKey && this.props.Document.scrollHeight === undefined) { + // let deltaScale = (1 - (e.deltaY / coefficient)); + // let nw = this.nativeWidth * deltaScale; + // let nh = this.nativeHeight * deltaScale; + // if (nw && nh) { + // this.props.Document.nativeWidth = nw; + // this.props.Document.nativeHeight = nh; + // } + // e.preventDefault(); + // } + // else + { let deltaScale = e.deltaY > 0 ? (1 / 1.1) : 1.1; if (deltaScale * this.zoomScaling() < 1 && this.isAnnotationOverlay) { deltaScale = 1 / this.zoomScaling(); @@ -280,23 +525,19 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let safeScale = Math.min(Math.max(0.15, localTransform.Scale), 40); this.props.Document.scale = Math.abs(safeScale); this.setPan(-localTransform.TranslateX / safeScale, -localTransform.TranslateY / safeScale); - e.stopPropagation(); + e.preventDefault(); } } @action setPan(panX: number, panY: number) { - if (BoolCast(this.props.Document.lockedPosition)) return; - this.props.Document.panTransformType = "None"; - var scale = this.getLocalTransform().inverse().Scale; - const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); - const newPanY = Math.min((1 - 1 / scale) * this.nativeHeight, Math.max(0, panY)); - this.props.Document.panX = this.isAnnotationOverlay ? newPanX : panX; - this.props.Document.panY = this.isAnnotationOverlay && StrCast(this.props.Document.backgroundLayout).indexOf("PDFBox") === -1 ? newPanY : panY; - // this.props.Document.panX = panX; - // this.props.Document.panY = panY; - if (this.props.Document.scrollY) { - this.props.Document.scrollY = panY - scale * this.props.Document[HeightSym](); + if (!BoolCast(this.props.Document.lockedPosition)) { + this.props.Document.panTransformType = "None"; + var scale = this.getLocalTransform().inverse().Scale; + const newPanX = Math.min((1 - 1 / scale) * this.nativeWidth, Math.max(0, panX)); + const newPanY = Math.min((this.props.Document.scrollHeight !== undefined ? NumCast(this.props.Document.scrollHeight) : (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; } } @@ -310,7 +551,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } bringToFront = (doc: Doc, sendToBack?: boolean) => { - if (sendToBack) { + if (sendToBack || doc.isBackground) { doc.zIndex = 0; return; } @@ -380,16 +621,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { this.Document.scale = scale; } - getScale = () => { - if (this.Document.scale) { - return this.Document.scale; - } - return 1; - } - + getScale = () => this.Document.scale ? this.Document.scale : 1; getChildDocumentViewProps(childDocLayout: Doc): DocumentViewProps { - let self = this; let pair = Doc.GetLayoutDataDocPair(this.props.Document, this.props.DataDoc, this.props.fieldKey, childDocLayout); return { DataDoc: pair.data, @@ -397,7 +631,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { addDocument: this.props.addDocument, removeDocument: this.props.removeDocument, moveDocument: this.props.moveDocument, - ScreenToLocalTransform: this.getTransform, + ScreenToLocalTransform: pair.layout.z ? this.getTransformOverlay : this.getTransform, renderDepth: this.props.renderDepth + 1, selectOnLoad: pair.layout[Id] === this._selectOnLoaded, PanelWidth: pair.layout[WidthSym], @@ -405,6 +639,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, + backgroundColor: this.getClusterColor, parentActive: this.props.active, whenActiveChanged: this.props.whenActiveChanged, bringToFront: this.bringToFront, @@ -428,6 +663,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ContentScaling: returnOne, ContainingCollectionView: this.props.CollectionView, focus: this.focusDocument, + backgroundColor: returnEmptyString, parentActive: this.props.active, whenActiveChanged: this.props.whenActiveChanged, bringToFront: this.bringToFront, @@ -437,19 +673,33 @@ 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 } { + getCalculatedPositions(script: ScriptField, params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): { x?: number, y?: number, z?: number, width?: number, height?: number, state?: any } { const result = script.script.run(params); if (!result.success) { return {}; } - return result.result === undefined ? {} : result.result; + let doc = params.doc; + return result.result === undefined ? { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") } : result.result; + } + + viewDefsToJSX = (views: any[]) => { + let elements: ViewDefResult[] = []; + if (Array.isArray(views)) { + elements = views.reduce<typeof elements>((prev, ele) => { + const jsx = this.viewDefToJSX(ele); + jsx && prev.push(jsx); + return prev; + }, elements); + } + return elements; } - private viewDefToJSX(viewDef: any): { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } } | undefined { + private viewDefToJSX(viewDef: any): Opt<ViewDefResult> { if (viewDef.type === "text") { const text = Cast(viewDef.text, "string"); const x = Cast(viewDef.x, "number"); const y = Cast(viewDef.y, "number"); + const z = Cast(viewDef.z, "number"); const width = Cast(viewDef.width, "number"); const height = Cast(viewDef.height, "number"); const fontSize = Cast(viewDef.fontSize, "number"); @@ -461,7 +711,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { ele: <div className="collectionFreeform-customText" style={{ transform: `translate(${x}px, ${y}px)`, width, height, fontSize - }}>{text}</div>, bounds: { x: x!, y: y!, width: width!, height: height! } + }}>{text}</div>, bounds: { x: x!, y: y!, z: z, width: width!, height: height! } }; } } @@ -473,20 +723,14 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { const script = this.Document.arrangeScript; let state: any = undefined; const docs = this.childDocs; - let elements: { ele: JSX.Element, bounds?: { x: number, y: number, width: number, height: number } }[] = []; + let elements: ViewDefResult[] = []; if (initScript) { const initResult = initScript.script.run({ docs, collection: this.Document }); if (initResult.success) { const result = initResult.result; const { state: scriptState, views } = result; state = scriptState; - if (Array.isArray(views)) { - elements = views.reduce<typeof elements>((prev, ele) => { - const jsx = this.viewDefToJSX(ele); - jsx && prev.push(jsx); - return prev; - }, elements); - } + elements = this.viewDefsToJSX(views); } } let docviews = docs.filter(doc => doc instanceof Doc).reduce((prev, doc) => { @@ -495,78 +739,118 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { let minim = BoolCast(doc.isMinimized); if (minim === undefined || !minim) { const pos = script ? this.getCalculatedPositions(script, { doc, index: prev.length, collection: this.Document, docs, state }) : - { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") }; + { x: Cast(doc.x, "number"), y: Cast(doc.y, "number"), z: Cast(doc.z, "number"), width: Cast(doc.width, "number"), height: Cast(doc.height, "number") }; state = pos.state === undefined ? state : pos.state; prev.push({ ele: <CollectionFreeFormDocumentView key={doc[Id]} x={script ? pos.x : undefined} y={script ? pos.y : undefined} width={script ? pos.width : undefined} height={script ? pos.height : undefined} {...this.getChildDocumentViewProps(doc)} />, - bounds: (pos.x !== undefined && pos.y !== undefined && pos.width !== undefined && pos.height !== undefined) ? { x: pos.x, y: pos.y, width: pos.width, height: pos.height } : undefined + bounds: (pos.x !== undefined && pos.y !== undefined) ? { x: pos.x, y: pos.y, z: pos.z, width: NumCast(pos.width), height: NumCast(pos.height) } : undefined }); } } return prev; }, elements); - setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way .... + this.resetSelectOnLoaded(); return docviews; } + resetSelectOnLoaded = () => setTimeout(() => this._selectOnLoaded = "", 600);// bcz: surely there must be a better way .... + @computed.struct get views() { - return this.elements.map(ele => ele.ele); + let source = this.Document.usePivotLayout === true ? PivotView.elements(this) : this.elements; + return source.filter(ele => ele.bounds && !ele.bounds.z).map(ele => ele.ele); + } + @computed.struct + get overlayViews() { + return this.elements.filter(ele => ele.bounds && ele.bounds.z).map(ele => ele.ele); } + @action onCursorMove = (e: React.PointerEvent) => { super.setCursorPosition(this.getTransform().transformPoint(e.clientX, e.clientY)); } + fitToContainer = async () => this.props.Document.fitToBox = !this.fitToBox; + + arrangeContents = async () => { + const docs = await DocListCastAsync(this.Document[this.props.fieldKey]); + UndoManager.RunInBatch(() => { + if (docs) { + let startX = this.Document.panX || 0; + let x = startX; + let y = this.Document.panY || 0; + let i = 0; + const width = Math.max(...docs.map(doc => NumCast(doc.width))); + const height = Math.max(...docs.map(doc => NumCast(doc.height))); + for (const doc of docs) { + doc.x = x; + doc.y = y; + x += width + 20; + if (++i === 6) { + i = 0; + x = startX; + y += height + 20; + } + } + } + }, "arrange contents"); + } + + analyzeStrokes = async () => { + let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField); + if (!data) { + return; + } + let relevantKeys = ["inkAnalysis", "handwriting"]; + CognitiveServices.Inking.Appliers.ConcatenateHandwriting(this.fieldExtensionDoc, relevantKeys, data.inkData); + } + onContextMenu = (e: React.MouseEvent) => { let layoutItems: ContextMenuProps[] = []; layoutItems.push({ description: `${this.fitToBox ? "Unset" : "Set"} Fit To Container`, - event: async () => this.props.Document.fitToBox = !this.fitToBox, + event: this.fitToContainer, icon: !this.fitToBox ? "expand-arrows-alt" : "compress-arrows-alt" }); layoutItems.push({ - description: "Arrange contents in grid", - icon: "table", + description: "reset view", event: () => { + this.props.Document.panX = this.props.Document.panY = 0; + this.props.Document.scale = 1; + }, icon: "compress-arrows-alt" + }); + layoutItems.push({ + description: `${this.props.Document.useClusters ? "Uncluster" : "Use Clusters"}`, event: async () => { - const docs = await DocListCastAsync(this.Document[this.props.fieldKey]); - UndoManager.RunInBatch(() => { - if (docs) { - let startX = this.Document.panX || 0; - let x = startX; - let y = this.Document.panY || 0; - let i = 0; - const width = Math.max(...docs.map(doc => NumCast(doc.width))); - const height = Math.max(...docs.map(doc => NumCast(doc.height))); - for (const doc of docs) { - doc.x = x; - doc.y = y; - x += width + 20; - if (++i === 6) { - i = 0; - x = startX; - y += height + 20; - } - } - } - }, "arrange contents"); - } + Docs.Prototypes.get(DocumentType.TEXT).defaultBackgroundColor = "#f1efeb"; // backward compatibility with databases that didn't have a default background color on prototypes + Docs.Prototypes.get(DocumentType.COL).defaultBackgroundColor = "white"; + this.props.Document.useClusters = !this.props.Document.useClusters; + }, + icon: !this.props.Document.useClusters ? "braille" : "braille" + }); + layoutItems.push({ + description: `${this.props.Document.clusterOverridesDefaultBackground ? "Use Default Backgrounds" : "Clusters Override Defaults"}`, + event: async () => this.props.Document.clusterOverridesDefaultBackground = !this.props.Document.clusterOverridesDefaultBackground, + 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" }); ContextMenu.Instance.addItem({ - description: "Analyze Strokes", event: async () => { - let data = Cast(this.fieldExtensionDoc[this.inkKey], InkField); - if (!data) { - return; - } - let relevantKeys = ["inkAnalysis", "handwriting"]; - CognitiveServices.Inking.Manager.analyzer(this.fieldExtensionDoc, relevantKeys, data.inkData); - }, icon: "paint-brush" + description: "Layout...", + subitems: layoutItems, + icon: "compass" + }); + ContextMenu.Instance.addItem({ + description: "Analyze Strokes", + event: this.analyzeStrokes, + icon: "paint-brush" }); ContextMenu.Instance.addItem({ description: "Import document", icon: "upload", event: () => { @@ -604,6 +888,9 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { <CollectionFreeFormBackgroundView key="backgroundView" {...this.props} {...this.getDocumentViewProps(this.props.Document)} />, ...this.views ] + private overlayChildViews = () => { + return [...this.overlayViews]; + } public static AddCustomLayout(doc: Doc, dataKey: string): () => void { return () => { @@ -652,6 +939,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { <CollectionFreeFormRemoteCursors {...this.props} key="remoteCursors" /> </CollectionFreeFormViewPannableContents> </MarqueeView> + {this.overlayChildViews()} <CollectionFreeFormOverlayView {...this.props} {...this.getDocumentViewProps(this.props.Document)} /> </div> ); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b9ee588dd..aad26efa0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -226,15 +226,17 @@ export class MarqueeView extends React.Component<MarqueeViewProps> } get ink() { - let container = this.props.container.Document; + let container = this.props.container.props.Document; let containerKey = this.props.container.props.fieldKey; - return Cast(container[containerKey + "_ink"], InkField); + let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); + return Cast(extensionDoc.ink, InkField); } set ink(value: InkField | undefined) { - let container = Doc.GetProto(this.props.container.Document); + let container = Doc.GetProto(this.props.container.props.Document); let containerKey = this.props.container.props.fieldKey; - container[containerKey + "_ink"] = value; + let extensionDoc = Doc.resolvedFieldDataDoc(container, containerKey, "true"); + extensionDoc.ink = value; } @undoBatch @@ -247,7 +249,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> this._commandExecuted = true; e.stopPropagation(); (e as any).propagationIsStopped = true; - this.marqueeSelect().map(d => this.props.removeDocument(d)); + this.marqueeSelect(false).map(d => this.props.removeDocument(d)); if (this.ink) { this.marqueeInkDelete(this.ink.inkData); } @@ -261,7 +263,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> e.preventDefault(); (e as any).propagationIsStopped = true; let bounds = this.Bounds; - let selected = this.marqueeSelect(); + let selected = this.marqueeSelect(false); if (e.key === "c") { selected.map(d => { this.props.removeDocument(d); @@ -278,11 +280,13 @@ export class MarqueeView extends React.Component<MarqueeViewProps> panX: 0, panY: 0, backgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", + defaultBackgroundColor: this.props.container.isAnnotationOverlay ? undefined : "white", width: bounds.width, height: bounds.height, title: e.key === "s" || e.key === "S" ? "-summary-" : "a nested collection", }); - newCollection.data_ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; + let dataExtensionField = Doc.CreateDocumentExtensionForField(newCollection, "data"); + dataExtensionField.ink = inkData ? new InkField(this.marqueeInkSelect(inkData)) : undefined; this.marqueeInkDelete(inkData); if (e.key === "s") { @@ -366,7 +370,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> } } - marqueeSelect() { + marqueeSelect(selectBackgrounds: boolean = true) { let selRect = this.Bounds; let selection: Doc[] = []; this.props.activeDocuments().filter(doc => !doc.isBackground).map(doc => { @@ -378,7 +382,7 @@ export class MarqueeView extends React.Component<MarqueeViewProps> selection.push(doc); } }); - if (!selection.length) { + if (!selection.length && selectBackgrounds) { this.props.activeDocuments().map(doc => { var x = NumCast(doc.x); var y = NumCast(doc.y); |
