diff options
author | bobzel <zzzman@gmail.com> | 2025-05-19 19:43:28 -0400 |
---|---|---|
committer | bobzel <zzzman@gmail.com> | 2025-05-19 19:43:28 -0400 |
commit | 51b4b64eae4749542590785bca9d33d3ccdded69 (patch) | |
tree | eb7e572d864b24a179997bb9038aecf403e73f43 /src | |
parent | 9eb4669477eb03fb404b11eec41e09a93ec15425 (diff) |
fixed text boxes to calculate height correctly. removed usertag modification time in text. fixed masonry and stacking views with pivots.
Diffstat (limited to 'src')
7 files changed, 111 insertions, 162 deletions
diff --git a/src/ClientUtils.ts b/src/ClientUtils.ts index cc8b715b4..8eb1a39ec 100644 --- a/src/ClientUtils.ts +++ b/src/ClientUtils.ts @@ -670,7 +670,8 @@ export function setupMoveUpEvents( } export function DivHeight(ele: HTMLElement | null): number { - return ele ? Number(getComputedStyle(ele).height.replace('px', '')) : 0; + const hgt = ele ? Number(getComputedStyle(ele).height.replace('px', '')) : 0; + return Number.isNaN(hgt) ? 0 : hgt; } export function DivWidth(ele: HTMLElement | null): number { return ele ? Number(getComputedStyle(ele).width.replace('px', '')) : 0; diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 89ccf5a0f..d1f7971d4 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -28,12 +28,14 @@ interface CMVFieldRowProps { headingObject: SchemaHeaderField | undefined; docList: Doc[]; parent: CollectionStackingView; + panelWidth: () => number; + columnWidth: () => number; pivotField: string; type: 'string' | 'number' | 'bigint' | 'boolean' | 'symbol' | 'undefined' | 'object' | 'function' | undefined; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; setDocHeight: (key: string, thisHeight: number) => void; - refList: Element[]; + sectionRefs: Element[]; showHandle: boolean; } @@ -74,7 +76,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF createRowDropRef = (ele: HTMLDivElement | null) => { this._dropDisposer?.(); if (ele) this._dropDisposer = DragManager.MakeDropTarget(ele, this.rowDrop.bind(this), this._props.Doc); - else if (this._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 1); + else if (this._ele) this.props.sectionRefs.splice(this.props.sectionRefs.indexOf(this._ele), 1); this._ele = ele; }; @action @@ -82,10 +84,10 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF this.heading = this._props.headingObject?.heading || ''; this.color = this._props.headingObject?.color || '#f1efeb'; this.collapsed = this._props.headingObject?.collapsed || false; - this._ele && this.props.refList.push(this._ele); + this._ele && this.props.sectionRefs.push(this._ele); } componentWillUnmount() { - this._ele && this.props.refList.splice(this.props.refList.indexOf(this._ele), 1); + this._ele && this.props.sectionRefs.splice(this.props.sectionRefs.indexOf(this._ele), 1); this._ele = null; } @@ -128,10 +130,8 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF const key = this._props.pivotField; const castedValue = this.getValue(value); if (castedValue) { - if (this._props.parent.colHeaderData) { - if (this._props.parent.colHeaderData.map(i => i.heading).indexOf(castedValue.toString()) > -1) { - return false; - } + if (this._props.parent.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) || 0 > -1) { + return false; } key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true)); this._heading = castedValue.toString(); @@ -251,20 +251,11 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF textCallback = (/* char: string */) => this.addDocument('', false); @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 showChrome = !this._props.chromeHidden; - const stackPad = showChrome ? `0px ${this._props.parent.xMargin}px` : `${this._props.parent.yMargin}px ${this._props.parent.xMargin}px 0px ${this._props.parent.xMargin}px `; + const rows = Math.max(1, Math.min(this._props.docList.length, Math.floor(this._props.panelWidth() / this._props.columnWidth()))); return this.collapsed ? null : ( <div style={{ position: 'relative' }}> - {showChrome ? ( - <div - className="collectionStackingView-addDocumentButton" - style={ - { - // width: style.columnWidth / style.numGroupColumns, - // padding: `${NumCast(this._props.parent.layoutDoc._yPadding, this._props.parent.yMargin)}px 0px 0px 0px`, - } - }> + {!this._props.chromeHidden ? ( + <div className="collectionStackingView-addDocumentButton"> <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents="+ NEW" /> </div> ) : null} @@ -272,11 +263,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF className="collectionStackingView-masonryGrid" ref={this._contRef} style={{ - padding: stackPad, minHeight: this._props.showHandle && this._props.parent._props.isContentActive() ? '10px' : undefined, - width: this._props.parent.NodeWidth, gridGap: this._props.parent.gridGap, - gridTemplateColumns: numberRange(rows).reduce(list => list + ` ${this._props.parent.columnWidth}px`, ''), + gridTemplateColumns: numberRange(rows).reduce(list => list + ` ${this._props.columnWidth()}px`, ''), }}> {this._props.parent.children(this._props.docList)} </div> @@ -339,7 +328,12 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF render() { 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}> + <div + className="collectionStackingView-masonrySection" + style={{ width: this._props.pivotField ? this._props.panelWidth() : '100%', background }} + ref={this.createRowDropRef} + onPointerEnter={this.pointerEnteredRow} + onPointerLeave={this.pointerLeaveRow}> {this.headingView} {this.contentLayout} </div> diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 05ac52ff9..d6e4943ff 100644 --- a/src/client/views/collections/CollectionStackingView.scss +++ b/src/client/views/collections/CollectionStackingView.scss @@ -2,10 +2,12 @@ .collectionMasonryView { display: inline; + flex-wrap: wrap; } .collectionStackingView { display: flex; + justify-content: space-between; } .collectionStackingMasonry-cont { @@ -56,7 +58,6 @@ top: 0; overflow-y: auto; overflow-x: hidden; - flex-wrap: wrap; transition: top 0.5s; > div { @@ -210,7 +211,6 @@ .collectionStackingView-sectionHeader { text-align: center; margin: auto; - margin-bottom: 10px; background: global.$medium-gray; // overflow: hidden; overflow is visible so the color menu isn't hidden -ftong @@ -367,7 +367,6 @@ .collectionStackingView-addGroupButton { display: flex; overflow: hidden; - margin: auto; width: 90%; overflow: ellipses; diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 25a222cbb..cc514f262 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,10 +68,25 @@ 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); } + + @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() { return StrCast(this.layoutDoc._pivotField); @@ -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)); @@ -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} @@ -636,8 +596,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @action addGroup = (value: string) => { 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 +704,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 +719,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} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} /> + </div> </div> </div> </> diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 345f60e75..8c7cb8276 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -1,5 +1,5 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { DivHeight, DivWidth, returnEmptyString, returnTrue, setupMoveUpEvents } from '../../../ClientUtils'; @@ -50,14 +50,14 @@ interface CSVFieldColumnProps { addDocument: (doc: Doc | Doc[]) => boolean; createDropTarget: (ele: HTMLDivElement) => void; screenToLocalTransform: () => Transform; - refList: HTMLElement[]; + colStackRefs: HTMLElement[]; + colHeaderRefs: HTMLElement[]; } @observer export class CollectionStackingViewFieldColumn extends ObservableReactComponent<CSVFieldColumnProps> { private dropDisposer?: DragManager.DragDropDisposer; private _disposers: { [name: string]: IReactionDisposer } = {}; - private _headerRef: React.RefObject<HTMLDivElement> = React.createRef(); @observable _background = 'inherit'; @observable _paletteOn = false; @observable _heading = ''; @@ -72,6 +72,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< _ele: HTMLElement | null = null; _eleMasonrySingle = React.createRef<HTMLDivElement>(); + _headerRef: HTMLDivElement | null = null; protected onInternalPreDrop = (e: Event, de: DragManager.DropEvent, targetDropAction: dropActionType) => { const dragData = de.complete.docDragData; @@ -93,13 +94,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc, this.onInternalPreDrop.bind(this)); - else if (this._eleMasonrySingle.current) this.props.refList.splice(this.props.refList.indexOf(this._eleMasonrySingle.current), 1); + else if (this._eleMasonrySingle.current) runInAction(() => this.props.colStackRefs.splice(this.props.colStackRefs.indexOf(this._eleMasonrySingle.current!), 1)); this._ele = ele; }; @action componentDidMount() { - this._eleMasonrySingle.current && this.props.refList.push(this._eleMasonrySingle.current); + this._eleMasonrySingle.current && this.props.colStackRefs.push(this._eleMasonrySingle.current); this._disposers.collapser = reaction( () => this._props.headingObject?.collapsed, collapsed => { this.collapsed = collapsed !== undefined ? BoolCast(collapsed) : false; }, // prettier-ignore @@ -108,7 +109,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< } componentWillUnmount() { this._disposers.collapser?.(); - this._ele && this.props.refList.splice(this.props.refList.indexOf(this._ele), 1); + this._ele && runInAction(() => this.props.colStackRefs.splice(this.props.colStackRefs.indexOf(this._ele!), 1)); this._ele = null; } @@ -192,7 +193,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< value = typeof value === 'string' ? `"${value}"` : value; embedding.viewSpecScript = ScriptField.MakeFunction(`doc.${this._props.pivotField} === ${value}`, { doc: Doc.name }); if (embedding.viewSpecScript) { - DragManager.StartDocumentDrag([this._headerRef.current!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY); + DragManager.StartDocumentDrag([this._headerRef!], new DragManager.DocumentDragData([embedding]), e.clientX, e.clientY); return true; } return false; @@ -314,9 +315,14 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< <div key={heading} className="collectionStackingView-sectionHeader" - ref={this._headerRef} + ref={r => { + if (this._headerRef && this._props.colHeaderRefs.includes(this._headerRef)) this._props.colHeaderRefs.splice(this._props.colStackRefs.indexOf(this._headerRef), 1); + r && this._props.colHeaderRefs.push(r); + this._headerRef = r; + }} style={{ - marginTop: this._props.yMargin, + marginTop: 0, + marginBottom: this._props.gridGap, width: this._props.columnWidth, }}> {/* the default bucket (no key value) has a tooltip that describes what it is. @@ -367,7 +373,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< ref={this._eleMasonrySingle} className="collectionStackingView-masonrySingle" style={{ - padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`, + padding: `${columnYMargin}px ${0}px ${0}px ${0}px`, margin: this._props.dontCenter.includes('x') ? undefined : 'auto', height: 'max-content', position: 'relative', @@ -400,15 +406,13 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< render() { TraceMobx(); - const headings = this._props.headings(); const heading = this._heading; - const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); return ( <div className="collectionStackingViewFieldColumn" key={heading} style={{ - width: `${100 / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1)}%`, + width: this._props.columnWidth, height: undefined, background: this._background, }} diff --git a/src/client/views/nodes/formattedText/FormattedTextBox.tsx b/src/client/views/nodes/formattedText/FormattedTextBox.tsx index 4189fcc0c..fea2986c7 100644 --- a/src/client/views/nodes/formattedText/FormattedTextBox.tsx +++ b/src/client/views/nodes/formattedText/FormattedTextBox.tsx @@ -1522,7 +1522,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB } if (this.EditorView && selectOnLoad && !this._props.dontRegisterView && !this._props.dontSelectOnLoad && this.isActiveTab(this.ProseRef)) { const { $from } = this.EditorView.state.selection; - const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) }); + const mark = schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail() }); const curMarks = this.EditorView.state.storedMarks ?? $from?.marksAcross(this.EditorView.state.selection.$head) ?? []; const storedMarks = [...curMarks.filter(m => m.type !== mark.type), mark]; if (selLoadChar === 'Enter') { @@ -1549,7 +1549,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const { text, paragraph } = schema.nodes; const selNode = this.EditorView.state.selection.$anchor.node(); if (this.EditorView.state.selection.from === 1 && this.EditorView.state.selection.empty && [undefined, text, paragraph].includes(selNode?.type)) { - const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })]; + const docDefaultMarks = [schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail() })]; this.EditorView.state.selection.empty && this.EditorView.state.selection.from === 1 && this.EditorView?.dispatch(this.EditorView?.state.tr.setStoredMarks(docDefaultMarks).removeStoredMark(schema.marks.pFontColor)); } } @@ -1685,7 +1685,7 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB if (e.clientX > boundsRect.left && e.clientX < boundsRect.right && e.clientY > boundsRect.bottom) { // if we clicked below the last prosemirror div, then set the selection to be the end of the document editorView.focus(); - editorView.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, editorView.state.doc.content.size))); + // editorView.dispatch(editorView.state.tr.setSelection(TextSelection.create(editorView.state.doc, editorView.state.doc.content.size))); } } else if (node && [editorView.state.schema.nodes.ordered_list, editorView.state.schema.nodes.listItem].includes(node.type) && node !== (editorView.state.selection as NodeSelection)?.node && pcords) { editorView.dispatch(editorView.state.tr.setSelection(NodeSelection.create(editorView.state.doc, pcords.pos))); @@ -1814,7 +1814,6 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB return; } if (this._enteringStyle && 'tix!'.includes(e.key)) { - const tag = e.key === 't' ? 'todo' : e.key === 'i' ? 'ignore' : e.key === 'x' ? 'disagree' : e.key === '!' ? 'important' : '??'; const node = state.selection.$from.nodeAfter; const start = state.selection.from; const end = state.selection.to; @@ -1823,9 +1822,8 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB StopEvent(e); _editorView.dispatch( state.tr - .removeMark(start, end, schema.marks.user_mark) - .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified: Math.floor(Date.now() / 1000) })) - .addMark(start, end, schema.marks.user_tag.create({ userid: ClientUtils.CurrentUserEmail(), tag, modified: Math.round(Date.now() / 1000 / 60) })) + .removeMark(start, end, schema.marks.user_mark) // + .addMark(start, end, schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail() })) ); return; } @@ -1858,9 +1856,12 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB break; default: if ([AclEdit, AclAugment, AclAdmin].includes(GetEffectiveAcl(this.Document))) { - const modified = Math.floor(Date.now() / 1000); - const mark = state.selection.$to.marks().find(m => m.type === schema.marks.user_mark && m.attrs.modified === modified); - _editorView.dispatch(state.tr.removeStoredMark(schema.marks.user_mark).addStoredMark(mark ?? schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail(), modified }))); + const mark = state.selection.$to.marks().find(m => m.type === schema.marks.user_mark); + _editorView.dispatch( + state.tr + .removeStoredMark(schema.marks.user_mark) // + .addStoredMark(mark ?? schema.marks.user_mark.create({ userid: ClientUtils.CurrentUserEmail() })) + ); } break; } @@ -1884,14 +1885,14 @@ export class FormattedTextBox extends ViewBoxAnnotatableComponent<FormattedTextB const margins = 2 * NumCast(this.layoutDoc._yMargin, this._props.yMargin || 0); const children = this.ProseRef?.children.length ? Array.from(this.ProseRef.children[0].children) : undefined; if (this.EditorView && children && !SnappingManager.IsDragging) { - const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), margins) ?? 0; + const getChildrenHeights = (kids: Element[] | undefined) => kids?.reduce((p, child) => p + toHgt(child), 0) ?? 0; const toNum = (val: string) => Number(val.replace('px', '')); const toHgt = (node: Element): number => { const { height, marginTop, marginBottom } = getComputedStyle(node); const childHeight = height === 'auto' ? getChildrenHeights(Array.from(node.children)) : toNum(height); return childHeight + Math.max(0, toNum(marginTop)) + Math.max(0, toNum(marginBottom)); }; - const proseHeight = !this.ProseRef ? 0 : getChildrenHeights(children); + const proseHeight = !this.ProseRef ? 0 : getChildrenHeights(children) + margins; const scrollHeight = this.ProseRef && proseHeight; if (this._props.setHeight && !this._props.suppressSetHeight && scrollHeight && !this._props.dontRegisterView) { // if top === 0, then the text box is growing upward (as the overlay caption) which doesn't contribute to the height computation diff --git a/src/client/views/nodes/formattedText/marks_rts.ts b/src/client/views/nodes/formattedText/marks_rts.ts index b7dae1ca3..333ee6be8 100644 --- a/src/client/views/nodes/formattedText/marks_rts.ts +++ b/src/client/views/nodes/formattedText/marks_rts.ts @@ -330,20 +330,6 @@ export const marks: { [index: string]: MarkSpec } = { return ['span', { class: 'UM-' + uid + remote + ' UM-min-' + min + ' UM-hr-' + hr + ' UM-day-' + day }, 0]; }, }, - // the id of the user who entered the text - user_tag: { - attrs: { - userid: { default: '' }, - modified: { default: 'when?' }, // 1 second intervals since 1970 - tag: { default: '' }, - }, - group: 'inline', - inclusive: false, - toDOM: node => { - const uid = node.attrs.userid.replace('.', '').replace('@', ''); - return ['span', { class: 'UT-' + uid + ' UT-' + node.attrs.tag }, 0]; - }, - }, // :: MarkSpec Code font mark. Represented as a `<code>` element. code: { |