diff options
| author | bob <bcz@cs.brown.edu> | 2019-06-11 10:53:36 -0400 |
|---|---|---|
| committer | bob <bcz@cs.brown.edu> | 2019-06-11 10:53:36 -0400 |
| commit | 95674ee7f68782d1ce85858120efea956825bcb9 (patch) | |
| tree | 0d2ddc9838b45278d6533099f7030a9713d6413f /src/client/views/collections/CollectionStackingView.tsx | |
| parent | 4dacd1220e6a0ef73167f187f52f3b4c222c2586 (diff) | |
| parent | 06d5bb5c65da6f4a115ebf8118989115420bccef (diff) | |
merged embedded linking with master
Diffstat (limited to 'src/client/views/collections/CollectionStackingView.tsx')
| -rw-r--r-- | src/client/views/collections/CollectionStackingView.tsx | 185 |
1 files changed, 185 insertions, 0 deletions
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx new file mode 100644 index 000000000..cad7cd50c --- /dev/null +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -0,0 +1,185 @@ +import React = require("react"); +import { action, computed, IReactionDisposer, reaction, trace } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, HeightSym, WidthSym } from "../../../new_fields/Doc"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { BoolCast, NumCast } from "../../../new_fields/Types"; +import { emptyFunction, returnOne, Utils } from "../../../Utils"; +import { SelectionManager } from "../../util/SelectionManager"; +import { undoBatch } from "../../util/UndoManager"; +import { DocumentView } from "../nodes/DocumentView"; +import { CollectionSchemaPreview } from "./CollectionSchemaView"; +import "./CollectionStackingView.scss"; +import { CollectionSubView } from "./CollectionSubView"; +import { ContextMenu } from "../ContextMenu"; + +@observer +export class CollectionStackingView extends CollectionSubView(doc => doc) { + _masonryGridRef: HTMLDivElement | null = null; + _heightDisposer?: IReactionDisposer; + _gridSize = 1; + @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() - 2 * this.xMargin : Math.min(this.props.PanelWidth() - 2 * this.xMargin, NumCast(this.props.Document.columnWidth, 250)); } + + singleColDocHeight(d: Doc) { + let nw = NumCast(d.nativeWidth); + let nh = NumCast(d.nativeHeight); + let aspect = nw && nh ? nh / nw : 1; + let wid = Math.min(d[WidthSym](), this.columnWidth); + return (nw && nh) ? wid * aspect : d[HeightSym](); + } + componentDidMount() { + this._heightDisposer = reaction(() => [this.yMargin, this.gridGap, this.columnWidth, this.childDocs.map(d => [d.height, d.width, d.zoomBasis, d.nativeHeight, d.nativeWidth, d.isMinimized])], + () => { + if (this.singleColumn) { + let children = this.childDocs.filter(d => !d.isMinimized); + this.props.Document.height = children.reduce((height, d, i) => + height + this.singleColDocHeight(d) + (i === children.length - 1 ? this.yMargin : this.gridGap) + , this.yMargin); + } + }, { fireImmediately: true }); + } + componentWillUnmount() { + if (this._heightDisposer) this._heightDisposer(); + } + + @action + moveDocument = (doc: Doc, targetCollection: Doc, addDocument: (document: Doc) => boolean): boolean => { + this.props.removeDocument(doc); + addDocument(doc); + return true; + } + getDocTransform(doc: Doc, dref: HTMLDivElement) { + let { scale, translateX, translateY } = Utils.GetScreenTransform(dref); + let outerXf = Utils.GetScreenTransform(this._masonryGridRef!); + let offset = this.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); + return this.props.ScreenToLocalTransform().translate(offset[0], offset[1]).scale(NumCast(doc.width, 1) / this.columnWidth); + } + createRef = (ele: HTMLDivElement | null) => { + this._masonryGridRef = ele; + this.createDropTarget(ele!); + } + @undoBatch + @action + public collapseToPoint = (scrpt: number[], expandedDocs: Doc[] | undefined): void => { + SelectionManager.DeselectAll(); + if (expandedDocs) { + let isMinimized: boolean | undefined; + expandedDocs.map(d => Doc.GetProto(d)).map(maximizedDoc => { + if (isMinimized === undefined) { + isMinimized = BoolCast(maximizedDoc.isMinimized, false); + } + maximizedDoc.isMinimized = !isMinimized; + }); + } + } + + @computed + get singleColumnChildren() { + let children = this.childDocs.filter(d => !d.isMinimized); + return children.map((d, i) => { + let dref = React.createRef<HTMLDivElement>(); + let script = undefined; + let colWidth = () => d.nativeWidth ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth; + let rowHeight = () => this.singleColDocHeight(d); + let dxf = () => this.getDocTransform(d, dref.current!).scale(this.columnWidth / d[WidthSym]()); + return <div className="collectionStackingView-masonryDoc" + key={d[Id]} + ref={dref} + style={{ width: colWidth(), height: rowHeight(), marginLeft: "auto", marginRight: "auto" }} > + <CollectionSchemaPreview + Document={d} + width={colWidth} + height={rowHeight} + getTransform={dxf} + CollectionView={this.props.CollectionView} + addDocument={this.props.addDocument} + removeDocument={this.props.removeDocument} + active={this.props.active} + whenActiveChanged={this.props.whenActiveChanged} + addDocTab={this.props.addDocTab} + setPreviewScript={emptyFunction} + previewScript={script}> + </CollectionSchemaPreview> + </div>; + }); + } + @computed + get children() { + return this.childDocs.filter(d => !d.isMinimized).map((d, i) => { + let dref = React.createRef<HTMLDivElement>(); + let dxf = () => this.getDocTransform(d, dref.current!); + let rowSpan = Math.ceil((this.columnWidth / d[WidthSym]() * d[HeightSym]() + this.gridGap) / (this._gridSize + this.gridGap)); + let childFocus = (doc: Doc) => { + doc.libraryBrush = true; + this.props.focus(this.props.Document); // just focus on this collection, not the underlying document because the API doesn't support adding an offset to focus on and we can't pan zoom our contents to be centered. + } + return (<div className="collectionStackingView-masonryDoc" + key={d[Id]} + ref={dref} + style={{ + width: NumCast(d.nativeWidth, d[WidthSym]()), + height: NumCast(d.nativeHeight, d[HeightSym]()), + transformOrigin: "top left", + gridRowEnd: `span ${rowSpan}`, + gridColumnEnd: `span 1`, + transform: `scale(${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())}, ${this.columnWidth / NumCast(d.nativeWidth, d[WidthSym]())})` + }} > + <DocumentView key={d[Id]} Document={d} + addDocument={this.props.addDocument} + removeDocument={this.props.removeDocument} + moveDocument={this.moveDocument} + ContainingCollectionView={this.props.CollectionView} + isTopMost={false} + ScreenToLocalTransform={dxf} + focus={childFocus} + ContentScaling={returnOne} + PanelWidth={d[WidthSym]} + PanelHeight={d[HeightSym]} + selectOnLoad={false} + parentActive={this.props.active} + addDocTab={this.props.addDocTab} + bringToFront={emptyFunction} + whenActiveChanged={this.props.whenActiveChanged} + collapseToPoint={this.collapseToPoint} + /> + </div>); + }) + } + onContextMenu = (e: React.MouseEvent): void => { + if (!e.isPropagationStopped() && this.props.Document[Id] !== "mainDoc") { // need to test this because GoldenLayout causes a parallel hierarchy in the React DOM for its children and the main document view7 + ContextMenu.Instance.addItem({ + description: "Toggle multi-column", + event: () => this.props.Document.singleColumn = !BoolCast(this.props.Document.singleColumn, true), icon: "file-pdf" + }); + } + } + render() { + let cols = this.singleColumn ? 1 : Math.max(1, Math.min(this.childDocs.filter(d => !d.isMinimized).length, + Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); + let templatecols = ""; + for (let i = 0; i < cols; i++) templatecols += `${this.columnWidth}px `; + return ( + <div className="collectionStackingView"> + <div className={`collectionStackingView-masonry${this.singleColumn ? "Single" : "Grid"}`} + onContextMenu={this.onContextMenu} + ref={this.createRef} onWheel={(e: React.WheelEvent) => e.stopPropagation()} + style={{ + padding: this.singleColumn ? `${this.yMargin}px ${this.xMargin}px ${this.yMargin}px ${this.xMargin}px` : `${this.yMargin}px ${this.xMargin}px`, + margin: "auto", + width: this.singleColumn ? undefined : `${cols * (this.columnWidth + this.gridGap) + 2 * this.xMargin - this.gridGap}px`, + height: "100%", + position: "relative", + gridGap: this.gridGap, + gridTemplateColumns: this.singleColumn ? undefined : templatecols, + gridAutoRows: this.singleColumn ? undefined : `${this._gridSize}px` + }} + > + {this.singleColumn ? this.singleColumnChildren : this.children} + </div></div> + ); + } +}
\ No newline at end of file |
