diff options
Diffstat (limited to 'src')
8 files changed, 630 insertions, 29 deletions
diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index fecba32c5..bcb190de4 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -43,6 +43,7 @@ import { ScriptField, ComputedField } from '../../../fields/ScriptField'; import { InteractionUtils } from '../../util/InteractionUtils'; import { ObjectField } from '../../../fields/ObjectField'; import CollectionMapView from './CollectionMapView'; +import { CollectionGridView } from './collectionGrid/CollectionGridView'; import { CollectionPileView } from './CollectionPileView'; const higflyout = require("@hig/flyout"); export const { anchorPoints } = higflyout; @@ -66,6 +67,7 @@ export enum CollectionViewType { Linear = "linear", Staff = "staff", Map = "map", + Grid = "grid", Pile = "pileup" } export interface CollectionViewCustomProps { @@ -90,7 +92,7 @@ export interface CollectionRenderProps { export class CollectionView extends Touchable<FieldViewProps & CollectionViewCustomProps> { public static LayoutString(fieldStr: string) { return FieldView.LayoutString(CollectionView, fieldStr); } - private _isChildActive = false; //TODO should this be observable? + _isChildActive = false; //TODO should this be observable? get _isLightboxOpen() { return BoolCast(this.props.Document.isLightboxOpen); } set _isLightboxOpen(value) { this.props.Document.isLightboxOpen = value; } @observable private _curLightboxImg = 0; @@ -192,6 +194,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus case CollectionViewType.Masonry: { this.props.Document.singleColumn = false; return (<CollectionStackingView key="collview" {...props} />); } case CollectionViewType.Time: { return (<CollectionTimeView key="collview" {...props} />); } case CollectionViewType.Map: return (<CollectionMapView key="collview" {...props} />); + case CollectionViewType.Grid: return (<CollectionGridView key="gridview" {...props} />); case CollectionViewType.Freeform: default: { this.props.Document._freeformLayoutEngine = undefined; return (<CollectionFreeFormView key="collview" {...props} />); } } @@ -229,6 +232,7 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus subItems.push({ description: "Carousel", event: () => func(CollectionViewType.Carousel), icon: "columns" }); subItems.push({ description: "Pivot/Time", event: () => func(CollectionViewType.Time), icon: "columns" }); subItems.push({ description: "Map", event: () => func(CollectionViewType.Map), icon: "globe-americas" }); + subItems.push({ description: "Grid", event: () => func(CollectionViewType.Grid), icon: "border-all" }); if (addExtras && this.props.Document._viewType === CollectionViewType.Freeform) { subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) }); } @@ -238,6 +242,37 @@ export class CollectionView extends Touchable<FieldViewProps & CollectionViewCus onContextMenu = (e: React.MouseEvent): void => { 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 + const existingVm = ContextMenu.Instance.findByDescription("View Modes..."); + const subItems = existingVm && "subitems" in existingVm ? existingVm.subitems : []; + subItems.push({ description: "Freeform", event: () => { this.props.Document._viewType = CollectionViewType.Freeform; }, icon: "signature" }); + if (CollectionView._safeMode) { + ContextMenu.Instance.addItem({ description: "Test Freeform", event: () => this.props.Document._viewType = CollectionViewType.Invalid, icon: "project-diagram" }); + } + subItems.push({ description: "Schema", event: () => this.props.Document._viewType = CollectionViewType.Schema, icon: "th-list" }); + subItems.push({ description: "Treeview", event: () => this.props.Document._viewType = CollectionViewType.Tree, icon: "tree" }); + subItems.push({ description: "Stacking", event: () => this.props.Document._viewType = CollectionViewType.Stacking, icon: "ellipsis-v" }); + subItems.push({ + description: "Stacking (AutoHeight)", event: () => { + this.props.Document._viewType = CollectionViewType.Stacking; + this.props.Document._autoHeight = true; + }, icon: "ellipsis-v" + }); + subItems.push({ description: "Staff", event: () => this.props.Document._viewType = CollectionViewType.Staff, icon: "music" }); + subItems.push({ description: "Multicolumn", event: () => this.props.Document._viewType = CollectionViewType.Multicolumn, icon: "columns" }); + subItems.push({ description: "Multirow", event: () => this.props.Document._viewType = CollectionViewType.Multirow, icon: "columns" }); + subItems.push({ description: "Masonry", event: () => this.props.Document._viewType = CollectionViewType.Masonry, icon: "columns" }); + subItems.push({ description: "Carousel", event: () => this.props.Document._viewType = CollectionViewType.Carousel, icon: "columns" }); + subItems.push({ description: "Pivot/Time", event: () => this.props.Document._viewType = CollectionViewType.Time, icon: "columns" }); + subItems.push({ description: "Map", event: () => this.props.Document._viewType = CollectionViewType.Map, icon: "globe-americas" }); + subItems.push({ description: "Grid", event: () => this.props.Document._viewType = CollectionViewType.Grid, icon: "rainbow" }); + switch (this.props.Document._viewType) { + case CollectionViewType.Freeform: { + subItems.push({ description: "Custom", icon: "fingerprint", event: AddCustomFreeFormLayout(this.props.Document, this.props.fieldKey) }); + break; + } + } + subItems.push({ description: "lightbox", event: action(() => this._isLightboxOpen = true), icon: "eye" }); + !existingVm && ContextMenu.Instance.addItem({ description: "View Modes...", subitems: subItems, icon: "eye" }); this.setupViewTypes("Add a Perspective...", vtype => { const newRendition = Doc.MakeAlias(this.props.Document); diff --git a/src/client/views/collections/CollectionViewChromes.scss b/src/client/views/collections/CollectionViewChromes.scss index 03bd9a01a..42ec35853 100644 --- a/src/client/views/collections/CollectionViewChromes.scss +++ b/src/client/views/collections/CollectionViewChromes.scss @@ -3,7 +3,7 @@ .collectionViewChrome-cont { position: absolute; - width:100%; + width: 100%; opacity: 0.9; z-index: 9001; transition: top .5s; @@ -13,7 +13,7 @@ .collectionViewChrome { display: flex; padding-bottom: 1px; - height:32px; + height: 32px; border-bottom: .5px solid rgb(180, 180, 180); overflow: hidden; @@ -35,7 +35,7 @@ outline-color: black; } - .collectionViewBaseChrome-button{ + .collectionViewBaseChrome-button { font-size: 75%; text-transform: uppercase; letter-spacing: 2px; @@ -46,6 +46,7 @@ padding: 12px 10px 11px 10px; margin-left: 10px; } + .collectionViewBaseChrome-cmdPicker { margin-left: 3px; margin-right: 0px; @@ -54,15 +55,17 @@ border: none; color: grey; } + .commandEntry-outerDiv { pointer-events: all; background-color: gray; display: flex; flex-direction: row; - height:30px; + height: 30px; + .commandEntry-drop { - color:white; - width:25px; + color: white; + width: 25px; margin-top: auto; margin-bottom: auto; } @@ -76,15 +79,17 @@ pointer-events: all; // margin-top: 10px; } + .collectionViewBaseChrome-template, .collectionViewBaseChrome-viewModes { display: grid; background: rgb(238, 238, 238); - color:grey; - margin-top:auto; - margin-bottom:auto; + color: grey; + margin-top: auto; + margin-bottom: auto; margin-left: 5px; } + .collectionViewBaseChrome-viewModes { margin-left: 25px; } @@ -92,7 +97,7 @@ .collectionViewBaseChrome-viewSpecs { margin-left: 5px; display: grid; - + .collectionViewBaseChrome-filterIcon { position: relative; display: flex; @@ -163,13 +168,31 @@ } } - .collectionStackingViewChrome-cont, .collectionTreeViewChrome-cont { display: flex; justify-content: space-between; } + .collectionGridViewChrome-cont { + display: flex; + margin-left: 10; + + .grid-control { + align-self: center; + width: 30%; + + .grid-icon { + margin-right: 5px; + } + } + + .collectionGridViewChrome-entryBox { + width: 50%; + } + } + + .collectionStackingViewChrome-sort, .collectionTreeViewChrome-sort { display: flex; @@ -199,13 +222,13 @@ .collectionTreeViewChrome-pivotField-label { vertical-align: center; padding-left: 10px; - margin:auto; + margin: auto; } .collectionStackingViewChrome-pivotField, .collectionTreeViewChrome-pivotField { color: white; - width:100%; + width: 100%; min-width: 100px; display: flex; align-items: center; @@ -215,7 +238,7 @@ input, .editableView-container-editing-oneLine, .editableView-container-editing { - margin:auto; + margin: auto; border: 0px; color: grey; text-align: center; @@ -236,6 +259,7 @@ .collectionTreeViewChrome-pivotField:hover { cursor: text; } + } } @@ -244,7 +268,10 @@ display: flex; position: relative; align-items: center; - .fwdKeyframe, .numKeyframe, .backKeyframe { + + .fwdKeyframe, + .numKeyframe, + .backKeyframe { cursor: pointer; position: absolute; width: 20; @@ -253,26 +280,31 @@ background: gray; display: flex; align-items: center; - color:white; + color: white; } + .backKeyframe { - left:0; + left: 0; + svg { - display:block; - margin:auto; + display: block; + margin: auto; } } + .numKeyframe { - left:20; + left: 20; display: flex; flex-direction: column; padding: 5px; } + .fwdKeyframe { - left:40; + left: 40; + svg { - display:block; - margin:auto; + display: block; + margin: auto; } } } @@ -334,8 +366,9 @@ flex-direction: column; height: 40px; } + .commandEntry-inputArea { - display:flex; + display: flex; flex-direction: row; width: 150px; margin: auto auto auto auto; diff --git a/src/client/views/collections/CollectionViewChromes.tsx b/src/client/views/collections/CollectionViewChromes.tsx index 29a3e559a..9b8a24d4a 100644 --- a/src/client/views/collections/CollectionViewChromes.tsx +++ b/src/client/views/collections/CollectionViewChromes.tsx @@ -2,7 +2,7 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { action, computed, observable, runInAction } from "mobx"; import { observer } from "mobx-react"; import * as React from "react"; -import { Doc, DocListCast } from "../../../fields/Doc"; +import { Doc, DocListCast, HeightSym } from "../../../fields/Doc"; import { Id } from "../../../fields/FieldSymbols"; import { List } from "../../../fields/List"; import { listSpec } from "../../../fields/Schema"; @@ -201,6 +201,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewChro case CollectionViewType.Schema: return (<CollectionSchemaViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />); case CollectionViewType.Tree: return (<CollectionTreeViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />); case CollectionViewType.Masonry: return (<CollectionStackingViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />); + case CollectionViewType.Grid: return (<CollectionGridViewChrome key="collchrome" PanelWidth={this.props.PanelWidth} CollectionView={this.props.CollectionView} type={this.props.type} />); default: return null; } } @@ -562,3 +563,69 @@ export class CollectionTreeViewChrome extends React.Component<CollectionViewChro } } +/** + * Chrome for grid view. + */ +@observer +export class CollectionGridViewChrome extends React.Component<CollectionViewChromeProps> { + + /** + * Sets the value of `numCols` on the grid's Document to the value entered. + */ + @undoBatch + onNumColsEnter = (e: React.KeyboardEvent<HTMLInputElement>) => { + if (e.key === "Enter" || e.key === "Tab") { + if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.numCols as number !== e.currentTarget.valueAsNumber) { + this.props.CollectionView.props.Document.numCols = e.currentTarget.valueAsNumber; + //this.props.CollectionView.forceUpdate(); // to be used if CollectionGridView is not an observer + } + + } + } + + /** + * Sets the value of `rowHeight` on the grid's Document to the value entered. + */ + @undoBatch + onRowHeightEnter = (e: React.KeyboardEvent<HTMLInputElement>) => { + if (e.key === "Enter" || e.key === "Tab") { + if (e.currentTarget.valueAsNumber > 0 && this.props.CollectionView.props.Document.rowHeight as number !== e.currentTarget.valueAsNumber) { + this.props.CollectionView.props.Document.rowHeight = e.currentTarget.valueAsNumber; + } + } + } + + /** + * Sets whether the grid is flexible or not on the grid's Document. + */ + @undoBatch + toggleFlex = () => { + this.props.CollectionView.props.Document.flexGrid = !this.props.CollectionView.props.Document.flexGrid; + } + + render() { + return ( + <div className="collectionGridViewChrome-cont"> + <span className={"grid-control"}> + <span className="grid-icon"> + <FontAwesomeIcon icon="columns" size="1x" /> + </span> + <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.numCols as string} onKeyDown={this.onNumColsEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => e.currentTarget.focus()} /> + </span> + <span className={"grid-control"}> + <span className="grid-icon"> + <FontAwesomeIcon icon="text-height" size="1x" /> + </span> + <input className="collectionGridViewChrome-entryBox" type="number" placeholder={this.props.CollectionView.props.Document.rowHeight as string} onKeyDown={this.onRowHeightEnter} onClick={(e: React.MouseEvent<HTMLInputElement, MouseEvent>) => e.currentTarget.focus()} /> + </span> + <span className={"grid-control"}> + <input style={{ marginRight: 5 }} type="checkbox" onClick={this.toggleFlex} defaultChecked={this.props.CollectionView.props.Document.flexGrid as boolean} /> + <span className="grid-icon"> + <FontAwesomeIcon icon="arrow-up" size="1x" /> + </span> + <label>Flexible</label> + </span> + </div> + ); + } +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss new file mode 100644 index 000000000..fb30fe4c1 --- /dev/null +++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss @@ -0,0 +1,39 @@ +.collectionGridView-contents { + display: flex; + overflow: hidden; + width: 100%; + height: 100%; + flex-direction: column; + +} + +// .documentDecorations-container .documentDecorations-resizer { +// pointer-events: none; +// } + +// #documentDecorations-bottomRightResizer, +// #documentDecorations-bottomLeftResizer, +// #documentDecorations-topRightResizer, +// #documentDecorations-topLeftResizer { +// visibility: collapse; +// } + +.collectionGridView-contents .collectionGridView-gridContainer { + height: 100%; + overflow-y: auto; + background-color: white; +} + + + +/* Chrome, Safari, Edge, Opera */ +input::-webkit-outer-spin-button, +input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +/* Firefox */ +input[type=number] { + -moz-appearance: textfield; +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx new file mode 100644 index 000000000..dd355ef47 --- /dev/null +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -0,0 +1,326 @@ +import { computed, observable, action } from 'mobx'; +import * as React from "react"; +import { Doc, DocListCast } from '../../../../fields/Doc'; +import { documentSchema } from '../../../../fields/documentSchemas'; +import { makeInterface } from '../../../../fields/Schema'; +import { BoolCast, NumCast, ScriptCast } from '../../../../fields/Types'; +import { Transform } from '../../../util/Transform'; +import { undoBatch } from '../../../util/UndoManager'; +import { ContentFittingDocumentView } from '../../nodes/ContentFittingDocumentView'; +import { CollectionSubView } from '../CollectionSubView'; +import { SubCollectionViewProps } from '../CollectionSubView'; +import { List } from '../../../../fields/List'; +import { returnZero } from '../../../../Utils'; +import Grid, { Layout } from "./Grid"; +import { Id } from '../../../../fields/FieldSymbols'; +import { observer } from 'mobx-react'; +import "./CollectionGridView.scss"; +import { SnappingManager } from '../../../util/SnappingManager'; + + +type GridSchema = makeInterface<[typeof documentSchema]>; +const GridSchema = makeInterface(documentSchema); + +@observer +export class CollectionGridView extends CollectionSubView(GridSchema) { + private containerRef: React.RefObject<HTMLDivElement>; + @observable private _scroll: number = 0; + + constructor(props: Readonly<SubCollectionViewProps>) { + super(props); + + this.props.Document.numCols = this.props.Document.numCols ? this.props.Document.numCols : 10; + this.props.Document.rowHeight = this.props.Document.rowHeight ? this.props.Document.rowHeight : 100; + this.props.Document.flexGrid = (this.props.Document.flexGrid !== undefined) ? this.props.Document.flexGrid : true; + + this.setLayout = this.setLayout.bind(this); + this.deleteInContext = this.deleteInContext.bind(this); + + this.containerRef = React.createRef(); + } + + componentDidMount() { + if (!(this.props.Document.gridLayouts as List<Doc>)?.length) { + + console.log("no layouts stored on doc"); + + this.props.Document.gridLayouts = new List<Doc>(); + + for (let i = 0; i < this.childLayoutPairs.length; i++) { + + const layoutDoc: Doc = new Doc(); + layoutDoc.i = this.childLayoutPairs[i].layout[Id]; + layoutDoc.x = 2 * (i % Math.floor(this.props.Document.numCols as number / 2)); + layoutDoc.y = 2 * Math.floor(i / Math.floor(this.props.Document.numCols as number / 2)); + layoutDoc.w = 2; + layoutDoc.h = 2; + + (this.props.Document.gridLayouts as List<Doc>).push(layoutDoc); + + // use childlayoutpairs length instead + } + + } + } + + /** + * @returns the transform that will correctly place + * the document decorations box, shifted to the right by + * the sum of all the resolved column widths of the + * documents before the target. + */ + private lookupIndividualTransform = (doc: Doc) => { + const yTranslation = this.rowHeightPlusGap * NumCast(doc.y) + 10 - this._scroll; + const xTranslation = this.colWidthPlusGap * NumCast(doc.x) + 10; + return this.props.ScreenToLocalTransform().translate(-xTranslation, -yTranslation); + } + + @computed get colWidthPlusGap() { return (this.props.PanelWidth() - 10) / NumCast(this.props.Document.numCols); } + @computed get rowHeightPlusGap() { return NumCast(this.props.Document.rowHeight) + 10; } + + @computed get onChildClickHandler() { return ScriptCast(this.Document.onChildClick); } + + /** + * Sets the width of the decorating box. + * @param Doc doc + */ + @observable private width = (doc: Doc) => NumCast(doc.w) * this.colWidthPlusGap - 10; + + /** + * Sets the height of the decorating box. + * @param doc `Doc` + */ + @observable private height = (doc: Doc) => NumCast(doc.h) * this.rowHeightPlusGap - 10; + + addDocTab = (doc: Doc, where: string) => { + if (where === "inPlace" && this.layoutDoc.isInPlaceContainer) { + this.dataDoc[this.props.fieldKey] = new List<Doc>([doc]); + return true; + } + return this.props.addDocTab(doc, where); + } + + getDisplayDoc(layout: Doc, dxf: () => Transform, width: () => number, height: () => number) { + console.log(layout[Id]); + return <ContentFittingDocumentView + {...this.props} + Document={layout} + DataDoc={layout.resolvedDataDoc as Doc} + NativeHeight={returnZero} + NativeWidth={returnZero} + addDocTab={this.addDocTab} + backgroundColor={this.props.backgroundColor} + ContainingCollectionDoc={this.props.Document} + PanelWidth={width} + PanelHeight={height} + ScreenToLocalTransform={dxf} + onClick={this.onChildClickHandler} + renderDepth={this.props.renderDepth + 1} + parentActive={this.props.active} + display={"contents"} // this causes an issue- this is the reason the decorations box is weird with images and web boxes + removeDocument={this.deleteInContext} + />; + } + + @undoBatch + deleteInContext(doc: Doc | Doc[]): boolean { + + if (!(this.props.Document.flexGrid as boolean)) { + this.props.removeDocument(doc); + } + else { + const docList: Doc[] = DocListCast(this.props.Document.gridLayouts); + const newDocList: Doc[] = []; + if (doc instanceof Doc) { + for (const savedDoc of docList) { + if (savedDoc.i !== doc[Id]) { + console.log("compare"); + console.log(savedDoc.i); + console.log(doc[Id]); + newDocList.push(savedDoc); + } + } + this.props.Document.gridLayouts = new List<Doc>(newDocList); + this.props.removeDocument(doc); + } + // else { + // console.log("doc is list"); + // this.props.removeDocument(doc); + // } + } + console.log("here???? in deletei n conte"); + return true; + } + + + /** + * Saves the layouts received from the Grid to the Document. + * @param layouts `Layout[]` + */ + @undoBatch + setLayout(layouts: Layout[]) { + + if (this.props.Document.flexGrid) { + + const docList: Doc[] = []; + for (const layout of layouts) { + + const layoutDoc = new Doc(); + layoutDoc.i = layout.i; + layoutDoc.x = layout.x; + layoutDoc.y = layout.y; + layoutDoc.w = layout.w; + layoutDoc.h = layout.h; + + docList.push(layoutDoc); + } + + this.props.Document.gridLayouts = new List<Doc>(docList); + } + } + + /** + * @returns a list of `ContentFittingDocumentView`s inside wrapper divs. + * The key of the wrapper div must be the same as the `i` value of the corresponding layout. + */ + @computed + private get contents(): JSX.Element[] { + const { childLayoutPairs } = this; + const collector: JSX.Element[] = []; + //const layoutArray: Layout[] = []; + + const docList: Doc[] = DocListCast(this.props.Document.gridLayouts); + + const previousLength = docList.length; + // layoutArray.push(...this.layout); + + if (!previousLength) { + // console.log("early return"); + return []; + } + + for (let i = 0; i < childLayoutPairs.length; i++) { + const { layout } = childLayoutPairs[i]; + const dxf = () => this.lookupIndividualTransform(docList[i]); + const width = () => this.width(docList[i]); + const height = () => this.height(docList[i]); + collector.push( + <div className={"document-wrapper"} + key={docList?.[i].i as string} + id={docList?.[i].i as string} + > + {this.getDisplayDoc(layout, dxf, width, height)} + </div> + ); + } + + return collector; + } + + /** + * @returns a list of Layouts from a list of Docs + * @param docLayoutList `Doc[]` + */ + toLayoutList(docLayoutList: Doc[]): Layout[] { + + const layouts: Layout[] = []; + + if (this.props.Document.flexGrid) { + for (const layout of docLayoutList) { + layouts.push( + { i: layout.i as string, x: layout.x as number, y: layout.y as number, w: layout.w as number, h: layout.h as number, static: !(this.props.Document.flexGrid as boolean) } + ); + } + } + else { + for (let i = 0; i < docLayoutList.length; i++) { + layouts.push( + { i: docLayoutList[i].i as string, x: 2 * (i % Math.floor(this.props.Document.numCols as number / 2)), y: 2 * Math.floor(i / Math.floor(this.props.Document.numCols as number / 2)), w: 2, h: 2, static: true } + ); + } + } + return layouts; + } + + /** + * Checks whether a new node has been added to the grid and updates the Document accordingly. + */ + @undoBatch + checkUpdate() { + const previousLength = (this.props.Document.gridLayouts as List<Doc>)?.length; + if (this.childLayoutPairs.length > previousLength) { + console.log("adding doc"); + const layoutDoc: Doc = new Doc(); + layoutDoc.i = this.childLayoutPairs[this.childLayoutPairs.length - 1].layout[Id]; + layoutDoc.x = 2 * (previousLength % Math.floor(this.props.Document.numCols as number / 2)); + layoutDoc.y = 2 * Math.floor(previousLength / Math.floor(this.props.Document.numCols as number / 2)); + layoutDoc.w = 2; + layoutDoc.h = 2; + + (this.props.Document.gridLayouts as List<Doc>).push(layoutDoc); + } + } + + render(): JSX.Element { + + this.checkUpdate(); + + //console.log("here first?"); + + const docList: Doc[] = DocListCast(this.props.Document.gridLayouts); + + //console.log("doclist length:::" + docList.length); + const contents: JSX.Element[] = this.contents; + const layout: Layout[] = this.toLayoutList(docList); + + // for (const doc of docList) { + // console.log(doc.i); + // } + + // if (layout.length === 0) { + // console.log("layouts not loaded"); + // } + // else { + // console.log("rendering with this"); + // console.log(layout[0].w); + // } + + console.log(this.props.Document.title + " " + this.props.isSelected() + " " + (!this.props.isSelected() && this.props.renderDepth !== 0 && !this.props.ContainingCollectionView?._isChildActive && !SnappingManager.GetIsDragging() ? "none" : undefined)); + return ( + <div className="collectionGridView-contents" + style={{ + marginLeft: NumCast(this.props.Document._xMargin), marginRight: NumCast(this.props.Document._xMargin), + marginTop: NumCast(this.props.Document._yMargin), marginBottom: NumCast(this.props.Document._yMargin), + pointerEvents: !this.props.isSelected() && this.props.renderDepth !== 0 && !this.props.ContainingCollectionView?._isChildActive && !SnappingManager.GetIsDragging() ? "none" : undefined + }} + ref={this.createDashEventsTarget} + onPointerDown={e => { + if (this.props.active(true)) { + if (this.props.isSelected(true)) { + e.stopPropagation(); + } + } + if (this.props.isSelected(true)) { + !((e.target as any)?.className.includes("react-resizable-handle")) && e.preventDefault(); + } + }} // the grid doesn't stopPropagation when its widgets are hit, so we need to otherwise the outer documents will respond + > + <div className="collectionGridView-gridContainer" + ref={this.containerRef} + onScroll={action((e: React.UIEvent<HTMLDivElement>) => this._scroll = e.currentTarget.scrollTop)} + > + <Grid + width={this.props.PanelWidth()} + nodeList={contents} + layout={layout} + childrenDraggable={this.props.isSelected() ? true : false} + numCols={this.props.Document.numCols as number} + rowHeight={this.props.Document.rowHeight as number} + setLayout={this.setLayout} + transformScale={this.props.ScreenToLocalTransform().Scale} + /> + </div> + </div> + ); + } +} diff --git a/src/client/views/collections/collectionGrid/Grid.tsx b/src/client/views/collections/collectionGrid/Grid.tsx new file mode 100644 index 000000000..0e9e8bbca --- /dev/null +++ b/src/client/views/collections/collectionGrid/Grid.tsx @@ -0,0 +1,63 @@ +import * as React from 'react'; +import { observer } from "mobx-react"; + + +import "../../../../../node_modules/react-grid-layout/css/styles.css"; +import "../../../../../node_modules/react-resizable/css/styles.css"; + +import * as GridLayout from 'react-grid-layout'; +import { Layout } from 'react-grid-layout'; +export { Layout } from 'react-grid-layout'; + + +interface GridProps { + width: number; + nodeList: JSX.Element[] | null; + layout: Layout[]; + numCols: number; + rowHeight: number; + setLayout: Function; + transformScale: number; + childrenDraggable: boolean; +} + +/** + * Wrapper around the actual GridLayout of `react-grid-layout`. + */ +@observer +export default class Grid extends React.Component<GridProps> { + + constructor(props: Readonly<GridProps>) { + super(props); + this.onLayoutChange = this.onLayoutChange.bind(this); + } + /** + * If there has been a change in layout, calls a method in CollectionGridView to set the layouts on the Document. + * @param layout `Layout[]` + */ + onLayoutChange(layout: Layout[]) { + this.props.setLayout(layout); + } + + render() { + console.log(this.props.transformScale); + return ( + <GridLayout className="layout" + layout={this.props.layout} + cols={this.props.numCols} + rowHeight={this.props.rowHeight} + width={this.props.width} + compactType={null} + isDroppable={true} + // isDraggable={this.props.childrenDraggable} + // useCSSTransforms={true} + margin={[10, 10]} + onLayoutChange={this.onLayoutChange} + preventCollision={false} // change this to true later + // transformScale={2} // 1.2/scale + > + {this.props.nodeList} + </GridLayout > + ); + } +} diff --git a/src/client/views/nodes/ContentFittingDocumentView.tsx b/src/client/views/nodes/ContentFittingDocumentView.tsx index a90b4668e..77555061f 100644 --- a/src/client/views/nodes/ContentFittingDocumentView.tsx +++ b/src/client/views/nodes/ContentFittingDocumentView.tsx @@ -9,7 +9,42 @@ import { emptyFunction, returnOne } from "../../../Utils"; import '../DocumentDecorations.scss'; import { DocumentView, DocumentViewProps } from "../nodes/DocumentView"; import "./ContentFittingDocumentView.scss"; +import { dropActionType } from "../../util/DragManager"; +import { CollectionView } from "../collections/CollectionView"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { Transform } from "nodemailer/lib/xoauth2"; +interface ContentFittingDocumentViewProps { + Document: Doc; + DataDocument?: Doc; + LayoutDoc?: () => Opt<Doc>; + NativeWidth?: () => number; + NativeHeight?: () => number; + FreezeDimensions?: boolean; + LibraryPath: Doc[]; + renderDepth: number; + fitToBox?: boolean; + layoutKey?: string; + dropAction?: dropActionType; + PanelWidth: () => number; + PanelHeight: () => number; + focus?: (doc: Doc) => void; + CollectionView?: CollectionView; + CollectionDoc?: Doc; + onClick?: ScriptField; + backgroundColor?: (doc: Doc) => string | undefined; + getTransform: () => Transform; + addDocument?: (document: Doc) => boolean; + moveDocument?: (document: Doc, target: Doc | undefined, addDoc: ((doc: Doc) => boolean)) => boolean; + removeDocument?: (document: Doc) => boolean; + active: (outsideReaction: boolean) => boolean; + whenActiveChanged: (isActive: boolean) => void; + addDocTab: (document: Doc, where: string) => boolean; + pinToPres: (document: Doc) => void; + dontRegisterView?: boolean; + rootSelected: (outsideReaction?: boolean) => boolean; + Display?: string; +} @observer export class ContentFittingDocumentView extends React.Component<DocumentViewProps>{ @@ -47,7 +82,8 @@ export class ContentFittingDocumentView extends React.Component<DocumentViewProp TraceMobx(); return (<div className="contentFittingDocumentView" style={{ width: Math.abs(this.centeringYOffset) > 0.001 ? "auto" : this.props.PanelWidth(), - height: Math.abs(this.centeringOffset) > 0.0001 ? "auto" : this.props.PanelHeight() + height: Math.abs(this.centeringOffset) > 0.0001 ? "auto" : this.props.PanelHeight(), + display: this.props.display /* just added for grid */ }}> {!this.props.Document || !this.props.PanelWidth ? (null) : ( <div className="contentFittingDocumentView-previewDoc" diff --git a/src/client/views/nodes/DocumentView.tsx b/src/client/views/nodes/DocumentView.tsx index e245e045c..334033874 100644 --- a/src/client/views/nodes/DocumentView.tsx +++ b/src/client/views/nodes/DocumentView.tsx @@ -96,6 +96,7 @@ export interface DocumentViewProps { dontRegisterView?: boolean; layoutKey?: string; radialMenu?: String[]; + display?: string; } @observer @@ -516,6 +517,7 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu if (!(InteractionUtils.IsType(e, InteractionUtils.MOUSETYPE) || InkingControl.Instance.selectedTool === InkTool.Highlighter || InkingControl.Instance.selectedTool === InkTool.Pen)) { if (!InteractionUtils.IsType(e, InteractionUtils.PENTYPE)) { e.stopPropagation(); + if (SelectionManager.IsSelected(this, true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it // TODO: check here for panning/inking } return; @@ -529,8 +531,8 @@ export class DocumentView extends DocComponent<DocumentViewProps, Document>(Docu !e.ctrlKey && (e.button === 0 || InteractionUtils.IsType(e, InteractionUtils.TOUCHTYPE)) && !this.Document.inOverlay) { - e.stopPropagation(); // events stop at the lowest document that is active. if right dragging, we let it go through though to allow for context menu clicks. PointerMove callbacks should remove themselves if the move event gets stopPropagated by a lower-level handler (e.g, marquee drag); - + e.stopPropagation(); + if (SelectionManager.IsSelected(this, true) && this.props.Document._viewType !== CollectionViewType.Docking) e.preventDefault(); // goldenlayout needs to be able to move its tabs, so can't preventDefault for it } document.removeEventListener("pointermove", this.onPointerMove); document.removeEventListener("pointerup", this.onPointerUp); |