diff options
| author | anika-ahluwalia <anika.ahluwalia@gmail.com> | 2020-04-29 17:21:06 -0500 |
|---|---|---|
| committer | anika-ahluwalia <anika.ahluwalia@gmail.com> | 2020-04-29 17:21:06 -0500 |
| commit | a3d0d5cb8d00eb6c360c95e0c5c03e37b218e49a (patch) | |
| tree | 734f941feef0c87e2c15cb0c323334de29cafcaf /src/client/views/collections | |
| parent | 7b8651a1a1f824e6c6a5135a4420766686f35175 (diff) | |
| parent | d66aaffc27405f4231a29cd6edda3477077ae946 (diff) | |
Merge branch 'master' of https://github.com/browngraphicslab/Dash-Web into script_documents
Diffstat (limited to 'src/client/views/collections')
22 files changed, 500 insertions, 322 deletions
diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index 9e7248db2..eda8e5684 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -11,7 +11,7 @@ import "./CollectionCarouselView.scss"; import { CollectionSubView } from './CollectionSubView'; import { faCaretLeft, faCaretRight } from '@fortawesome/free-solid-svg-icons'; import { Doc } from '../../../new_fields/Doc'; -import { FormattedTextBox } from '../nodes/FormattedTextBox'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { ContextMenu } from '../ContextMenu'; import { ObjectField } from '../../../new_fields/ObjectField'; @@ -27,7 +27,7 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this._dropDisposer?.(); if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this)); + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); } } @@ -47,13 +47,22 @@ export class CollectionCarouselView extends CollectionSubView(CarouselDocument) <> <div className="collectionCarouselView-image" key="image"> <ContentFittingDocumentView {...this.props} + renderDepth={this.props.renderDepth + 1} Document={this.childLayoutPairs[index].layout} DataDocument={this.childLayoutPairs[index].data} PanelHeight={this.panelHeight} getTransform={this.props.ScreenToLocalTransform} /> </div> - <div className="collectionCarouselView-caption" key="caption" style={{ background: this.props.backgroundColor?.(this.props.Document) }}> - <FormattedTextBox key={index} {...this.props} Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox> + <div className="collectionCarouselView-caption" key="caption" + style={{ + background: StrCast(this.layoutDoc._captionBackgroundColor, this.props.backgroundColor?.(this.props.Document)), + color: StrCast(this.layoutDoc._captionColor, StrCast(this.layoutDoc.color)), + borderRadius: StrCast(this.layoutDoc._captionBorderRounding), + }}> + <FormattedTextBox key={index} {...this.props} + xMargin={NumCast(this.layoutDoc["caption-xMargin"])} + yMargin={NumCast(this.layoutDoc["caption-yMargin"])} + Document={this.childLayoutPairs[index].layout} DataDoc={undefined} fieldKey={"caption"}></FormattedTextBox> </div> </>; } diff --git a/src/client/views/collections/CollectionLinearView.tsx b/src/client/views/collections/CollectionLinearView.tsx index cb0206260..344dca23a 100644 --- a/src/client/views/collections/CollectionLinearView.tsx +++ b/src/client/views/collections/CollectionLinearView.tsx @@ -64,7 +64,7 @@ export class CollectionLinearView extends CollectionSubView(LinearDocument) { protected createDashEventsTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this._dropDisposer && this._dropDisposer(); if (ele) { - this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this)); + this._dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); } } diff --git a/src/client/views/collections/CollectionMapView.tsx b/src/client/views/collections/CollectionMapView.tsx index 7b7828d7d..971224482 100644 --- a/src/client/views/collections/CollectionMapView.tsx +++ b/src/client/views/collections/CollectionMapView.tsx @@ -1,4 +1,4 @@ -import { GoogleApiWrapper, Map as GeoMap, MapProps, Marker } from "google-maps-react"; +import { GoogleApiWrapper, Map as GeoMap, IMapProps, Marker } from "google-maps-react"; import { observer } from "mobx-react"; import { Doc, Opt, DocListCast, FieldResult, Field } from "../../../new_fields/Doc"; import { documentSchema } from "../../../new_fields/documentSchemas"; @@ -42,7 +42,7 @@ const query = async (data: string | google.maps.LatLngLiteral) => { }; @observer -class CollectionMapView extends CollectionSubView<MapSchema, Partial<MapProps> & { google: any }>(MapSchema) { +class CollectionMapView extends CollectionSubView<MapSchema, Partial<IMapProps> & { google: any }>(MapSchema) { private _cancelAddrReq = new Map<string, boolean>(); private _cancelLocReq = new Map<string, boolean>(); @@ -221,7 +221,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<MapProps> & zoom={center.zoom || 10} initialCenter={center} center={center} - onIdle={(_props?: MapProps, map?: google.maps.Map) => { + onIdle={(_props?: IMapProps, map?: google.maps.Map) => { if (this.layoutDoc.lockedTransform) { // reset zoom (ideally, we could probably can tell the map to disallow zooming somehow instead) map?.setZoom(center?.zoom || 10); @@ -233,7 +233,7 @@ class CollectionMapView extends CollectionSubView<MapSchema, Partial<MapProps> & }))(); } }} - onDragend={(_props?: MapProps, map?: google.maps.Map) => { + onDragend={(_props?: IMapProps, map?: google.maps.Map) => { if (this.layoutDoc.lockedTransform) { // reset the drag (ideally, we could probably can tell the map to disallow dragging somehow instead) map?.setCenter({ lat: center?.lat!, lng: center?.lng! }); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index b272151c1..7ad15ef41 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -2,14 +2,13 @@ import React = require("react"); import { library } from '@fortawesome/fontawesome-svg-core'; import { faPalette } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; -import { action, computed, observable } from "mobx"; +import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; -import Measure from "react-measure"; import { Doc } from "../../../new_fields/Doc"; import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; import { ScriptField } from "../../../new_fields/ScriptField"; import { StrCast, NumCast } from "../../../new_fields/Types"; -import { numberRange } from "../../../Utils"; +import { numberRange, setupMoveUpEvents, emptyFunction } from "../../../Utils"; import { Docs } from "../../documents/Documents"; import { DragManager } from "../../util/DragManager"; import { CompileScript } from "../../util/Scripting"; @@ -45,39 +44,44 @@ interface CMVFieldRowProps { export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowProps> { @observable private _background = "inherit"; @observable private _createAliasSelected: boolean = false; - @observable private _collapsed: boolean = false; - @observable private _headingsHack: number = 1; - @observable private _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; - @observable private _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + @observable private heading: string = ""; + @observable private color: string = "#f1efeb"; + @observable private collapsed: boolean = false; + private set _heading(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.heading = this.heading = value)); } + private set _color(value: string) { runInAction(() => this.props.headingObject && (this.props.headingObject.color = this.color = value)); } + private set _collapsed(value: boolean) { runInAction(() => this.props.headingObject && (this.props.headingObject.collapsed = this.collapsed = value)); } private _dropDisposer?: DragManager.DragDropDisposer; private _headerRef: React.RefObject<HTMLDivElement> = React.createRef(); - private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 }; private _contRef: React.RefObject<HTMLDivElement> = React.createRef(); - private _sensitivity: number = 16; - private _counter: number = 0; private _ele: any; createRowDropRef = (ele: HTMLDivElement | null) => { - this._dropDisposer && this._dropDisposer(); + this._dropDisposer?.(); if (ele) { this._ele = ele; this.props.observeHeight(ele); this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this)); } } + @action + componentDidMount() { + this.heading = this.props.headingObject?.heading || ""; + this.color = this.props.headingObject?.color || "#f1efeb"; + this.collapsed = this.props.headingObject?.collapsed || false; + } componentWillUnmount() { this.props.unobserveHeight(this._ele); } getTrueHeight = () => { - if (this._collapsed) { - this.props.setDocHeight(this._heading, 20); + if (this.collapsed) { + this.props.setDocHeight(this.heading, 20); } else { const rawHeight = this._contRef.current!.getBoundingClientRect().height + 15; //+ 15 accounts for the group header const transformScale = this.props.screenToLocalTransform().Scale; const trueHeight = rawHeight * transformScale; - this.props.setDocHeight(this._heading, trueHeight); + this.props.setDocHeight(this.heading, trueHeight); } } @@ -89,7 +93,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr (this.props.parent.Document.dropConverter instanceof ScriptField) && this.props.parent.Document.dropConverter.script.run({ dragData: de.complete.docDragData }); const key = StrCast(this.props.parent.props.Document._pivotField); - const castedValue = this.getValue(this._heading); + const castedValue = this.getValue(this.heading); de.complete.docDragData.droppedDocuments.forEach(d => d[key] = castedValue); this.props.parent.onInternalDrop(e, de); e.stopPropagation(); @@ -116,10 +120,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr } } this.props.docList.forEach(d => d[key] = castedValue); - if (this.props.headingObject) { - this.props.headingObject.setHeading(castedValue.toString()); - this._heading = this.props.headingObject.heading; - } + this._heading = castedValue.toString(); return true; } return false; @@ -128,10 +129,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr @action changeColumnColor = (color: string) => { this._createAliasSelected = false; - if (this.props.headingObject) { - this.props.headingObject.setColor(color); - this._color = color; - } + this._color = color; } pointerEnteredRow = action(() => SelectionManager.GetIsDragging() && (this._background = "#b4b4b4")); @@ -140,7 +138,6 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr pointerLeaveRow = () => { this._createAliasSelected = false; this._background = "inherit"; - document.removeEventListener("pointermove", this.startDrag); } @action @@ -164,62 +161,36 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr })); @action - collapseSection = () => { + collapseSection = (e: any) => { this._createAliasSelected = false; - if (this.props.headingObject) { - this._headingsHack++; - this.props.headingObject.setCollapsed(!this.props.headingObject.collapsed); - this.toggleVisibility(); - } + this.toggleVisibility(); + e.stopPropagation(); } - startDrag = (e: PointerEvent) => { - const [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX - this._startDragPosition.x, e.clientY - this._startDragPosition.y); - if (Math.abs(dx) + Math.abs(dy) > this._sensitivity) { - const alias = Doc.MakeAlias(this.props.parent.props.Document); - const key = StrCast(this.props.parent.props.Document._pivotField); - let value = this.getValue(this._heading); - value = typeof value === "string" ? `"${value}"` : value; - const script = `return doc.${key} === ${value}`; - const compiled = CompileScript(script, { params: { doc: Doc.name } }); - if (compiled.compiled) { - alias.viewSpecScript = new ScriptField(compiled); - DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY); - } - - e.stopPropagation(); - document.removeEventListener("pointermove", this.startDrag); - document.removeEventListener("pointerup", this.pointerUp); + headerMove = (e: PointerEvent) => { + const alias = Doc.MakeAlias(this.props.parent.props.Document); + const key = StrCast(this.props.parent.props.Document._pivotField); + let value = this.getValue(this.heading); + value = typeof value === "string" ? `"${value}"` : value; + const script = `return doc.${key} === ${value}`; + const compiled = CompileScript(script, { params: { doc: Doc.name } }); + if (compiled.compiled) { + alias.viewSpecScript = new ScriptField(compiled); + DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([alias]), e.clientX, e.clientY); } - } - - pointerUp = (e: PointerEvent) => { - e.stopPropagation(); - e.preventDefault(); - - document.removeEventListener("pointermove", this.startDrag); - document.removeEventListener("pointerup", this.pointerUp); + return true; } @action headerDown = (e: React.PointerEvent<HTMLDivElement>) => { - e.stopPropagation(); - e.preventDefault(); - - const [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX, e.clientY); - this._startDragPosition = { x: dx, y: dy }; - - if (this._createAliasSelected) { - document.removeEventListener("pointermove", this.startDrag); - document.addEventListener("pointermove", this.startDrag); - document.removeEventListener("pointerup", this.pointerUp); - document.addEventListener("pointerup", this.pointerUp); + if (e.button === 0 && !e.ctrlKey) { + setupMoveUpEvents(this, e, this.headerMove, emptyFunction, () => (this.props.parent.props.Document._chromeStatus === "disabled") && this.collapseSection(e)); + this._createAliasSelected = false; } - this._createAliasSelected = false; } renderColorPicker = () => { - const selected = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + const selected = this.color; const pink = PastelSchemaPalette.get("pink2"); const purple = PastelSchemaPalette.get("purple4"); @@ -249,7 +220,7 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr } toggleAlias = action(() => this._createAliasSelected = true); - toggleVisibility = action(() => this._collapsed = !this._collapsed); + toggleVisibility = () => this._collapsed = !this.collapsed; renderMenu = () => { const selected = this._createAliasSelected; @@ -261,27 +232,19 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr </div>); } - handleResize = (size: any) => { - if (++this._counter !== 1) { - this.getTrueHeight(); - } - } - @computed get contentLayout() { const rows = Math.max(1, Math.min(this.props.docList.length, Math.floor((this.props.parent.props.PanelWidth() - 2 * this.props.parent.xMargin) / (this.props.parent.columnWidth + this.props.parent.gridGap)))); const style = this.props.parent; - const collapsed = this._collapsed; const chromeStatus = this.props.parent.props.Document._chromeStatus; const newEditableViewProps = { GetValue: () => "", SetValue: this.addDocument, contents: "+ NEW", HeadingObject: this.props.headingObject, - HeadingsHack: this._headingsHack, toggle: this.toggleVisibility, - color: this._color + color: this.color }; - return collapsed ? (null) : + return this.collapsed ? (null) : <div style={{ position: "relative" }}> {(chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? <div className="collectionStackingView-addDocumentButton" @@ -307,18 +270,17 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr } @computed get headingView() { - const heading = this._heading; + const noChrome = this.props.parent.props.Document._chromeStatus === "disabled"; const key = StrCast(this.props.parent.props.Document._pivotField); - const evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; + const evContents = this.heading ? this.heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; const headerEditableViewProps = { GetValue: () => evContents, SetValue: this.headingChanged, contents: evContents, oneLine: true, HeadingObject: this.props.headingObject, - HeadingsHack: this._headingsHack, toggle: this.toggleVisibility, - color: this._color + color: this.color }; return this.props.parent.props.Document.miniHeaders ? <div className="collectionStackingView-miniHeader"> @@ -329,9 +291,9 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr <div className="collectionStackingView-sectionHeader-subCont" onPointerDown={this.headerDown} title={evContents === `NO ${key.toUpperCase()} VALUE` ? `Documents that don't have a ${key} value will go here. This column cannot be removed.` : ""} - style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this._color : "lightgrey" }}> - <EditableView {...headerEditableViewProps} /> - {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : + style={{ background: evContents !== `NO ${key.toUpperCase()} VALUE` ? this.color : "lightgrey" }}> + {noChrome ? evContents : <EditableView {...headerEditableViewProps} />} + {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : <div className="collectionStackingView-sectionColor"> <Flyout anchorPoint={anchorPoints.CENTER_RIGHT} content={this.renderColorPicker()}> <button className="collectionStackingView-sectionColorButton"> @@ -340,10 +302,10 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr </ Flyout > </div> } - <button className="collectionStackingView-sectionDelete" onClick={this.collapseSection}> - <FontAwesomeIcon icon={this._collapsed ? "chevron-down" : "chevron-up"} size="lg" /> - </button> - {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : + {noChrome ? (null) : <button className="collectionStackingView-sectionDelete" onClick={noChrome ? undefined : this.collapseSection}> + <FontAwesomeIcon icon={this.collapsed ? "chevron-down" : "chevron-up"} size="lg" /> + </button>} + {noChrome || evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : <div className="collectionStackingView-sectionOptions"> <Flyout anchorPoint={anchorPoints.TOP_RIGHT} content={this.renderMenu()}> <button className="collectionStackingView-sectionOptionButton"> @@ -356,23 +318,15 @@ export class CollectionMasonryViewFieldRow extends React.Component<CMVFieldRowPr </div>; } render() { - const background = this._background; //to account for observables in Measure - const contentlayout = this.contentLayout; - const headingview = this.headingView; - return <Measure offset onResize={this.handleResize}> - {({ measureRef }) => { - return <div ref={measureRef}> - <div className="collectionStackingView-masonrySection" - style={{ width: this.props.parent.NodeWidth, background }} - ref={this.createRowDropRef} - onPointerEnter={this.pointerEnteredRow} - onPointerLeave={this.pointerLeaveRow} - > - {headingview} - {contentlayout} - </div > - </div>; - }} - </Measure>; + const background = this._background; + return <div className="collectionStackingView-masonrySection" + style={{ width: this.props.parent.NodeWidth, background }} + ref={this.createRowDropRef} + onPointerEnter={this.pointerEnteredRow} + onPointerLeave={this.pointerLeaveRow} + > + {this.headingView} + {this.contentLayout} + </div >; } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionPileView.scss b/src/client/views/collections/CollectionPileView.scss new file mode 100644 index 000000000..ac874b663 --- /dev/null +++ b/src/client/views/collections/CollectionPileView.scss @@ -0,0 +1,8 @@ +.collectionPileView { + display: flex; + flex-direction: row; + position: absolute; + height: 100%; + width: 100%; + overflow: visible; +} diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx new file mode 100644 index 000000000..3bbfcc4d7 --- /dev/null +++ b/src/client/views/collections/CollectionPileView.tsx @@ -0,0 +1,127 @@ +import { action, computed, observable, runInAction } from "mobx"; +import { observer } from "mobx-react"; +import { HeightSym, Opt, WidthSym } from "../../../new_fields/Doc"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { BoolCast, NumCast, StrCast } from "../../../new_fields/Types"; +import { ContextMenu } from "../ContextMenu"; +import { ContextMenuProps } from "../ContextMenuItem"; +import { CollectionFreeFormView } from "./collectionFreeForm/CollectionFreeFormView"; +import { CollectionSubView } from "./CollectionSubView"; +import "./CollectionPileView.scss"; +import React = require("react"); +import { setupMoveUpEvents, emptyFunction, returnFalse } from "../../../Utils"; +import { SelectionManager } from "../../util/SelectionManager"; +import { UndoManager } from "../../util/UndoManager"; + +@observer +export class CollectionPileView extends CollectionSubView(doc => doc) { + _lastTap = 0; + _doubleTap: boolean | undefined = false; + _originalChrome: string = ""; + @observable _contentsActive = true; + @observable _layoutEngine = "pass"; + @observable _collapsed: boolean = false; + @observable _childClickedScript: Opt<ScriptField>; + componentDidMount() { + this._originalChrome = StrCast(this.layoutDoc._chromeStatus); + this.layoutDoc._chromeStatus = "disabled"; + this.layoutDoc.hideFilterView = true; + } + componentWillUnmount() { + this.layoutDoc.hideFilterView = false; + this.layoutDoc._chromeStatus = this._originalChrome; + } + + layoutEngine = () => this._layoutEngine; + + @computed get contents() { + return <div className="collectionPileView-innards" style={{ + width: "100%", + pointerEvents: this.layoutEngine() !== "pass" && (this.props.active() || this.layoutEngine() === "starburst") ? undefined : "none" + }} > + <CollectionFreeFormView {...this.props} layoutEngine={this.layoutEngine} /> + </div>; + } + + specificMenu = (e: React.MouseEvent) => { + const layoutItems: ContextMenuProps[] = []; + const doc = this.props.Document; + + ContextMenu.Instance.addItem({ description: "Options...", subitems: layoutItems, icon: "eye" }); + } + + toggleStarburst = action(() => { + if (this._layoutEngine === 'starburst') { + const defaultSize = 110; + this.layoutDoc.overflow = undefined; + this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - NumCast(this.layoutDoc._starburstPileWidth, defaultSize) / 2; + this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - NumCast(this.layoutDoc._starburstPileHeight, defaultSize) / 2; + this.layoutDoc._width = NumCast(this.layoutDoc._starburstPileWidth, defaultSize); + this.layoutDoc._height = NumCast(this.layoutDoc._starburstPileHeight, defaultSize); + this._layoutEngine = 'pass'; + } else { + const defaultSize = 25; + this.layoutDoc.overflow = 'visible'; + !this.layoutDoc._starburstRadius && (this.layoutDoc._starburstRadius = 500); + !this.layoutDoc._starburstDocScale && (this.layoutDoc._starburstDocScale = 2.5); + if (this._layoutEngine === 'pass') { + this.rootDoc.x = NumCast(this.rootDoc.x) + this.layoutDoc[WidthSym]() / 2 - defaultSize / 2; + this.rootDoc.y = NumCast(this.rootDoc.y) + this.layoutDoc[HeightSym]() / 2 - defaultSize / 2; + this.layoutDoc._starburstPileWidth = this.layoutDoc[WidthSym](); + this.layoutDoc._starburstPileHeight = this.layoutDoc[HeightSym](); + } + this.layoutDoc._width = this.layoutDoc._height = defaultSize; + this._layoutEngine = 'starburst'; + } + }); + + _undoBatch: UndoManager.Batch | undefined; + pointerDown = (e: React.PointerEvent) => { + let dist = 0; + SelectionManager.SetIsDragging(true); + // this._lastTap should be set to 0, and this._doubleTap should be set to false in the class header + setupMoveUpEvents(this, e, (e: PointerEvent, down: number[], delta: number[]) => { + if (this.layoutEngine() === "pass" && this.childDocs.length && this.props.isSelected(true)) { + dist += Math.sqrt(delta[0] * delta[0] + delta[1] * delta[1]); + if (dist > 100) { + if (!this._undoBatch) { + this._undoBatch = UndoManager.StartBatch("layout pile"); + } + const doc = this.childDocs[0]; + doc.x = e.clientX; + doc.y = e.clientY; + this.props.addDocTab(doc, "inParent") && this.props.removeDocument(doc); + dist = 0; + } + } + return false; + }, () => { + this._undoBatch?.end(); + this._undoBatch = undefined; + SelectionManager.SetIsDragging(false); + if (!this.childDocs.length) { + this.props.ContainingCollectionView?.removeDocument(this.props.Document); + } + }, emptyFunction, false, this.layoutEngine() === "pass" && this.props.isSelected(true)); // this sets _doubleTap + } + + onClick = (e: React.MouseEvent) => { + if (e.button === 0 && (this._doubleTap || this.layoutEngine() === "starburst")) { + SelectionManager.DeselectAll(); + this.toggleStarburst(); + e.stopPropagation(); + } + // else if (this.layoutEngine() === "pass") { + // runInAction(() => this._contentsActive = false); + // setTimeout(action(() => this._contentsActive = true), 300); + // } + } + + render() { + + return <div className={"collectionPileView"} onContextMenu={this.specificMenu} onClick={this.onClick} onPointerDown={this.pointerDown} + style={{ width: this.props.PanelWidth(), height: `calc(100% - ${this.props.Document._chromeStatus === "enabled" ? 51 : 0}px)` }}> + {this.contents} + </div>; + } +} diff --git a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx index 670d6dbb2..972714e34 100644 --- a/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx +++ b/src/client/views/collections/CollectionSchemaMovableTableHOC.tsx @@ -54,7 +54,7 @@ export class MovableColumn extends React.Component<MovableColumnProps> { } createColDropTarget = (ele: HTMLDivElement) => { - this._colDropDisposer && this._colDropDisposer(); + this._colDropDisposer?.(); if (ele) { this._colDropDisposer = DragManager.MakeDropTarget(ele, this.colDrop.bind(this)); } diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 24a3119cc..e3720bf01 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -49,7 +49,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { @computed get columnWidth() { TraceMobx(); return Math.min(this.props.PanelWidth() / (this.props as any).ContentScaling() - 2 * this.xMargin, - this.isStackingView ? Number.MAX_VALUE : NumCast(this.props.Document.columnWidth, 250)); + this.isStackingView ? Number.MAX_VALUE : this.props.Document.columnWidth === -1 ? this.props.PanelWidth() - 2 * this.xMargin : NumCast(this.props.Document.columnWidth, 250)); } @computed get NodeWidth() { return this.props.PanelWidth() - this.gridGap; } @@ -189,8 +189,8 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { active={this.props.active} whenActiveChanged={this.props.whenActiveChanged} addDocTab={this.addDocTab} - pinToPres={this.props.pinToPres}> - </ContentFittingDocumentView>; + pinToPres={this.props.pinToPres} + />; } getDocWidth(d?: Doc) { @@ -303,7 +303,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { const doc = this.props.DataDoc && this.props.DataDoc.layout === this.layoutDoc ? this.props.DataDoc : this.layoutDoc; this.observer = new _global.ResizeObserver(action((entries: any) => { if (this.props.Document._autoHeight && ref && this.refList.length && !SelectionManager.GetIsDragging()) { - Doc.Layout(doc)._height = Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", "")))); + Doc.Layout(doc)._height = Math.min(1200, Math.max(...this.refList.map(r => Number(getComputedStyle(r).height.replace("px", ""))))); } })); this.observer.observe(ref); diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 5d926b7c7..323d7be25 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -156,7 +156,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC @action collapseSection = () => { if (this.props.headingObject) { - this._headingsHack++; this.props.headingObject.setCollapsed(!this.props.headingObject.collapsed); this.toggleVisibility(); } @@ -225,8 +224,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC private toggleVisibility = action(() => this.collapsed = !this.collapsed); - @observable _headingsHack: number = 1; - menuCallback = (x: number, y: number) => { ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; @@ -300,7 +297,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC contents: evContents, oneLine: true, HeadingObject: this.props.headingObject, - HeadingsHack: this._headingsHack, toggle: this.toggleVisibility, color: this._color }; @@ -309,7 +305,6 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC SetValue: this.addDocument, contents: "+ NEW", HeadingObject: this.props.headingObject, - HeadingsHack: this._headingsHack, toggle: this.toggleVisibility, color: this._color }; @@ -364,7 +359,7 @@ export class CollectionStackingViewFieldColumn extends React.Component<CSVFieldC <div className="collectionStackingViewFieldColumn" key={heading} style={{ width: `${100 / ((uniqueHeadings.length + ((chromeStatus !== 'view-mode' && chromeStatus !== 'disabled') ? 1 : 0)) || 1)}%`, - height: SelectionManager.GetIsDragging() ? "100%" : undefined, + height: undefined, // SelectionManager.GetIsDragging() ? "100%" : undefined, background: this._background }} ref={this.createColumnDropRef} onPointerEnter={this.pointerEntered} onPointerLeave={this.pointerLeave}> diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 78ba3d6e6..1bfd408f8 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -1,30 +1,30 @@ import { action, computed, IReactionDisposer, reaction } from "mobx"; +import { basename } from 'path'; import CursorField from "../../../new_fields/CursorField"; -import { Doc, DocListCast, Opt, WidthSym, HeightSym } from "../../../new_fields/Doc"; +import { Doc, Opt } from "../../../new_fields/Doc"; import { Id } from "../../../new_fields/FieldSymbols"; import { List } from "../../../new_fields/List"; import { listSpec } from "../../../new_fields/Schema"; import { ScriptField } from "../../../new_fields/ScriptField"; -import { Cast, StrCast } from "../../../new_fields/Types"; +import { Cast } from "../../../new_fields/Types"; +import { GestureUtils } from "../../../pen-gestures/GestureUtils"; import { CurrentUserUtils } from "../../../server/authentication/models/current_user_utils"; +import { Upload } from "../../../server/SharedMediaTypes"; import { Utils } from "../../../Utils"; +import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; import { DocServer } from "../../DocServer"; -import { DocumentType } from "../../documents/DocumentTypes"; import { Docs, DocumentOptions } from "../../documents/Documents"; +import { DocumentType } from "../../documents/DocumentTypes"; +import { Networking } from "../../Network"; import { DragManager } from "../../util/DragManager"; +import { ImageUtils } from "../../util/Import & Export/ImageUtils"; +import { InteractionUtils } from "../../util/InteractionUtils"; import { undoBatch, UndoManager } from "../../util/UndoManager"; import { DocComponent } from "../DocComponent"; import { FieldViewProps } from "../nodes/FieldView"; -import { FormattedTextBox, GoogleRef } from "../nodes/FormattedTextBox"; +import { FormattedTextBox, GoogleRef } from "../nodes/formattedText/FormattedTextBox"; import { CollectionView } from "./CollectionView"; import React = require("react"); -import { basename } from 'path'; -import { GooglePhotos } from "../../apis/google_docs/GooglePhotosClientUtils"; -import { ImageUtils } from "../../util/Import & Export/ImageUtils"; -import { Networking } from "../../Network"; -import { GestureUtils } from "../../../pen-gestures/GestureUtils"; -import { InteractionUtils } from "../../util/InteractionUtils"; -import { Upload } from "../../../server/SharedMediaTypes"; export interface CollectionViewProps extends FieldViewProps { addDocument: (document: Doc) => boolean; @@ -64,7 +64,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: this.multiTouchDisposer?.(); if (ele) { this._mainCont = ele; - this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this)); + this.dropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.layoutDoc); this.gestureDisposer = GestureUtils.MakeGestureTarget(ele, this.onGesture.bind(this)); this.multiTouchDisposer = InteractionUtils.MakeMultiTouchTarget(ele, this.onTouchStart.bind(this)); } @@ -400,9 +400,7 @@ export function CollectionSubView<T, X>(schemaCtor: (doc: Doc) => T, moreProps?: if (generatedDocuments.length) { const set = generatedDocuments.length > 1 && generatedDocuments.map(d => Doc.iconify(d)); if (set) { - const pile = Docs.Create.FreeformDocument(generatedDocuments, { ...options, title: "pile", _LODdisable: true, }); - Doc.pileup(pile, generatedDocuments); - addDocument(pile); + addDocument(Doc.pileup(generatedDocuments, options.x!, options.y!)); } else { generatedDocuments.forEach(addDocument); } diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 63498cea1..045134225 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -19,7 +19,6 @@ const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; import React = require("react"); -import { DocumentView } from "../nodes/DocumentView"; @observer export class CollectionTimeView extends CollectionSubView(doc => doc) { @@ -84,7 +83,7 @@ export class CollectionTimeView extends CollectionSubView(doc => doc) { } @computed get contents() { - return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%" }} onPointerDown={this.contentsDown}> + return <div className="collectionTimeView-innards" key="timeline" style={{ width: "100%", pointerEvents: this.props.active() ? undefined : "none" }} onPointerDown={this.contentsDown}> <CollectionFreeFormView {...this.props} childClickScript={this._childClickedScript} viewDefDivClick={this._viewDefDivClick} fitToBox={true} freezeChildDimensions={BoolCast(this.layoutDoc._freezeChildDimensions, true)} layoutEngine={this.layoutEngine} /> </div>; } diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index ca2004b79..d938bd7ad 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -143,7 +143,7 @@ class TreeView extends React.Component<TreeViewProps> { protected createTreeDropTarget = (ele: HTMLDivElement) => { this._treedropDisposer && this._treedropDisposer(); - ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this))); + ele && (this._treedropDisposer = DragManager.MakeDropTarget(ele, this.treeDrop.bind(this)), this.props.document); } onPointerEnter = (e: React.PointerEvent): void => { @@ -261,7 +261,7 @@ class TreeView extends React.Component<TreeViewProps> { docHeight = () => { const layoutDoc = Doc.Layout(this.props.document); const bounds = this.boundsOfCollectionDocument; - return Math.min(this.MAX_EMBED_HEIGHT, (() => { + return Math.max(70, Math.min(this.MAX_EMBED_HEIGHT, (() => { const aspect = NumCast(layoutDoc._nativeHeight, layoutDoc._fitWidth ? 0 : layoutDoc[HeightSym]()) / NumCast(layoutDoc._nativeWidth, layoutDoc._fitWidth ? 1 : layoutDoc[WidthSym]()); if (aspect) return this.docWidth() * aspect; if (bounds) return this.docWidth() * (bounds.b - bounds.y) / (bounds.r - bounds.x); @@ -269,7 +269,7 @@ class TreeView extends React.Component<TreeViewProps> { Math.min(this.docWidth() * NumCast(layoutDoc.scrollHeight, NumCast(layoutDoc._nativeHeight)) / NumCast(layoutDoc._nativeWidth, NumCast(this.props.containingCollection._height)))) : NumCast(layoutDoc._height) ? NumCast(layoutDoc._height) : 50; - })()); + })())); } @computed get expandedField() { @@ -321,6 +321,9 @@ class TreeView extends React.Component<TreeViewProps> { return rows; } + rtfWidth = () => Math.min(Doc.Layout(this.props.document)?.[WidthSym](), this.props.panelWidth() - 20); + rtfHeight = () => this.rtfWidth() < Doc.Layout(this.props.document)?.[WidthSym]() ? Math.min(Doc.Layout(this.props.document)?.[HeightSym](), this.MAX_EMBED_HEIGHT) : this.MAX_EMBED_HEIGHT; + @computed get renderContent() { const expandKey = this.treeViewExpandedView === this.fieldKey ? this.fieldKey : this.treeViewExpandedView === "links" ? "links" : undefined; if (expandKey !== undefined) { @@ -345,7 +348,9 @@ class TreeView extends React.Component<TreeViewProps> { </div></ul>; } else { const layoutDoc = Doc.Layout(this.props.document); - return <div ref={this._dref} style={{ display: "inline-block", height: this.docHeight() }} key={this.props.document[Id] + this.props.document.title}> + const panelHeight = layoutDoc.type === DocumentType.RTF ? this.rtfHeight : this.docHeight; + const panelWidth = layoutDoc.type === DocumentType.RTF ? this.rtfWidth : this.docWidth; + return <div ref={this._dref} style={{ display: "inline-block", height: panelHeight() }} key={this.props.document[Id] + this.props.document.title}> <ContentFittingDocumentView Document={layoutDoc} DataDocument={this.templateDataDoc} @@ -355,8 +360,10 @@ class TreeView extends React.Component<TreeViewProps> { backgroundColor={this.props.backgroundColor} fitToBox={this.boundsOfCollectionDocument !== undefined} FreezeDimensions={true} - PanelWidth={this.docWidth} - PanelHeight={this.docHeight} + NativeWidth={layoutDoc.type === DocumentType.RTF ? this.rtfWidth : undefined} + NativeHeight={layoutDoc.type === DocumentType.RTF ? this.rtfHeight : undefined} + PanelWidth={panelWidth} + PanelHeight={panelHeight} getTransform={this.docTransform} CollectionDoc={this.props.containingCollection} CollectionView={undefined} @@ -663,7 +670,7 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll protected createTreeDropTarget = (ele: HTMLDivElement) => { this.treedropDisposer?.(); if (this._mainEle = ele) { - this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this)); + this.treedropDisposer = DragManager.MakeDropTarget(ele, this.onInternalDrop.bind(this), this.props.Document); } } @@ -722,21 +729,10 @@ export class CollectionTreeView extends CollectionSubView<Document, Partial<coll } }); }); - const { TextDocument, ImageDocument, CarouselDocument, TreeDocument } = Docs.Create; + const { ImageDocument } = Docs.Create; const { Document } = this.props; const fallbackImg = "http://www.cs.brown.edu/~bcz/face.gif"; - const detailedTemplate = `{ "doc": { "type": "doc", "content": [ { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "year" } } ] }, { "type": "paragraph", "content": [ { "type": "dashField", "attrs": { "fieldKey": "company" } } ] } ] }, "selection":{"type":"text","anchor":1,"head":1},"storedMarks":[] }`; - - const textDoc = TextDocument("", { title: "details", _autoHeight: true }); - const detailView = Docs.Create.StackingDocument([ - CarouselDocument([], { title: "data", _height: 350, _itemIndex: 0, backgroundColor: "#9b9b9b3F" }), - textDoc, - TextDocument("", { title: "shortDescription", _autoHeight: true }), - TreeDocument([], { title: "narratives", _height: 75, treeViewHideTitle: true }) - ], { _chromeStatus: "disabled", _width: 300, _height: 300, _autoHeight: true, title: "detailView" }); - textDoc.data = new RichTextField(detailedTemplate, "year company"); - detailView.isTemplateDoc = makeTemplate(detailView); - + const detailView = Cast(Cast(Doc.UserDoc()["template-button-detail"], Doc, null)?.dragFactory, Doc, null); const heroView = ImageDocument(fallbackImg, { title: "heroView", isTemplateDoc: true, isTemplateForField: "hero", }); // this acts like a template doc and a template field ... a little weird, but seems to work? heroView.proto!.layout = ImageBox.LayoutString("hero"); heroView._showTitle = "title"; diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 47e79f088..801704673 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -46,6 +46,7 @@ import { InteractionUtils } from '../../util/InteractionUtils'; import { ObjectField } from '../../../new_fields/ObjectField'; import CollectionMapView from './CollectionMapView'; import { Transform } from 'prosemirror-transform'; +import { CollectionPileView } from './CollectionPileView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; export const Flyout = higflyout.default; @@ -67,7 +68,8 @@ export enum CollectionViewType { Carousel = "carousel", Linear = "linear", Staff = "staff", - Map = "map" + Map = "map", + Pile = "pileup" } export interface CollectionRenderProps { @@ -169,6 +171,7 @@ export class CollectionView extends Touchable<FieldViewProps> { case CollectionViewType.Multicolumn: return (<CollectionMulticolumnView key="collview" {...props} />); case CollectionViewType.Multirow: return (<CollectionMultirowView key="rpwview" {...props} />); case CollectionViewType.Linear: { return (<CollectionLinearView key="collview" {...props} />); } + case CollectionViewType.Pile: { return (<CollectionPileView key="collview" {...props} />); } case CollectionViewType.Carousel: { return (<CollectionCarouselView key="collview" {...props} />); } case CollectionViewType.Stacking: { this.props.Document.singleColumn = true; return (<CollectionStackingView key="collview" {...props} />); } case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); } @@ -222,7 +225,7 @@ export class CollectionView extends Touchable<FieldViewProps> { if (!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 this.setupViewTypes("Change Perspective...", (vtype => { this.props.Document._viewType = vtype; return this.props.Document; }), true); - this.setupViewTypes("New Perspective...", vtype => { + this.setupViewTypes("Add a Perspective...", vtype => { const newRendition = Doc.MakeAlias(this.props.Document); newRendition._viewType = vtype; this.props.addDocTab(newRendition, "onRight"); @@ -490,7 +493,7 @@ export class CollectionView extends Touchable<FieldViewProps> { {!this.props.isSelected() || this.props.PanelHeight() < 100 || this.props.Document.hideFilterView ? (null) : <div className="collectionTimeView-dragger" title="library View Dragger" onPointerDown={this.onPointerDown} style={{ right: this.facetWidth() - 10 }} /> } - {this.filterView} + {this.facetWidth() < 10 ? (null) : this.filterView} </div>); } }
\ No newline at end of file diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index 5203eb55f..e4581eb46 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -33,6 +33,17 @@ outline-color: black; } + .collectionViewBaseChrome-button{ + font-size: 75%; + text-transform: uppercase; + letter-spacing: 2px; + background: rgb(238, 238, 238); + color: purple; + outline-color: black; + border: none; + padding: 12px 10px 11px 10px; + margin-left: 10px; + } .collectionViewBaseChrome-cmdPicker { margin-left: 3px; margin-right: 0px; diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 7315d2c4e..d26e3a38b 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -215,9 +215,9 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro private dropDisposer?: DragManager.DragDropDisposer; protected createDropTarget = (ele: HTMLDivElement) => { - this.dropDisposer && this.dropDisposer(); + this.dropDisposer?.(); if (ele) { - this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this)); + this.dropDisposer = DragManager.MakeDropTarget(ele, this.drop.bind(this), this.document); } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 566a6788b..9a864078a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -9,13 +9,15 @@ import React = require("react"); import { Id, ToString } from "../../../../new_fields/FieldSymbols"; import { ObjectField } from "../../../../new_fields/ObjectField"; import { RefField } from "../../../../new_fields/RefField"; +import { listSpec } from "../../../../new_fields/Schema"; export interface ViewDefBounds { type: string; - text?: string; + payload: any; x: number; y: number; z?: number; + text?: string; zIndex?: number; width?: number; height?: number; @@ -23,12 +25,13 @@ export interface ViewDefBounds { fontSize?: number; highlight?: boolean; color?: string; - payload: any; + replica?: string; + pair?: { layout: Doc, data?: Doc }; } export interface PoolData { - x?: number; - y?: number; + x: number; + y: number; z?: number; zIndex?: number; width?: number; @@ -36,6 +39,8 @@ export interface PoolData { color?: string; transition?: string; highlight?: boolean; + replica: string; + pair: { layout: Doc, data?: Doc }; } export interface ViewDefResult { @@ -72,89 +77,103 @@ function getTextWidth(text: string, font: string): number { interface PivotColumn { docs: Doc[]; + replicas: string[]; filters: string[]; } export function computerPassLayout( poolData: Map<string, PoolData>, pivotDoc: Doc, - childDocs: Doc[], - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { - const docMap = new Map<Doc, ViewDefBounds>(); - childDocs.forEach((doc, i) => { - docMap.set(doc, { - type: "doc", - x: NumCast(doc.x), - y: NumCast(doc.y), - width: doc[WidthSym](), - height: doc[HeightSym](), - payload: undefined + const docMap = new Map<string, PoolData>(); + childPairs.forEach(({ layout, data }, i) => { + docMap.set(layout[Id], { + x: NumCast(layout.x), + y: NumCast(layout.y), + width: layout[WidthSym](), + height: layout[HeightSym](), + pair: { layout, data }, + replica: "" }); }); - return normalizeResults(panelDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c))); + return normalizeResults(panelDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); } export function computerStarburstLayout( poolData: Map<string, PoolData>, pivotDoc: Doc, - childDocs: Doc[], - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { - const docMap = new Map<Doc, ViewDefBounds>(); + const docMap = new Map<string, PoolData>(); const burstRadius = [NumCast(pivotDoc._starburstRadius, panelDim[0]), NumCast(pivotDoc._starburstRadius, panelDim[1])]; const docScale = NumCast(pivotDoc._starburstDocScale); const docSize = docScale * 100; // assume a icon sized at 100 const scaleDim = [burstRadius[0] + docSize, burstRadius[1] + docSize]; - childDocs.forEach((doc, i) => { - const deg = i / childDocs.length * Math.PI * 2; - docMap.set(doc, { - type: "doc", - x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * doc[WidthSym]() / 2, - y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * doc[HeightSym]() / 2, - width: docScale * doc[WidthSym](), - height: docScale * doc[HeightSym](), - payload: undefined + childPairs.forEach(({ layout, data }, i) => { + const deg = i / childPairs.length * Math.PI * 2; + docMap.set(layout[Id], { + x: Math.cos(deg) * (burstRadius[0] / 3) - docScale * layout[WidthSym]() / 2, + y: Math.sin(deg) * (burstRadius[1] / 3) - docScale * layout[HeightSym]() / 2, + width: docScale * layout[WidthSym](), + height: docScale * layout[HeightSym](), + pair: { layout, data }, + replica: "" }); }); - return normalizeResults(scaleDim, 12, childPairs, docMap, poolData, viewDefsToJSX, [], 0, [], childDocs.filter(c => !filterDocs.includes(c))); + return normalizeResults(scaleDim, 12, docMap, poolData, viewDefsToJSX, [], 0, []); } export function computePivotLayout( poolData: Map<string, PoolData>, pivotDoc: Doc, - childDocs: Doc[], - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { + const docMap = new Map<string, PoolData>(); const fieldKey = "data"; const pivotColumnGroups = new Map<FieldResult<Field>, PivotColumn>(); const pivotFieldKey = toLabel(pivotDoc._pivotField); - for (const doc of filterDocs) { - const val = Field.toString(doc[pivotFieldKey] as Field); - if (val) { - !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val] }); - pivotColumnGroups.get(val)!.docs.push(doc); + childPairs.map(pair => { + const lval = Cast(pair.layout[pivotFieldKey], listSpec("string"), null); + const val = Field.toString(pair.layout[pivotFieldKey] as Field); + if (lval) { + lval.forEach((val, i) => { + !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); + pivotColumnGroups.get(val)!.docs.push(pair.layout); + pivotColumnGroups.get(val)!.replicas.push(i.toString()); + }); + } else if (val) { + !pivotColumnGroups.get(val) && pivotColumnGroups.set(val, { docs: [], filters: [val], replicas: [] }); + pivotColumnGroups.get(val)!.docs.push(pair.layout); + pivotColumnGroups.get(val)!.replicas.push(""); + } else { + docMap.set(pair.layout[Id], { + x: 0, + y: 0, + zIndex: -99, + width: 0, + height: 0, + pair, + replica: "" + }); } - } + }); let nonNumbers = 0; - childDocs.map(doc => { - const num = toNumber(doc[pivotFieldKey]); + childPairs.map(pair => { + const num = toNumber(pair.layout[pivotFieldKey]); if (num === undefined || Number.isNaN(num)) { nonNumbers++; } }); - const pivotNumbers = nonNumbers / childDocs.length < .1; + const pivotNumbers = nonNumbers / childPairs.length < .1; if (pivotColumnGroups.size > 10) { const arrayofKeys = Array.from(pivotColumnGroups.keys()); const sortedKeys = pivotNumbers ? arrayofKeys.sort((n1: FieldResult, n2: FieldResult) => toNumber(n1)! - toNumber(n2)!) : arrayofKeys.sort(); @@ -166,6 +185,7 @@ export function computePivotLayout( const newgrp = pivotColumnGroups.get(sortedKeys[j])!; curgrp.docs.push(...newgrp.docs); curgrp.filters.push(...newgrp.filters); + curgrp.replicas.push(...newgrp.replicas); pivotColumnGroups.delete(sortedKeys[j]); } } @@ -193,7 +213,6 @@ export function computePivotLayout( } } - const docMap = new Map<Doc, ViewDefBounds>(); const groupNames: ViewDefBounds[] = []; const expander = 1.05; @@ -216,7 +235,7 @@ export function computePivotLayout( fontSize, payload: val }); - for (const doc of val.docs) { + val.docs.forEach((doc, i) => { const layoutDoc = Doc.Layout(doc); let wid = pivotAxisWidth; let hgt = layoutDoc._nativeWidth ? (NumCast(layoutDoc._nativeHeight) / NumCast(layoutDoc._nativeWidth)) * pivotAxisWidth : pivotAxisWidth; @@ -224,27 +243,27 @@ export function computePivotLayout( hgt = pivotAxisWidth; wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; } - docMap.set(doc, { - type: "doc", + docMap.set(doc[Id] + (val.replicas || ""), { x: x + xCount * pivotAxisWidth * expander + (pivotAxisWidth - wid) / 2 + (val.docs.length < numCols ? (numCols - val.docs.length) * pivotAxisWidth / 2 : 0), y: -y + (pivotAxisWidth - hgt) / 2, width: wid, height: hgt, - payload: undefined + pair: { layout: doc }, + replica: val.replicas[i] }); xCount++; if (xCount >= numCols) { xCount = 0; y += pivotAxisWidth * expander; } - } + }); x += pivotAxisWidth * (numCols * expander + gap); }); const dividers = sortedPivotKeys.map((key, i) => ({ type: "div", color: "lightGray", x: i * pivotAxisWidth * (numCols * expander + gap) - pivotAxisWidth * (expander - 1) / 2, y: -maxColHeight + pivotAxisWidth, width: pivotAxisWidth * numCols * expander, height: maxColHeight, payload: pivotColumnGroups.get(key)!.filters })); groupNames.push(...dividers); - return normalizeResults(panelDim, max_text, childPairs, docMap, poolData, viewDefsToJSX, groupNames, 0, [], childDocs.filter(c => !filterDocs.includes(c))); + return normalizeResults(panelDim, max_text, docMap, poolData, viewDefsToJSX, groupNames, 0, []); } function toNumber(val: FieldResult<Field>) { @@ -254,15 +273,13 @@ function toNumber(val: FieldResult<Field>) { export function computeTimelineLayout( poolData: Map<string, PoolData>, pivotDoc: Doc, - childDocs: Doc[], - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] ) { const fieldKey = "data"; const pivotDateGroups = new Map<number, Doc[]>(); - const docMap = new Map<Doc, ViewDefBounds>(); + const docMap = new Map<string, PoolData>(); const groupNames: ViewDefBounds[] = []; const timelineFieldKey = Field.toString(pivotDoc._pivotField as Field); const curTime = toNumber(pivotDoc[fieldKey + "-timelineCur"]); @@ -278,11 +295,11 @@ export function computeTimelineLayout( let minTime = minTimeReq === undefined ? Number.MAX_VALUE : minTimeReq; let maxTime = maxTimeReq === undefined ? -Number.MAX_VALUE : maxTimeReq; - filterDocs.map(doc => { - const num = NumCast(doc[timelineFieldKey], Number(StrCast(doc[timelineFieldKey]))); + childPairs.forEach(pair => { + const num = NumCast(pair.layout[timelineFieldKey], Number(StrCast(pair.layout[timelineFieldKey]))); if (!Number.isNaN(num) && (!minTimeReq || num >= minTimeReq) && (!maxTimeReq || num <= maxTimeReq)) { !pivotDateGroups.get(num) && pivotDateGroups.set(num, []); - pivotDateGroups.get(num)!.push(doc); + pivotDateGroups.get(num)!.push(pair.layout); minTime = Math.min(num, minTime); maxTime = Math.max(num, maxTime); } @@ -341,7 +358,7 @@ export function computeTimelineLayout( } const divider = { type: "div", color: Cast(Doc.UserDoc().activeWorkspace, Doc, null)?.darkScheme ? "dimGray" : "black", x: 0, y: 0, width: panelDim[0], height: -1, payload: undefined }; - return normalizeResults(panelDim, fontHeight, childPairs, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider], childDocs.filter(c => !filterDocs.includes(c))); + return normalizeResults(panelDim, fontHeight, docMap, poolData, viewDefsToJSX, groupNames, (maxTime - minTime) * scaling, [divider]); function layoutDocsAtTime(keyDocs: Doc[], key: number) { keyDocs.forEach(doc => { @@ -353,44 +370,55 @@ export function computeTimelineLayout( hgt = pivotAxisWidth; wid = layoutDoc._nativeHeight ? (NumCast(layoutDoc._nativeWidth) / NumCast(layoutDoc._nativeHeight)) * pivotAxisWidth : pivotAxisWidth; } - docMap.set(doc, { - type: "doc", + docMap.set(doc[Id], { x: x, y: -Math.sqrt(stack) * pivotAxisWidth / 2 - pivotAxisWidth + (pivotAxisWidth - hgt) / 2, - zIndex: (curTime === key ? 1000 : zind++), highlight: curTime === key, width: wid / (Math.max(stack, 1)), height: hgt / (Math.max(stack, 1)), payload: undefined + zIndex: (curTime === key ? 1000 : zind++), + highlight: curTime === key, + width: wid / (Math.max(stack, 1)), + height: hgt / (Math.max(stack, 1)), + pair: { layout: doc }, + replica: "" }); stacking[stack] = x + pivotAxisWidth; }); } } -function normalizeResults(panelDim: number[], fontHeight: number, childPairs: { data?: Doc, layout: Doc }[], docMap: Map<Doc, ViewDefBounds>, - poolData: Map<string, PoolData>, viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], groupNames: ViewDefBounds[], minWidth: number, extras: ViewDefBounds[], - extraDocs: Doc[]): ViewDefResult[] { - +function normalizeResults( + panelDim: number[], + fontHeight: number, + docMap: Map<string, PoolData>, + poolData: Map<string, PoolData>, + viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[], + groupNames: ViewDefBounds[], + minWidth: number, + extras: ViewDefBounds[] +): ViewDefResult[] { const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds); - const docEles = childPairs.filter(d => docMap.get(d.layout)).map(pair => docMap.get(pair.layout) as ViewDefBounds); - const aggBounds = aggregateBounds(docEles.concat(grpEles), 0, 0); + const docEles = Array.from(docMap.entries()).map(ele => ele[1]); + const aggBounds = aggregateBounds(grpEles.concat(docEles.map(de => ({ ...de, type: "doc", payload: "" }))).filter(e => e.zIndex !== -99), 0, 0); aggBounds.r = Math.max(minWidth, aggBounds.r - aggBounds.x); const wscale = panelDim[0] / (aggBounds.r - aggBounds.x); let scale = wscale * (aggBounds.b - aggBounds.y) > panelDim[1] ? (panelDim[1]) / (aggBounds.b - aggBounds.y) : wscale; if (Number.isNaN(scale)) scale = 1; - childPairs.filter(d => docMap.get(d.layout)).map(pair => { - const newPosRaw = docMap.get(pair.layout); + Array.from(docMap.entries()).filter(ele => ele[1].pair).map(ele => { + const newPosRaw = ele[1]; if (newPosRaw) { const newPos = { x: newPosRaw.x * scale, y: newPosRaw.y * scale, z: newPosRaw.z, + replica: newPosRaw.replica, highlight: newPosRaw.highlight, zIndex: newPosRaw.zIndex, width: (newPosRaw.width || 0) * scale, - height: newPosRaw.height! * scale + height: newPosRaw.height! * scale, + pair: ele[1].pair }; - poolData.set(pair.layout[Id], { transition: "transform 1s", ...newPos }); + poolData.set(newPos.pair.layout[Id] + (newPos.replica || ""), { transition: "transform 1s", ...newPos }); } }); - extraDocs.map(ed => poolData.set(ed[Id], { x: 0, y: 0, zIndex: -99 })); return viewDefsToJSX(extras.concat(groupNames).map(gname => ({ type: gname.type, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx index 702b02a20..4b5e977df 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLinksView.tsx @@ -36,7 +36,7 @@ export class CollectionFreeFormLinksView extends React.Component { } render() { - return <div className="collectionfreeformlinksview-container"> + return SelectionManager.GetIsDragging() ? (null) : <div className="collectionfreeformlinksview-container"> <svg className="collectionfreeformlinksview-svgCanvas"> {this.uniqueConnections} </svg> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 4e4e85e13..60c39c825 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -13,6 +13,15 @@ border-radius: inherit; } +.collectionfreeformview-viewdef { + > .collectionFreeFormDocumentView-container { + pointer-events: none; + .contentFittingDocumentDocumentView-previewDoc { + pointer-events: all; + } + } +} + .collectionfreeformview-ease { transition: transform 500ms; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 1f0bc89c5..d85233041 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -32,7 +32,7 @@ import { ContextMenuProps } from "../../ContextMenuItem"; import { InkingControl } from "../../InkingControl"; import { CollectionFreeFormDocumentView } from "../../nodes/CollectionFreeFormDocumentView"; import { DocumentViewProps, DocumentView } from "../../nodes/DocumentView"; -import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { pageSchema } from "../../nodes/ImageBox"; import PDFMenu from "../../pdf/PDFMenu"; import { CollectionDockingView } from "../CollectionDockingView"; @@ -44,6 +44,7 @@ import MarqueeOptionsMenu from "./MarqueeOptionsMenu"; import { MarqueeView } from "./MarqueeView"; import React = require("react"); import { CollectionViewType } from "../CollectionView"; +import { Timeline } from "../../animationtimeline/Timeline"; library.add(faEye as any, faTable, faPaintBrush, faExpandArrowsAlt, faCompressArrowsAlt, faCompass, faUpload, faBraille, faChalkboard, faFileUpload); @@ -85,14 +86,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P private _clusterDistance: number = 75; private _hitCluster = false; private _layoutComputeReaction: IReactionDisposer | undefined; - private _layoutPoolData = new ObservableMap<string, any>(); - private _cachedPool: Map<string, any> = new Map(); + private _layoutPoolData = new ObservableMap<string, PoolData>(); + private _layoutSizeData = new ObservableMap<string, { width?: number, height?: number }>(); + private _cachedPool: Map<string, PoolData> = new Map(); @observable private _pullCoords: number[] = [0, 0]; @observable private _pullDirection: string = ""; public get displayName() { return "CollectionFreeFormView(" + this.props.Document.title?.toString() + ")"; } // this makes mobx trace() statements more descriptive @observable.shallow _layoutElements: ViewDefResult[] = []; // shallow because some layout items (eg pivot labels) are just generated 'divs' and can't be frozen as observables @observable _clusterSets: (Doc[])[] = []; + @observable _timelineRef = React.createRef<Timeline>(); @computed get fitToContentScaling() { return this.fitToContent ? NumCast(this.layoutDoc.fitToContentScaling, 1) : 1; } @computed get fitToContent() { return (this.props.fitToBox || this.Document._fitToBox) && !this.isAnnotationOverlay; } @@ -508,7 +511,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (this.layoutDoc.targetScale && (Math.abs(e.pageX - this._downX) < 3 && Math.abs(e.pageY - this._downY) < 3)) { if (Date.now() - this._lastTap < 300) { const docpt = this.getTransform().transformPoint(e.clientX, e.clientY); - this.scaleAtPt(docpt, NumCast(this.layoutDoc.targetScale, NumCast(this.layoutDoc.scale))); + this.scaleAtPt(docpt, 1); e.stopPropagation(); e.preventDefault(); } @@ -728,7 +731,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P if (!this.isAnnotationOverlay && clamp) { // this section wraps the pan position, horizontally and/or vertically whenever the content is panned out of the viewing bounds const docs = this.childLayoutPairs.filter(pair => pair.layout instanceof Doc).map(pair => pair.layout); - const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc)).map(doc => this.childDataProvider(doc)); + const measuredDocs = docs.filter(doc => doc && this.childDataProvider(doc, "")).map(doc => this.childDataProvider(doc, "")); if (measuredDocs.length) { const ranges = measuredDocs.reduce(({ xrange, yrange }, { x, y, width, height }) => // computes range of content ({ @@ -821,18 +824,18 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const savedState = { px: this.Document._panX, py: this.Document._panY, s: this.Document.scale, pt: this.Document.panTransformType }; - if (!willZoom && DocumentView._focusHack.length) { - Doc.BrushDoc(this.props.Document); - !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1); - } else { - if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) { - if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow - } - Doc.BrushDoc(this.props.Document); - this.props.focus(this.props.Document); - willZoom && this.setScaleToZoom(layoutdoc, scale); + // if (!willZoom && DocumentView._focusHack.length) { + // Doc.BrushDoc(this.props.Document); + // !doc.z && NumCast(this.layoutDoc.scale) < 1 && this.scaleAtPt(DocumentView._focusHack, 1); // [NumCast(doc.x), NumCast(doc.y)], 1); + // } else { + if (DocListCast(this.dataDoc[this.props.fieldKey]).includes(doc)) { + if (!doc.z) this.setPan(newPanX, newPanY, "Ease", true); // docs that are floating in their collection can't be panned to from their collection -- need to propagate the pan to a parent freeform somehow } + Doc.BrushDoc(this.props.Document); + this.props.focus(this.props.Document); + willZoom && this.setScaleToZoom(layoutdoc, scale); Doc.linkFollowHighlight(doc); + //} afterFocus && setTimeout(() => { if (afterFocus?.()) { @@ -853,7 +856,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P @computed get libraryPath() { return this.props.LibraryPath ? [...this.props.LibraryPath, this.props.Document] : []; } @computed get onChildClickHandler() { return this.props.childClickScript || ScriptCast(this.Document.onChildClick); } backgroundHalo = () => BoolCast(this.Document.useClusters); - + @computed get backgroundActive() { return this.layoutDoc.isBackground && (this.props.ContainingCollectionView?.active() || this.props.active()); } + parentActive = () => this.props.active() || this.backgroundActive ? true : false; getChildDocumentViewProps(childLayout: Doc, childData?: Doc): DocumentViewProps { return { ...this.props, @@ -879,7 +883,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P focus: this.focusDocument, backgroundColor: this.getClusterColor, backgroundHalo: this.backgroundHalo, - parentActive: this.props.active, + parentActive: this.parentActive, bringToFront: this.bringToFront, addDocTab: this.addDocTab, }; @@ -899,16 +903,16 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } return this.props.addDocTab(doc, where); }); - getCalculatedPositions(params: { doc: Doc, index: number, collection: Doc, docs: Doc[], state: any }): PoolData { + getCalculatedPositions(params: { pair: { layout: Doc, data?: Doc }, index: number, collection: Doc, docs: Doc[], state: any }): PoolData { const result = this.Document.arrangeScript?.script.run(params, console.log); if (result?.success) { - return { ...result, transition: "transform 1s" }; + return { x: 0, y: 0, transition: "transform 1s", ...result, pair: params.pair, replica: "" }; } - const layoutDoc = Doc.Layout(params.doc); - const { x, y, z, color, zIndex } = params.doc; + const layoutDoc = Doc.Layout(params.pair.layout); + const { x, y, z, color, zIndex } = params.pair.layout; return { x: NumCast(x), y: NumCast(y), z: Cast(z, "number"), color: StrCast(color), zIndex: Cast(zIndex, "number"), - width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number") + width: Cast(layoutDoc._width, "number"), height: Cast(layoutDoc._height, "number"), pair: params.pair, replica: "" }; } @@ -946,21 +950,22 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P } } - childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc) { - return this._layoutPoolData.get(doc[Id]); + childDataProvider = computedFn(function childDataProvider(this: any, doc: Doc, replica: string) { + return this._layoutPoolData.get(doc[Id] + (replica || "")); + }.bind(this)); + childSizeProvider = computedFn(function childSizeProvider(this: any, doc: Doc, replica: string) { + return this._layoutSizeData.get(doc[Id] + (replica || "")); }.bind(this)); doEngineLayout(poolData: Map<string, PoolData>, engine: ( poolData: Map<string, PoolData>, pivotDoc: Doc, - childDocs: Doc[], - filterDocs: Doc[], childPairs: { layout: Doc, data?: Doc }[], panelDim: number[], - viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[]) { - return engine(poolData, this.props.Document, this.childDocs, this.childDocs, - this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); + viewDefsToJSX: ((views: ViewDefBounds[]) => ViewDefResult[])) => ViewDefResult[] + ) { + return engine(poolData, this.props.Document, this.childLayoutPairs, [this.props.PanelWidth(), this.props.PanelHeight()], this.viewDefsToJSX); } doFreeformLayout(poolData: Map<string, PoolData>) { @@ -970,15 +975,18 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const elements = initResult && initResult.success ? this.viewDefsToJSX(initResult.result.views) : []; this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map((pair, i) => { - const pos = this.getCalculatedPositions({ doc: pair.layout, index: i, collection: this.Document, docs: layoutDocs, state }); + const pos = this.getCalculatedPositions({ pair, index: i, collection: this.Document, docs: layoutDocs, state }); poolData.set(pair.layout[Id], pos); }); return elements; } @computed get doInternalLayoutComputation() { - const newPool = new Map<string, any>(); - const engine = StrCast(this.layoutDoc._layoutEngine) || this.props.layoutEngine?.(); + TraceMobx(); + + + const newPool = new Map<string, PoolData>(); + const engine = this.props.layoutEngine?.() || StrCast(this.layoutDoc._layoutEngine); switch (engine) { case "pass": return { newPool, computedElementData: this.doEngineLayout(newPool, computerPassLayout) }; case "timeline": return { newPool, computedElementData: this.doEngineLayout(newPool, computeTimelineLayout) }; @@ -992,30 +1000,39 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P get doLayoutComputation() { const { newPool, computedElementData } = this.doInternalLayoutComputation; runInAction(() => - Array.from(newPool.keys()).map(key => { - const lastPos = this._cachedPool.get(key); // last computed pos - const newPos = newPool.get(key); - if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex || newPos.width !== lastPos.width || newPos.height !== lastPos.height) { - this._layoutPoolData.set(key, newPos); + Array.from(newPool.entries()).map(entry => { + const lastPos = this._cachedPool.get(entry[0]); // last computed pos + const newPos = entry[1]; + if (!lastPos || newPos.x !== lastPos.x || newPos.y !== lastPos.y || newPos.z !== lastPos.z || newPos.zIndex !== lastPos.zIndex) { + this._layoutPoolData.set(entry[0], newPos); + } + if (!lastPos || newPos.height !== lastPos.height || newPos.width !== lastPos.width) { + this._layoutSizeData.set(entry[0], { width: newPos.width, height: newPos.height }); } })); this._cachedPool.clear(); - Array.from(newPool.keys()).forEach(k => this._cachedPool.set(k, newPool.get(k))); + Array.from(newPool.entries()).forEach(k => this._cachedPool.set(k[0], k[1])); const elements: ViewDefResult[] = computedElementData.slice(); - const engine = this.props.layoutEngine?.() || this.props.Document._layoutEngine; - this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).forEach(pair => + const engine = this.props.layoutEngine?.() || StrCast(this.props.Document._layoutEngine); + Array.from(newPool.entries()).filter(entry => this.isCurrent(entry[1].pair.layout)).forEach(entry => elements.push({ ele: <CollectionFreeFormDocumentView - key={pair.layout[Id]} - {...this.getChildDocumentViewProps(pair.layout, pair.data)} + key={entry[1].pair.layout[Id] + (entry[1].replica || "")} + {...this.getChildDocumentViewProps(entry[1].pair.layout, entry[1].pair.data)} + replica={entry[1].replica} dataProvider={this.childDataProvider} + sizeProvider={this.childSizeProvider} LayoutDoc={this.childLayoutDocFunc} - pointerEvents={engine && engine !== "starburst" ? false : undefined} - jitterRotation={NumCast(this.props.Document.jitterRotation)} - fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} + pointerEvents={ + this.backgroundActive ? + true : + (this.props.viewDefDivClick || (engine === "pass" && !this.props.isSelected(true))) ? false : undefined} + jitterRotation={NumCast(this.props.Document._jitterRotation)} + //fitToBox={this.props.fitToBox || BoolCast(this.props.freezeChildDimensions)} // bcz: check this + fitToBox={BoolCast(this.props.freezeChildDimensions)} // bcz: check this FreezeDimensions={BoolCast(this.props.freezeChildDimensions)} />, - bounds: this.childDataProvider(pair.layout) + bounds: this.childDataProvider(entry[1].pair.layout, entry[1].replica) })); return elements; @@ -1076,6 +1093,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const options = ContextMenu.Instance.findByDescription("Options..."); const optionItems: ContextMenuProps[] = options && "subitems" in options ? options.subitems : []; + this._timelineRef.current!.timelineContextMenu(e); optionItems.push({ description: "reset view", event: () => { this.props.Document._panX = this.props.Document._panY = 0; this.props.Document.scale = 1; }, icon: "compress-arrows-alt" }); optionItems.push({ description: `${this.Document._LODdisable ? "Enable LOD" : "Disable LOD"}`, event: () => this.Document._LODdisable = !this.Document._LODdisable, icon: "table" }); optionItems.push({ description: `${this.fitToContent ? "Unset" : "Set"} Fit To Container`, event: () => this.Document._fitToBox = !this.fitToContent, icon: !this.fitToContent ? "expand-arrows-alt" : "compress-arrows-alt" }); @@ -1083,7 +1101,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P this.props.ContainingCollectionView && optionItems.push({ description: "Promote Collection", event: this.promoteCollection, icon: "table" }); optionItems.push({ description: "Arrange contents in grid", event: this.layoutDocsInGrid, icon: "table" }); // layoutItems.push({ description: "Analyze Strokes", event: this.analyzeStrokes, icon: "paint-brush" }); - optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document.jitterRotation = (this.props.Document.jitterRotation ? 0 : 10)), icon: "paint-brush" }); + optionItems.push({ description: "Jitter Rotation", event: action(() => this.props.Document._jitterRotation = (this.props.Document._jitterRotation ? 0 : 10)), icon: "paint-brush" }); optionItems.push({ description: "Import document", icon: "upload", event: ({ x, y }) => { const input = document.createElement("input"); @@ -1111,8 +1129,8 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P input.click(); } }); - - ContextMenu.Instance.addItem({ description: "Options...", subitems: optionItems, icon: "eye" }); + ContextMenu.Instance.addItem({ description: "Options ...", subitems: optionItems, icon: "eye" }); + this._timelineRef.current!.timelineContextMenu(e); } private childViews = () => { @@ -1150,9 +1168,10 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P return <MarqueeView {...this.props} nudge={this.nudge} activeDocuments={this.getActiveDocuments} selectDocuments={this.selectDocuments} addDocument={this.addDocument} addLiveTextDocument={this.addLiveTextBox} getContainerTransform={this.getContainerTransform} getTransform={this.getTransform} isAnnotationOverlay={this.isAnnotationOverlay}> <CollectionFreeFormViewPannableContents centeringShiftX={this.centeringShiftX} centeringShiftY={this.centeringShiftY} shifted={!this.nativeHeight && !this.isAnnotationOverlay} - easing={this.easing} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> + easing={this.easing} viewDefDivClick={this.props.viewDefDivClick} zoomScaling={this.zoomScaling} panX={this.panX} panY={this.panY}> {this.children} </CollectionFreeFormViewPannableContents> + <Timeline ref={this._timelineRef} {...this.props} /> </MarqueeView>; } @@ -1164,6 +1183,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P const wscale = nw ? this.props.PanelWidth() / nw : 1; return wscale < hscale ? wscale : hscale; } + @computed get backgroundEvents() { return this.layoutDoc.isBackground && SelectionManager.GetIsDragging(); } render() { TraceMobx(); const clientRect = this._mainCont?.getBoundingClientRect(); @@ -1179,7 +1199,7 @@ export class CollectionFreeFormView extends CollectionSubView<PanZoomDocument, P onWheel={this.onPointerWheel} onClick={this.onClick} //pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, onPointerDown={this.onPointerDown} onPointerMove={this.onCursorMove} onDrop={this.onExternalDrop.bind(this)} onContextMenu={this.onContextMenu} style={{ - pointerEvents: SelectionManager.GetIsDragging() ? "all" : undefined, + pointerEvents: this.backgroundEvents ? "all" : undefined, transform: this.contentScaling ? `scale(${this.contentScaling})` : "", transformOrigin: this.contentScaling ? "left top" : "", width: this.contentScaling ? `${100 / this.contentScaling}%` : "", @@ -1223,6 +1243,7 @@ interface CollectionFreeFormViewPannableContentsProps { panY: () => number; zoomScaling: () => number; easing: () => boolean; + viewDefDivClick?: ScriptField; children: () => JSX.Element[]; shifted: boolean; } @@ -1230,7 +1251,7 @@ interface CollectionFreeFormViewPannableContentsProps { @observer class CollectionFreeFormViewPannableContents extends React.Component<CollectionFreeFormViewPannableContentsProps>{ render() { - const freeformclass = "collectionfreeformview" + (this.props.easing() ? "-ease" : "-none"); + const freeformclass = "collectionfreeformview" + (this.props.viewDefDivClick ? "-viewDef" : (this.props.easing() ? "-ease" : "-none")); const cenx = this.props.centeringShiftX(); const ceny = this.props.centeringShiftY(); const panx = -this.props.panX(); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index ab8174ba1..c70301b2f 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -1,12 +1,12 @@ import { action, computed, observable } from "mobx"; import { observer } from "mobx-react"; -import { Doc, DocListCast, DataSym, WidthSym, HeightSym } from "../../../../new_fields/Doc"; +import { Doc, DocListCast, DataSym, WidthSym, HeightSym, Opt } from "../../../../new_fields/Doc"; import { InkField, InkData } from "../../../../new_fields/InkField"; import { List } from "../../../../new_fields/List"; import { SchemaHeaderField } from "../../../../new_fields/SchemaHeaderField"; import { Cast, NumCast, FieldValue, StrCast } from "../../../../new_fields/Types"; import { Utils } from "../../../../Utils"; -import { Docs, DocUtils } from "../../../documents/Documents"; +import { Docs, DocUtils, DocumentOptions } from "../../../documents/Documents"; import { SelectionManager } from "../../../util/SelectionManager"; import { Transform } from "../../../util/Transform"; import { undoBatch } from "../../../util/UndoManager"; @@ -19,7 +19,7 @@ import React = require("react"); import { CognitiveServices } from "../../../cognitive_services/CognitiveServices"; import { RichTextField } from "../../../../new_fields/RichTextField"; import { CollectionView } from "../CollectionView"; -import { FormattedTextBox } from "../../nodes/FormattedTextBox"; +import { FormattedTextBox } from "../../nodes/formattedText/FormattedTextBox"; import { ScriptField } from "../../../../new_fields/ScriptField"; interface MarqueeViewProps { @@ -113,7 +113,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque if (template instanceof Doc) { tbox._width = NumCast(template._width); tbox.layoutKey = "layout_" + StrCast(template.title); - tbox[StrCast(tbox.layoutKey)] = template; + Doc.GetProto(tbox)[StrCast(tbox.layoutKey)] = template; } this.props.addLiveTextDocument(tbox); } @@ -310,11 +310,10 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque this.hideMarquee(); } - getCollection = (selected: Doc[], asTemplate: boolean, isBackground?: boolean) => { + getCollection = (selected: Doc[], creator: Opt<(documents: Array<Doc>, options: DocumentOptions, id?: string) => Doc>, isBackground?: boolean) => { const bounds = this.Bounds; // const inkData = this.ink ? this.ink.inkData : undefined; - const creator = asTemplate ? Docs.Create.StackingDocument : Docs.Create.FreeformDocument; - const newCollection = creator(selected, { + const newCollection = (creator || Docs.Create.FreeformDocument)(selected, { x: bounds.left, y: bounds.top, _panX: 0, @@ -338,8 +337,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque const selected = this.marqueeSelect(false); SelectionManager.DeselectAll(); selected.forEach(d => this.props.removeDocument(d)); - const newCollection = this.getCollection(selected, false); - Doc.pileup(newCollection, selected); + const newCollection = Doc.pileup(selected, this.Bounds.left + this.Bounds.width / 2, this.Bounds.top + this.Bounds.height / 2); this.props.addDocument(newCollection); this.props.selectDocuments([newCollection], []); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -359,7 +357,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque return d; }); } - const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t"); + const newCollection = this.getCollection(selected, (e as KeyboardEvent)?.key === "t" ? Docs.Create.StackingDocument : undefined); this.props.addDocument(newCollection); this.props.selectDocuments([newCollection], []); MarqueeOptionsMenu.Instance.fadeOut(true); @@ -470,7 +468,7 @@ export class MarqueeView extends React.Component<SubCollectionViewProps & Marque } @action background = (e: KeyboardEvent | React.PointerEvent | undefined) => { - const newCollection = this.getCollection([], false, true); + const newCollection = this.getCollection([], undefined, true); this.props.addDocument(newCollection); MarqueeOptionsMenu.Instance.fadeOut(true); this.hideMarquee(); diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 0e1cc2010..9d09ecc3b 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -5,7 +5,7 @@ import { Doc } from '../../../../new_fields/Doc'; import { documentSchema } from '../../../../new_fields/documentSchemas'; import { makeInterface } from '../../../../new_fields/Schema'; import { BoolCast, NumCast, ScriptCast, StrCast, Cast } from '../../../../new_fields/Types'; -import { DragManager } from '../../../util/DragManager'; +import { DragManager, dropActionType } from '../../../util/DragManager'; import { Transform } from '../../../util/Transform'; import { undoBatch } from '../../../util/UndoManager'; import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView'; @@ -214,21 +214,32 @@ export class CollectionMulticolumnView extends CollectionSubView(MulticolumnDocu } getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return <ContentFittingDocumentView - {...this.props} Document={layout} DataDocument={layout.resolvedDataDoc as Doc} - NativeHeight={returnZero} - NativeWidth={returnZero} - addDocTab={this.addDocTab} - fitToBox={BoolCast(this.props.Document._freezeChildDimensions)} - FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)} backgroundColor={this.props.backgroundColor} - CollectionDoc={this.props.Document} + LayoutDoc={this.props.childLayoutTemplate} + LibraryPath={this.props.LibraryPath} + FreezeDimensions={this.props.freezeChildDimensions} + renderDepth={this.props.renderDepth + 1} PanelWidth={width} PanelHeight={height} - getTransform={dxf} + NativeHeight={returnZero} + NativeWidth={returnZero} + fitToBox={BoolCast(this.props.Document._freezeChildDimensions)} + rootSelected={this.rootSelected} + dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} onClick={this.onChildClickHandler} - renderDepth={this.props.renderDepth + 1} + getTransform={dxf} + focus={this.props.focus} + CollectionDoc={this.props.CollectionView?.props.Document} + CollectionView={this.props.CollectionView} + addDocument={this.props.addDocument} + moveDocument={this.props.moveDocument} + removeDocument={this.props.removeDocument} + active={this.props.active} + whenActiveChanged={this.props.whenActiveChanged} + addDocTab={this.addDocTab} + pinToPres={this.props.pinToPres} />; } /** diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index 1eb486c4f..af0cc3b5c 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -13,7 +13,7 @@ import { Transform } from '../../../util/Transform'; import HeightLabel from './MultirowHeightLabel'; import ResizeBar from './MultirowResizer'; import { undoBatch } from '../../../util/UndoManager'; -import { DragManager } from '../../../util/DragManager'; +import { DragManager, dropActionType } from '../../../util/DragManager'; import { List } from '../../../../new_fields/List'; type MultirowDocument = makeInterface<[typeof documentSchema]>; @@ -214,21 +214,32 @@ export class CollectionMultirowView extends CollectionSubView(MultirowDocument) } getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { return <ContentFittingDocumentView - {...this.props} Document={layout} DataDocument={layout.resolvedDataDoc as Doc} - NativeHeight={returnZero} - NativeWidth={returnZero} - addDocTab={this.addDocTab} - fitToBox={BoolCast(this.props.Document._freezeChildDimensions)} - FreezeDimensions={BoolCast(this.props.Document._freezeChildDimensions)} backgroundColor={this.props.backgroundColor} - CollectionDoc={this.props.Document} + LayoutDoc={this.props.childLayoutTemplate} + LibraryPath={this.props.LibraryPath} + FreezeDimensions={this.props.freezeChildDimensions} + renderDepth={this.props.renderDepth + 1} PanelWidth={width} PanelHeight={height} - getTransform={dxf} + NativeHeight={returnZero} + NativeWidth={returnZero} + fitToBox={BoolCast(this.props.Document._freezeChildDimensions)} + rootSelected={this.rootSelected} + dropAction={StrCast(this.props.Document.childDropAction) as dropActionType} onClick={this.onChildClickHandler} - renderDepth={this.props.renderDepth + 1} + getTransform={dxf} + focus={this.props.focus} + CollectionDoc={this.props.CollectionView?.props.Document} + CollectionView={this.props.CollectionView} + addDocument={this.props.addDocument} + moveDocument={this.props.moveDocument} + removeDocument={this.props.removeDocument} + active={this.props.active} + whenActiveChanged={this.props.whenActiveChanged} + addDocTab={this.addDocTab} + pinToPres={this.props.pinToPres} />; } /** |
