diff options
Diffstat (limited to 'src/client/views/collections/CollectionStackingView.tsx')
-rw-r--r-- | src/client/views/collections/CollectionStackingView.tsx | 165 |
1 files changed, 66 insertions, 99 deletions
diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index be570564b..4a0ddc631 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -4,7 +4,7 @@ import { action, computed, IReactionDisposer, makeObservable, observable, Observ import { observer } from 'mobx-react'; import * as React from 'react'; import { ClientUtils, DivHeight, returnNone, returnZero, setupMoveUpEvents, smoothScroll } from '../../../ClientUtils'; -import { Doc, Opt } from '../../../fields/Doc'; +import { Doc, Field, Opt } from '../../../fields/Doc'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; @@ -35,6 +35,7 @@ import { CollectionStackingViewFieldColumn } from './CollectionStackingViewField import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { computedFn } from 'mobx-utils'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; +import { FieldsDropdown } from '../FieldsDropdown'; export type collectionStackingViewProps = { sortFunc?: (a: Doc, b: Doc) => number; @@ -48,15 +49,15 @@ export type collectionStackingViewProps = { @observer export class CollectionStackingView extends CollectionSubView<Partial<collectionStackingViewProps>>() { _disposers: { [key: string]: IReactionDisposer } = {}; + _addGroupRef = React.createRef<HTMLDivElement>(); _masonryGridRef: HTMLDivElement | null = null; // used in a column dragger, likely due for the masonry grid view. We want to use this _draggerRef = React.createRef<HTMLDivElement>(); // keeping track of documents. Updated on internal and external drops. What's the difference? _docXfs: { height: () => number; width: () => number; stackedDocTransform: () => Transform }[] = []; - // Doesn't look like this field is being used anywhere. Obsolete? - _columnStart: number = 0; - @observable _refList: HTMLElement[] = []; + @observable _colStackRefs: HTMLElement[] = []; + @observable _colHdrRefs: HTMLElement[] = []; // map of node headers to their heights. Used in Masonry @observable _heightMap = new Map<string, number>(); // Assuming that this is the current css cursor style @@ -67,9 +68,24 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @computed get chromeHidden() { return this._props.chromeHidden || BoolCast(this.layoutDoc.chromeHidden); } - // it looks like this gets the column headers that Mehek was showing just now @computed get colHeaderData() { - return Cast(this.dataDoc['_' + this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null); + return Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null); + } + + @computed get Sections() { + return this.filteredChildren.reduce( + (map, d) => { + const docHeader = d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`; + const docHeaderString = docHeader !== undefined ? Field.toString(docHeader) : `NO ${this.pivotField.toUpperCase()} VALUE`; + + // find existing header or create + const existingHeader = Array.from(map.keys()).find(sh => sh.heading === docHeaderString); + if (!existingHeader) map.set(new SchemaHeaderField(docHeaderString), [d]); + else map.get(existingHeader)!.push(d); + return map; + }, + new ObservableMap<SchemaHeaderField, Doc[]>(this.colHeaderData?.map(hdata => [hdata, []] as [SchemaHeaderField, Doc[]]) ?? []) + ); } // Still not sure what a pivot is, but it appears that we can actually filter docs somehow? @computed get pivotField() { @@ -89,7 +105,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return NumCast(this.layoutDoc._xMargin, Math.max(3, 0.05 * this._props.PanelWidth())); } @computed get yMargin() { - return this._props.yPadding || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); + return this._props.yMargin || NumCast(this.layoutDoc._yMargin, Math.min(5, 0.05 * this._props.PanelWidth())); } @computed get gridGap() { @@ -107,9 +123,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @computed get showAddAGroup() { return this.pivotField && !this.chromeHidden; } + @computed get availableWidth() { + return this._props.PanelWidth() - 2 * this.xMargin - (this.isStackingView ? this.gridGap * ((this.numGroupColumns || 1) - 1) : 0); + } // columnWidth handles the margin on the left and right side of the documents @computed get columnWidth() { - const availableWidth = this._props.PanelWidth() - 2 * this.xMargin; + const availableWidth = this.availableWidth; const cwid = availableWidth / (NumCast(this.Document._layout_columnCount) || this._props.PanelWidth() / NumCast(this.Document._layout_columnWidth, this._props.PanelWidth() / 4)); return Math.min(availableWidth, this.isStackingView ? availableWidth / (this.numGroupColumns || 1) : cwid - this.gridGap); } @@ -121,28 +140,17 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection constructor(props: SubCollectionViewProps) { super(props); makeObservable(this); - if (this.colHeaderData === undefined) { - // TODO: what is a layout doc? Is it literally how this document is supposed to be layed out? - // here we're making an empty list of column headers (again, what Mehek showed us) - this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>(); - } } + availableWidthFn = () => this.availableWidth; columnWidthFn = () => this.columnWidth; columnDocHeightFn = (doc: Doc) => () => (this.isStackingView ? this.getDocHeight(doc)() : Math.min(this.getDocHeight(doc)(), this._props.PanelHeight())); - // TODO: plj - these are the children children = (docs: Doc[]) => { - // TODO: can somebody explain me to what exactly TraceMobX is? TraceMobx(); - // appears that we are going to reset the _docXfs. TODO: what is Xfs? this._docXfs.length = 0; - this._renderCount < docs.length && - setTimeout( - action(() => { - this._renderCount = Math.min(docs.length, this._renderCount + 5); - }) - ); + this._renderCount < docs.length && + setTimeout(action(() => (this._renderCount = Math.min(docs.length, this._renderCount + 5)))); // prettier-ignore return docs.map((d, i) => { // assuming we need to get rowSpan because we might be dealing with many columns. Grid gap makes sense if multiple columns const rowSpan = Math.ceil((this.getDocHeight(d)() + this.gridGap) / this.gridGap); @@ -153,76 +161,28 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection margin: undefined, transition: this.getDocTransition(d)(), width: this.columnWidth, - marginTop: i ? this.gridGap : 0, height: this.getDocHeight(d)(), zIndex: DocumentView.getFirstDocumentView(d)?.IsSelected ? 1000 : 0, } : { gridRowEnd: `span ${rowSpan}`, zIndex: DocumentView.getFirstDocumentView(d)?.IsSelected ? 1000 : 0 }; // So we're choosing whether we're going to render a column or a masonry doc return ( - <div className={`collectionStackingView-${this.isStackingView ? 'columnDoc' : 'masonryDoc'}`} key={d[Id]} style={style}> + <div className={`collectionStackingView${this.isStackingView ? '-columnDoc' : '-masonryDoc'}`} key={d[Id]} style={style}> {this.getDisplayDoc(d, this.getDocTransition(d), i)} </div> ); }); }; @action - setDocHeight = (key: string, sectionHeight: number) => { - this._heightMap.set(key, sectionHeight); + setDocHeight = (key: string, sectionHeight: number) => this._heightMap.set(key, sectionHeight); + + setAutoHeight = () => { + const maxHeader = this.isStackingView ? this._colHdrRefs.reduce((p, r) => Math.max(p, DivHeight(r)), 0) + (this._colHdrRefs.length ? this.gridGap : 0) : 0; + const maxCol = this.isStackingView + ? this._colStackRefs.reduce((p, r) => Math.max(p, DivHeight(r)), 0) + this.gridGap + : this._colStackRefs.reduce((p, r) => p + DivHeight(r), this._addGroupRef.current ? DivHeight(this._addGroupRef.current) : 0); + this._props.setHeight?.(this.headerMargin + 2 * this.yMargin + maxCol + maxHeader); }; - - // is sections that all collections inherit? I think this is how we show the masonry/columns - // TODO: this seems important - get Sections() { - // appears that pivot field IS actually for sorting - if (!this.pivotField || this.colHeaderData instanceof Promise) return new Map<SchemaHeaderField, Doc[]>(); - - if (this.colHeaderData === undefined) { - setTimeout(() => { - this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List<SchemaHeaderField>(); - }); - return new Map<SchemaHeaderField, Doc[]>(); - } - const colHeaderData = Array.from(this.colHeaderData); - const fields = new Map<SchemaHeaderField, Doc[]>(colHeaderData.map(sh => [sh, []] as [SchemaHeaderField, []])); - let changed = false; - this.filteredChildren.forEach(d => { - const sectionValue = (d[this.pivotField] ? d[this.pivotField] : `NO ${this.pivotField.toUpperCase()} VALUE`) as object; - // the next five lines ensures that floating point rounding errors don't create more than one section -syip - const parsed = parseInt(sectionValue.toString()); - const castedSectionValue = !isNaN(parsed) ? parsed : sectionValue; - - // look for if header exists already - const existingHeader = colHeaderData.find(sh => sh.heading === (castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`)); - if (existingHeader) { - fields.get(existingHeader)!.push(d); - } else { - const newSchemaHeader = new SchemaHeaderField(castedSectionValue ? castedSectionValue.toString() : `NO ${this.pivotField.toUpperCase()} VALUE`); - fields.set(newSchemaHeader, [d]); - colHeaderData.push(newSchemaHeader); - changed = true; - } - }); - // remove all empty columns if hideHeadings is set - // we will want to have something like this, so that we can hide columns and add them back in - if (this.layoutDoc._columnsHideIfEmpty) { - Array.from(fields.keys()) - .filter(key => !fields.get(key)!.length) - .forEach(header => { - fields.delete(header); - colHeaderData.splice(colHeaderData.indexOf(header), 1); - changed = true; - }); - } - changed && - setTimeout( - action(() => this.colHeaderData?.splice(0, this.colHeaderData.length, ...colHeaderData)), - 0 - ); - return fields; - } - - setAutoHeight = () => this._props.setHeight?.(this.headerMargin + (this.isStackingView ? Math.max(...this._refList.map(DivHeight)) : 2 * this.yMargin + this._refList.reduce((p, r) => p + DivHeight(r), 0))); observer = new ResizeObserver(this.setAutoHeight); componentDidMount() { @@ -232,9 +192,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection // reset section headers when a new filter is inputted this._disposers.pivotField = reaction( () => this.pivotField, - () => { - this.dataDoc['_' + this.fieldKey + '_columnHeaders'] = new List(); - } + () => (this.dataDoc[this.fieldKey + '_columnHeaders'] = new List()) ); // reset section headers when a new filter is inputted this._disposers.width = reaction( @@ -252,7 +210,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection ); this._disposers.refList = reaction( - () => ({ refList: this._refList.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !DocumentView.LightboxContains(this.DocumentView?.()) }), + () => ({ refList: this._colStackRefs.slice(), autoHeight: this.layoutDoc._layout_autoHeight && !DocumentView.LightboxContains(this.DocumentView?.()) }), ({ refList, autoHeight }) => { this.observer.disconnect(); if (autoHeight) refList.forEach(r => this.observer.observe(r)); @@ -381,8 +339,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection hideDecorations={this._props.childHideDecorations} childFiltersByRanges={this.childDocRangeFilters} searchFilterDocs={this.searchFilterDocs} - xPadding={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)} - yPadding={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)} + xMargin={NumCast(this.layoutDoc._childXPadding, this._props.childXPadding)} + yMargin={NumCast(this.layoutDoc._childYPadding, this._props.childYPadding)} rejectDrop={this._props.childRejectDrop} addDocument={this._props.addDocument} moveDocument={this._props.moveDocument} @@ -409,11 +367,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection getDocWidth = computedFn((d?: Doc) => () => { if (!d) return 0; const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.()); - const maxWidth = this.columnWidth / this.numGroupColumns; if (!this.layoutDoc._columnsFill && !this.childFitWidth(childLayoutDoc)) { - return Math.min(NumCast(d._width), maxWidth); + return Math.min(NumCast(d._width), this.columnWidth); } - return maxWidth; + return this.columnWidth; }); getDocTransition = computedFn((d?: Doc) => () => StrCast(d?.dataTransition)); getDocHeight = computedFn((d?: Doc) => () => { @@ -424,7 +381,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._width) : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!this.childFitWidth(childLayoutDoc) ? NumCast(d._height) : 0); if (nw && nh) { - const colWid = this.columnWidth / (this.isStackingView ? this.numGroupColumns : 1); + const colWid = this.columnWidth; const docWid = this.layoutDoc._columnsFill ? colWid : Math.min(this.getDocWidth(d)(), colWid); return Math.min(maxHeight, (docWid * nh) / nw); } @@ -571,7 +528,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } return ( <CollectionStackingViewFieldColumn - refList={this._refList} + colStackRefs={this._colStackRefs} + colHeaderRefs={this._colHdrRefs} addDocument={this.addDocument} chromeHidden={this.chromeHidden} colHeaderData={this.colHeaderData} @@ -613,8 +571,10 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection showHandle={first} Doc={this.Document} chromeHidden={this.chromeHidden} + panelWidth={this.availableWidthFn} + columnWidth={this.columnWidthFn} pivotField={this.pivotField} - refList={this._refList} + sectionRefs={this._colStackRefs} key={heading ? heading.heading : ''} rows={rows} headings={this.headings} @@ -635,9 +595,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection /// add a new group category (column) to the active set of note categories. (e.g., if the pivot field is 'transportation', groups might be 'car', 'plane', 'bike', etc) @action addGroup = (value: string) => { + if (!this.colHeaderData) { + this.dataDoc[this.fieldKey + '_columnHeaders'] = new List(); + } if (value && this.colHeaderData) { - const schemaHdrField = new SchemaHeaderField(value); - this.colHeaderData.push(schemaHdrField); + this.colHeaderData.push(new SchemaHeaderField(value)); return true; } return false; @@ -745,7 +707,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection this.fixWheelEvents(ele, this._props.isContentActive); }} style={{ - overflowY: this.isContentActive() ? 'auto' : 'hidden', + paddingBottom: this.yMargin, + paddingTop: this.yMargin, + paddingLeft: this.xMargin, + paddingRight: this.xMargin, + overflowY: this.isContentActive() && !this.layoutDoc._layout_autoHeight ? 'auto' : 'hidden', background: this._props.styleProvider?.(this.Document, this._props, StyleProp.BackgroundColor) as string, pointerEvents: this._props.pointerEvents?.() ?? this.backgroundEvents, }} @@ -756,11 +722,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection onContextMenu={this.onContextMenu} onWheel={e => this.isContentActive() && e.stopPropagation()}> {this.renderedSections} - {!this.showAddAGroup ? null : ( - <div key={`${this.Document[Id]}-addGroup`} className="collectionStackingView-addGroupButton" style={{ width: !this.isStackingView ? '100%' : this.columnWidth / this.numGroupColumns - 10, marginTop: 10 }}> - <EditableView {...editableViewProps} /> - </div> - )} + <div className="collectionStackingView-addGroupButton" ref={this._addGroupRef} style={{ width: !this.isStackingView ? '100%' : this.columnWidth, display: this.showAddAGroup ? undefined : 'none' }}> + <EditableView {...editableViewProps} /> + </div> + <div style={{ right: 0, top: 0, position: 'absolute', display: !this.layoutDoc._pivotField ? 'none' : undefined }}> + <FieldsDropdown Doc={this.Document} isInactive={!this._props.isContentActive()} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} /> + </div> </div> </div> </> |