diff options
| author | ab <abdullah_ahmed@brown.edu> | 2019-08-12 12:24:51 -0400 |
|---|---|---|
| committer | ab <abdullah_ahmed@brown.edu> | 2019-08-12 12:24:51 -0400 |
| commit | 73ac98d53b80230bb085d71b61254f6c24a3e397 (patch) | |
| tree | 43b13e482c29ce6a3db1108aca056581495303d7 /src/client/views/collections | |
| parent | b8ab5a823ae22b021c09dfd713b77211a51b3eae (diff) | |
| parent | 3aea955ae56c1aa0611de09c4a013e9dc5c86c42 (diff) | |
merged
Diffstat (limited to 'src/client/views/collections')
14 files changed, 599 insertions, 290 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..897796174 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: boolean }[] { 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: boolean }[]); } @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..b87be5d68 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,41 +45,38 @@ 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; } componentDidMount() { // is there any reason this needs to exist? -syip - this._heightDisposer = reaction(() => [this.yMargin, this.props.Document[WidthSym](), this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], + this._heightDisposer = reaction(() => [this.props.Document.autoHeight, this.yMargin, this.props.Document[WidthSym](), this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], () => { if (this.singleColumn && BoolCast(this.props.Document.autoHeight)) { let hgt = this.Sections.size * 50 + this.filteredChildren.reduce((height, d, i) => { @@ -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 7f5d78313..24bd24d11 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -70,8 +70,9 @@ class TreeView extends React.Component<TreeViewProps> { private _header?: React.RefObject<HTMLDivElement> = React.createRef(); private _treedropDisposer?: DragManager.DragDropDisposer; private _dref = React.createRef<HTMLDivElement>(); + get defaultExpandedView() { return this.childDocs ? this.fieldKey : "fields"; } @observable _collapsed: boolean = true; - @computed get treeViewExpandedView() { return StrCast(this.props.document.treeViewExpandedView, "fields"); } + @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() { @@ -79,11 +80,17 @@ class TreeView extends React.Component<TreeViewProps> { return splits.length > 1 ? splits[1].split("\"")[0] : "data"; } @computed get childDocs() { - let layout = this.props.document.layout as Doc; + 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 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 @@ -120,19 +127,19 @@ class TreeView extends React.Component<TreeViewProps> { 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); @@ -276,13 +283,15 @@ class TreeView extends React.Component<TreeViewProps> { noOverlays = (doc: Doc) => ({ title: "", caption: "" }); @computed get renderContent() { - if (this.treeViewExpandedView === this.fieldKey) { - 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); - return <ul key={this.fieldKey + "more"}> - {!this.childDocs ? (null) : - TreeView.GetChildElements(this.childDocs as Doc[], this.props.treeViewId, this.props.document.layout as Doc, - this.resolvedDataDoc, this.fieldKey, addDoc, remDoc, this.move, + 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 >; @@ -332,9 +341,12 @@ class TreeView extends React.Component<TreeViewProps> { let headerElements = ( <span className="collectionTreeView-keyHeader" key={this.treeViewExpandedView} onPointerDown={action(() => { - this.props.document.treeViewExpandedView = this.treeViewExpandedView === this.fieldKey ? "fields" : - this.treeViewExpandedView === "fields" && this.props.document.layout ? "layout" : - this.childDocs ? this.fieldKey : "fields"; + 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} @@ -347,7 +359,7 @@ class TreeView extends React.Component<TreeViewProps> { return <> <div className="docContainer" id={`docContainer-${this.props.parentKey}`} ref={reference} onPointerDown={onItemDown} style={{ - background: BoolCast(this.props.document.libraryBrush) ? "#06121212" : "0", + 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" }} > 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 0beb0086b..e9791df4e 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, faBrain } 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,8 +29,8 @@ 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"; @@ -41,7 +41,11 @@ import { ClientRecommender } from "../../../ClientRecommender"; import { SearchUtil } from "../../../util/SearchUtil"; import { SearchBox } from "../../SearchBox"; -library.add(faEye, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBrain); +import { DocumentType, Docs } from "../../../documents/Documents"; +import { RouteStore } from "../../../../server/RouteStore"; +import { string, number, elementType } from "prop-types"; + +library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard); export const panZoomSchema = createSchema({ panX: "number", @@ -51,6 +55,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); @@ -70,7 +207,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) @@ -80,7 +217,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, @@ -101,6 +238,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) => { @@ -110,6 +248,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[]) => { @@ -129,17 +268,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 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 => { @@ -155,6 +315,8 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } this.bringToFront(d); }); + + this.updateClusters(); } } else if (de.data instanceof DragManager.AnnotationDragData) { @@ -175,6 +337,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()) { @@ -195,6 +438,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 || []; @@ -242,12 +492,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; @@ -256,21 +505,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(); @@ -282,23 +530,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; } } @@ -312,7 +556,7 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } bringToFront = (doc: Doc, sendToBack?: boolean) => { - if (sendToBack) { + if (sendToBack || doc.isBackground) { doc.zIndex = 0; return; } @@ -382,16 +626,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, @@ -399,7 +636,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], @@ -407,6 +644,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, @@ -430,6 +668,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, @@ -439,19 +678,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"); @@ -463,7 +716,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! } }; } } @@ -475,20 +728,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) => { @@ -497,28 +744,36 @@ 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) => { @@ -526,50 +781,82 @@ export class CollectionFreeFormView extends CollectionSubView(PanZoomDocument) { } @action + 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: () => { @@ -624,6 +911,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 () => { @@ -672,6 +962,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); |
