From fb76a0d66fc074a458706adf6fbb02492205b6e4 Mon Sep 17 00:00:00 2001 From: eeng5 Date: Tue, 20 Aug 2019 12:54:44 -0400 Subject: factoring out masonry code into new file --- .../collections/CollectionMasonryViewFieldRow.tsx | 377 +++++++++++++++++++++ .../views/collections/CollectionStackingView.scss | 20 +- .../views/collections/CollectionStackingView.tsx | 104 +++--- .../CollectionStackingViewFieldColumn.tsx | 1 + src/client/views/collections/CollectionSubView.tsx | 4 +- 5 files changed, 462 insertions(+), 44 deletions(-) create mode 100644 src/client/views/collections/CollectionMasonryViewFieldRow.tsx (limited to 'src') diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx new file mode 100644 index 000000000..f0d574132 --- /dev/null +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -0,0 +1,377 @@ +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, observable } from "mobx"; +import { observer } from "mobx-react"; +import { Doc, WidthSym } from "../../../new_fields/Doc"; +import { Id } from "../../../new_fields/FieldSymbols"; +import { PastelSchemaPalette, SchemaHeaderField } from "../../../new_fields/SchemaHeaderField"; +import { ScriptField } from "../../../new_fields/ScriptField"; +import { NumCast, StrCast } from "../../../new_fields/Types"; +import { Utils, numberRange } from "../../../Utils"; +import { Docs } from "../../documents/Documents"; +import { DragManager } from "../../util/DragManager"; +import { CompileScript } from "../../util/Scripting"; +import { SelectionManager } from "../../util/SelectionManager"; +import { Transform } from "../../util/Transform"; +import { undoBatch } from "../../util/UndoManager"; +import { anchorPoints, Flyout } from "../DocumentDecorations"; +import { EditableView } from "../EditableView"; +import { CollectionStackingView } from "./CollectionStackingView"; +import "./CollectionStackingView.scss"; + +library.add(faPalette); + +interface CMVFieldRowProps { + rows: () => number; + headings: () => object[]; + heading: string; + headingObject: SchemaHeaderField | undefined; + docList: Doc[]; + parent: CollectionStackingView; + type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined; + createDropTarget: (ele: HTMLDivElement) => void; + screenToLocalTransform: () => Transform; + color: string | undefined; +} + +@observer +export class CollectionMasonryViewFieldRow extends React.Component { + @observable private _background = "inherit"; + + private _dropRef: HTMLDivElement | null = null; + private dropDisposer?: DragManager.DragDropDisposer; + private _headerRef: React.RefObject = React.createRef(); + private _startDragPosition: { x: number, y: number } = { x: 0, y: 0 }; + private _sensitivity: number = 16; + + @observable _heading = this.props.headingObject ? this.props.headingObject.heading : this.props.heading; + @observable _color = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + + createRowDropRef = (ele: HTMLDivElement | null) => { + this._dropRef = ele; + this.dropDisposer && this.dropDisposer(); + if (ele) { + this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.rowDrop.bind(this) } }); + } + } + + @undoBatch + @action + rowDrop = (e: Event, de: DragManager.DropEvent) => { + if (de.data instanceof DragManager.DocumentDragData) { + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let castedValue = this.getValue(this._heading); + if (castedValue) { + de.data.droppedDocuments.forEach(d => d[key] = castedValue); + } + else { + de.data.droppedDocuments.forEach(d => d[key] = undefined); + } + this.props.parent.drop(e, de); + e.stopPropagation(); + } + } + + masonryChildren(docs: Doc[]) { + let parent = this.props.parent; + parent._docXfs.length = 0; + return docs.map((d, i) => { + let dref = React.createRef(); + let layoutDoc = Doc.expandTemplateLayout(d, parent.props.DataDoc); + let width = () => (d.nativeWidth && !d.ignoreAspect && !parent.props.Document.fillColumn ? Math.min(d[WidthSym](), parent.columnWidth) : parent.columnWidth);/// (uniqueHeadings.length + 1); + let height = () => parent.getDocHeight(layoutDoc); + let dxf = () => parent.getDocTransform(layoutDoc, dref.current!); + let rowSpan = Math.ceil((height() + parent.gridGap) / parent.gridGap); + parent._docXfs.push({ dxf: dxf, width: width, height: height }); + return
+ {this.props.parent.getDisplayDoc(layoutDoc, d, dxf, width)} +
; + }); + } + + getDocTransform(doc: Doc, dref: HTMLDivElement) { + let { scale, translateX, translateY } = Utils.GetScreenTransform(dref); + let outerXf = Utils.GetScreenTransform(this.props.parent._masonryGridRef!); + let offset = this.props.parent.props.ScreenToLocalTransform().transformDirection(outerXf.translateX - translateX, outerXf.translateY - translateY); + return this.props.parent.props.ScreenToLocalTransform(). + translate(offset[0], offset[1]). + scale(NumCast(doc.width, 1) / this.props.parent.columnWidth); + } + + getValue = (value: string): any => { + let parsed = parseInt(value); + if (!isNaN(parsed)) { + return parsed; + } + if (value.toLowerCase().indexOf("true") > -1) { + return true; + } + if (value.toLowerCase().indexOf("false") > -1) { + return false; + } + return value; + } + + @action + headingChanged = (value: string, shiftDown?: boolean) => { + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let castedValue = this.getValue(value); + if (castedValue) { + if (this.props.parent.sectionHeaders) { + if (this.props.parent.sectionHeaders.map(i => i.heading).indexOf(castedValue.toString()) > -1) { + return false; + } + } + this.props.docList.forEach(d => d[key] = castedValue); + if (this.props.headingObject) { + this.props.headingObject.setHeading(castedValue.toString()); + this._heading = this.props.headingObject.heading; + } + return true; + } + return false; + } + + @action + changeColumnColor = (color: string) => { + if (this.props.headingObject) { + this.props.headingObject.setColor(color); + this._color = color; + } + } + + @action + pointerEntered = () => { + if (SelectionManager.GetIsDragging()) { + this._background = "#b4b4b4"; + } + } + + @action + pointerLeave = () => { + this._background = "inherit"; + document.removeEventListener("pointermove", this.startDrag); + } + + @action + addDocument = (value: string, shiftDown?: boolean) => { + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let newDoc = Docs.Create.TextDocument({ height: 18, width: 200, title: value }); + newDoc[key] = this.getValue(this.props.heading); + return this.props.parent.props.addDocument(newDoc); + } + + @action + deleteColumn = () => { + let key = StrCast(this.props.parent.props.Document.sectionFilter); + this.props.docList.forEach(d => d[key] = undefined); + if (this.props.parent.sectionHeaders && this.props.headingObject) { + let index = this.props.parent.sectionHeaders.indexOf(this.props.headingObject); + this.props.parent.sectionHeaders.splice(index, 1); + } + } + + startDrag = (e: PointerEvent) => { + let [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) { + let alias = Doc.MakeAlias(this.props.parent.props.Document); + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let value = this.getValue(this._heading); + value = typeof value === "string" ? `"${value}"` : value; + let script = `return doc.${key} === ${value}`; + let compiled = CompileScript(script, { params: { doc: Doc.name } }); + if (compiled.compiled) { + let scriptField = new ScriptField(compiled); + alias.viewSpecScript = scriptField; + let dragData = new DragManager.DocumentDragData([alias], [alias.proto]); + DragManager.StartDocumentDrag([this._headerRef.current!], dragData, e.clientX, e.clientY); + } + + e.stopPropagation(); + document.removeEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + } + } + + pointerUp = (e: PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + + document.removeEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + } + + headerDown = (e: React.PointerEvent) => { + e.stopPropagation(); + e.preventDefault(); + + let [dx, dy] = this.props.screenToLocalTransform().transformDirection(e.clientX, e.clientY); + this._startDragPosition = { x: dx, y: dy }; + + document.removeEventListener("pointermove", this.startDrag); + document.addEventListener("pointermove", this.startDrag); + document.removeEventListener("pointerup", this.pointerUp); + document.addEventListener("pointerup", this.pointerUp); + } + + renderColorPicker = () => { + let selected = this.props.headingObject ? this.props.headingObject.color : "#f1efeb"; + + let pink = PastelSchemaPalette.get("pink2"); + let purple = PastelSchemaPalette.get("purple4"); + let blue = PastelSchemaPalette.get("bluegreen1"); + let yellow = PastelSchemaPalette.get("yellow4"); + let red = PastelSchemaPalette.get("red2"); + let green = PastelSchemaPalette.get("bluegreen7"); + let cyan = PastelSchemaPalette.get("bluegreen5"); + let orange = PastelSchemaPalette.get("orange1"); + let gray = "#f1efeb"; + + return ( +
+
+
this.changeColumnColor(pink!)}>
+
this.changeColumnColor(purple!)}>
+
this.changeColumnColor(blue!)}>
+
this.changeColumnColor(yellow!)}>
+
this.changeColumnColor(red!)}>
+
this.changeColumnColor(gray)}>
+
this.changeColumnColor(green!)}>
+
this.changeColumnColor(cyan!)}>
+
this.changeColumnColor(orange!)}>
+
+
+ ); + } + + @observable _headingsHack: number = 1; + render() { + let cols = this.props.rows(); + let key = StrCast(this.props.parent.props.Document.sectionFilter); + let templatecols = ""; + let headings = this.props.headings(); + let heading = this._heading; + let style = this.props.parent; + let uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); + let evContents = heading ? heading : this.props.type && this.props.type === "number" ? "0" : `NO ${key.toUpperCase()} VALUE`; + let headerEditableViewProps = { + GetValue: () => evContents, + SetValue: this.headingChanged, + contents: evContents, + oneLine: true + }; + let newEditableViewProps = { + GetValue: () => "", + SetValue: this.addDocument, + contents: "+ NEW" + }; + // let headingView = this.props.headingObject ? + let headingView = +
+
this._headingsHack++ && instHeading.setCollapsed(!instHeading.collapsed))} + > + {this.props.heading} +
+ {/*
*/} + + {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : +
+ + + +
+ } +
; + + + //
+ // {/* the default bucket (no key value) has a tooltip that describes what it is. + // Further, it does not have a color and cannot be deleted. */} + //
+ // + // {evContents === `NO ${key.toUpperCase()} VALUE` ? (null) : + //
+ // + // + // + //
+ // } + // {evContents === `NO ${key.toUpperCase()} VALUE` ? + // (null) : + // } + //
; + //
: (null); + // for (let i = 0; i < cols; i++) templatecols += `${style.columnWidth / style.numGroupColumns}px `; + return ( +
+ {headingView} + {/* {!heading ? (null) : +
this._headingsHack++ && instHeading.setCollapsed(!instHeading.collapsed))} > + {instHeading.heading} +
} */} + {/*
+ {this.masonryChildren(this.props.docList)} + {singleColumn ? (null) : this.props.parent.columnDragger} +
*/} + { + this._headingsHack && heading ? (null) : +
list + ` ${this.props.parent.columnWidth}px`, ""), + }}> + {this.masonryChildren(this.props.docList)} + {this.props.parent.columnDragger} +
+ } + { //controls the +NEW for each row + (this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'view-mode' && this.props.parent.props.CollectionView.props.Document.chromeStatus !== 'disabled') ? +
+ +
: null + } +
+ ); + } +} \ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 01d4ea2b6..a83c97624 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -1,12 +1,15 @@ @import "../globalCssVariables"; .collectionMasonryView { - display:inline; + display: inline; } -.collectionStackingView{ + +.collectionStackingView { display: flex; } -.collectionStackingView, .collectionMasonryView{ + +.collectionStackingView, +.collectionMasonryView { height: 100%; width: 100%; position: absolute; @@ -14,6 +17,7 @@ overflow-y: auto; flex-wrap: wrap; transition: top .5s; + .collectionSchemaView-previewDoc { height: 100%; position: absolute; @@ -40,10 +44,12 @@ top: 0; left: 0; } + .collectionStackingView-masonrySingle { height: 100%; position: absolute; } + .collectionStackingView-masonryGrid { margin: auto; height: max-content; @@ -91,7 +97,7 @@ height: 100%; margin: auto; } - + .collectionStackingView-masonrySection { margin: auto; } @@ -290,4 +296,10 @@ } +} + +.red-box { + width: 200px; + height: 200px; + background: red; } \ No newline at end of file diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 2e4f6aff5..c2342eb50 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -23,6 +23,7 @@ import { CollectionSubView } from "./CollectionSubView"; import { ContextMenu } from "../ContextMenu"; import { ContextMenuProps } from "../ContextMenuItem"; import { ScriptBox } from "../ScriptBox"; +import { CollectionMasonryViewFieldRow } from "./CollectionMasonryViewFieldRow"; @observer export class CollectionStackingView extends CollectionSubView(doc => doc) { @@ -125,7 +126,7 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { } createRef = (ele: HTMLDivElement | null) => { this._masonryGridRef = ele; - this.createDropTarget(ele!); + this.createDropTarget(ele!); //so the whole grid is the drop target? } overlays = (doc: Doc) => { @@ -282,45 +283,72 @@ export class CollectionStackingView extends CollectionSubView(doc => doc) { translate(offset[0], offset[1]). scale(NumCast(doc.width, 1) / this.columnWidth); } - masonryChildren(docs: Doc[]) { - this._docXfs.length = 0; - return docs.map((d, i) => { - let dref = React.createRef(); - let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc); - let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1); - let height = () => this.getDocHeight(layoutDoc); - let dxf = () => this.getDocTransform(layoutDoc, dref.current!); - let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); - this._docXfs.push({ dxf: dxf, width: width, height: height }); - return
- {this.getDisplayDoc(layoutDoc, d, dxf, width)} -
; - }); - } + // masonryChildren(docs: Doc[]) { + // this._docXfs.length = 0; + // return docs.map((d, i) => { + // let dref = React.createRef(); + // let layoutDoc = Doc.expandTemplateLayout(d, this.props.DataDoc); + // let width = () => (d.nativeWidth && !d.ignoreAspect && !this.props.Document.fillColumn ? Math.min(d[WidthSym](), this.columnWidth) : this.columnWidth);/// (uniqueHeadings.length + 1); + // let height = () => this.getDocHeight(layoutDoc); + // let dxf = () => this.getDocTransform(layoutDoc, dref.current!); + // let rowSpan = Math.ceil((height() + this.gridGap) / this.gridGap); + // this._docXfs.push({ dxf: dxf, width: width, height: height }); + // return
+ // {this.getDisplayDoc(layoutDoc, d, dxf, width)} + //
; + // }); + // } + + // @observable _headingsHack: number = 1; + // sectionMasonry(heading: SchemaHeaderField | undefined, docList: Doc[]) { + // let cols = Math.max(1, Math.min(docList.length, + // Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); + // // let specialCols = () => this.isMasonryView ? 1 : Math.max(1, Math.min(docList.length, + // // Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); + // // let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; + // return
+ // {!heading ? (null) : + //
this._headingsHack++ && heading.setCollapsed(!heading.collapsed))} > + // {heading.heading} + //
} + // {this._headingsHack && heading && heading.collapsed ? (null) : + //
list + ` ${this.columnWidth}px`, ""), + // }}> + // {this.masonryChildren(docList)} + // {this.columnDragger} + //
+ // } + //
; + // } - @observable _headingsHack: number = 1; - sectionMasonry(heading: SchemaHeaderField | undefined, docList: Doc[]) { - let cols = Math.max(1, Math.min(docList.length, + sectionMasonry = (heading: SchemaHeaderField | undefined, docList: Doc[]) => { + let key = this.sectionFilter; + let type: "string" | "number" | "bigint" | "boolean" | "symbol" | "undefined" | "object" | "function" | undefined = undefined; + let types = docList.length ? docList.map(d => typeof d[key]) : this.childDocs.map(d => typeof d[key]); + if (types.map((i, idx) => types.indexOf(i) === idx).length === 1) { + type = types[0]; + } + let rows = () => !this.isStackingView ? 1 : Math.max(1, Math.min(docList.length, Math.floor((this.props.PanelWidth() - 2 * this.xMargin) / (this.columnWidth + this.gridGap)))); - return
- {!heading ? (null) : -
this._headingsHack++ && heading.setCollapsed(!heading.collapsed))} > - {heading.heading} -
} - {this._headingsHack && heading && heading.collapsed ? (null) : -
list + ` ${this.columnWidth}px`, ""), - }}> - {this.masonryChildren(docList)} - {this.columnDragger} -
- } -
; + return ; } @action diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 74c7ef305..a3db46584 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -318,6 +318,7 @@ export class CollectionStackingViewFieldColumn extends React.Component {this.children(this.props.docList)} {singleColumn ? (null) : this.props.parent.columnDragger} diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 077f3f941..ff0c76932 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -38,13 +38,13 @@ export interface SubCollectionViewProps extends CollectionViewProps { export function CollectionSubView(schemaCtor: (doc: Doc) => T) { class CollectionSubView extends DocComponent(schemaCtor) { private dropDisposer?: DragManager.DragDropDisposer; - protected createDropTarget = (ele: HTMLDivElement) => { + protected createDropTarget = (ele: HTMLDivElement) => { //used for stacking and masonry view this.dropDisposer && this.dropDisposer(); if (ele) { this.dropDisposer = DragManager.MakeDropTarget(ele, { handlers: { drop: this.drop.bind(this) } }); } } - protected CreateDropTarget(ele: HTMLDivElement) { + protected CreateDropTarget(ele: HTMLDivElement) { //used in schema view this.createDropTarget(ele); } -- cgit v1.2.3-70-g09d2