diff options
Diffstat (limited to 'src/client/views/collections')
18 files changed, 194 insertions, 260 deletions
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index d5edc3e0b..3dc7bc515 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -133,7 +133,7 @@ export class CollectionCardView extends CollectionSubView() { } @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 cardDeckWidth() { diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index ac1981012..3c5bc10de 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -206,7 +206,7 @@ export class CollectionCarouselView extends CollectionSubView() { marginLeft: this.captionMarginX, width: `calc(100% - ${this.captionMarginX * 2}px)`, }}> - <FormattedTextBox xPadding={10} yPadding={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={this.curDoc()} TemplateDataDocument={undefined} /> + <FormattedTextBox xMargin={10} yMargin={10} {...captionProps} fieldKey={carouselShowsCaptions} styleProvider={this.captionStyleProvider} Document={this.curDoc()} TemplateDataDocument={undefined} /> </div> )} </> 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/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index 4736070c3..5487315f0 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -102,13 +102,7 @@ export class CollectionPivotView extends CollectionSubView() { <div className="collectionTimeView-pivot" style={{ width: this._props.PanelWidth(), height: '100%' }}> {this.contents} <div style={{ right: 0, top: 0, position: 'absolute' }}> - <FieldsDropdown - Doc={this.Document} - selectFunc={fieldKey => { - this.layoutDoc._pivotField = fieldKey; - }} - placeholder={StrCast(this.layoutDoc._pivotField)} - /> + <FieldsDropdown Doc={this.Document} isInactive={!this._props.isContentActive()} selectFunc={fieldKey => (this.layoutDoc._pivotField = fieldKey)} placeholder={StrCast(this.layoutDoc._pivotField)} /> </div> </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 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> </> diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 994669734..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 = ''; @@ -71,6 +71,8 @@ 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; @@ -92,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._ele) this.props.refList.splice(this.props.refList.indexOf(this._ele), 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._ele && this.props.refList.push(this._ele); + 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 @@ -107,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; } @@ -191,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; @@ -313,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. @@ -363,9 +370,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< }}> <div key={`${heading}-stack`} + 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', @@ -398,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/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index e79d0a76d..01a8da313 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -9,7 +9,7 @@ import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; +import { BoolCast, Cast, DateCast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; @@ -356,12 +356,16 @@ export function CollectionSubView<X>() { return !!added; } if (de.complete.annoDragData) { - const dropCreator = de.complete.annoDragData.dropDocCreator; - de.complete.annoDragData.dropDocCreator = () => { - const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined); - this.addDocument(dropped); - return dropped; - }; + if (![de.complete.annoDragData.dragDocument.embedContainer, de.complete.annoDragData.dragDocument].includes(this.Document)) { + de.complete.annoDragData.dropDocCreator = () => this.getAnchor?.(true) || this.Document; + } else { + const dropCreator = de.complete.annoDragData.dropDocCreator; + de.complete.annoDragData.dropDocCreator = () => { + const dropped = dropCreator(this._props.isAnnotationOverlay ? this.Document : undefined); + this.addDocument(dropped); + return dropped; + }; + } return true; } return false; @@ -415,7 +419,7 @@ export function CollectionSubView<X>() { const tags = html.split('<'); if (tags[0] === '') tags.splice(0, 1); let img = tags[0].startsWith('img') ? tags[0] : tags.length > 1 && tags[1].startsWith('img') ? tags[1] : ''; - const cors = img.includes('corsProxy') ? img.match(/http.*corsProxy\//)![0] : ''; + const cors = img.includes('corsproxy') ? img.match(/http.*corsproxy\//)![0] : ''; img = cors ? img.replace(cors, '') : img; if (img) { const imgSrc = img.split('src="')[1].split('"')[0]; @@ -561,7 +565,17 @@ export function CollectionSubView<X>() { } const loading = Docs.Create.LoadingDocument(file, options); Doc.addCurrentlyLoading(loading); - DocUtils.uploadFileToDoc(file, {}, loading); + DocUtils.uploadFileToDoc(file, {}, loading).then(d => { + if (d && d?.type === DocumentType.IMG) { + const imgTemplate = DocCast(Doc.UserDoc().defaultImageLayout); + if (imgTemplate) { + const templateFieldKey = StrCast(imgTemplate.title); + d.layout_fieldKey = templateFieldKey; + d[templateFieldKey] = imgTemplate; + } + } + }); + return loading; }) )) diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index eb9caf29d..72c8c3f8c 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -113,7 +113,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr }; setupViewTypes(category: string, func: (type_collection: CollectionViewType) => Doc) { - if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking && !this.dataDoc.isGroup && !this.Document.annotationOn) { + if (!Doc.IsSystem(this.Document) && this.Document._type_collection !== CollectionViewType.Docking && !this.Document.annotationOn) { // prettier-ignore const subItems: ContextMenuProps[] = [ { description: 'Freeform', event: () => func(CollectionViewType.Freeform), icon: 'signature' }, diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx index 2f46c00bd..17b65334c 100644 --- a/src/client/views/collections/FlashcardPracticeUI.tsx +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -61,7 +61,8 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp get practiceField() { return this._props.fieldKey + "_practice"; } // prettier-ignore @computed get filterDoc() { return DocListCast(Doc.MyContextMenuBtns?.data).find(doc => doc.title === 'Filter'); } // prettier-ignore - @computed get practiceMode() { return this._props.allChildDocs().some(doc => doc._layout_flashcardType) ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore + @computed get hasFlashcards() { return this._props.allChildDocs().some(doc => doc._layout_flashcardType); } // prettier-ignore + @computed get practiceMode() { return this.hasFlashcards ? StrCast(this._props.layoutDoc.practiceMode) : ''; } // prettier-ignore btnHeight = () => NumCast(this.filterDoc?.height); btnWidth = () => (!this.filterDoc ? 1 : NumCast(this.filterDoc._width)); @@ -130,7 +131,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp const setColor = (mode: practiceMode) => (StrCast(this.practiceMode) === mode ? 'white' : 'lightgray'); const togglePracticeMode = (mode: practiceMode) => this.setPracticeMode(mode === this.practiceMode ? undefined : mode); - return !this._props.allChildDocs().some(doc => doc._layout_flashcardType) ? null : ( + return !this.hasFlashcards ? null : ( <div className="FlashcardPracticeUI-practiceModes" style={{ @@ -141,8 +142,8 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp type={Type.PRIM} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} + showUntilToggle={false} multiSelect={false} - toggleStatus={!!this.practiceMode} label="Practice" items={[ [practiceMode.QUIZ, 'file-pen', 'Practice flashcards using GPT'], @@ -160,8 +161,8 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp type={Type.PRIM} color={SnappingManager.userColor} background={SnappingManager.userVariantColor} + showUntilToggle={false} multiSelect={false} - toggleStatus={!!this.practiceMode} label={StrCast(this._props.layoutDoc.revealOp, flashcardRevealOp.FLIP)} items={[ ['reveal', StrCast(this._props.layoutDoc.revealOp) === flashcardRevealOp.SLIDE ? 'expand' : 'question', StrCast(this._props.layoutDoc.revealOp, flashcardRevealOp.FLIP)], diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 4348bc7dc..c4373aaa7 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -163,8 +163,8 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps childFiltersByRanges={CollectionDockingView.Instance?.childDocRangeFilters ?? returnEmptyFilter} searchFilterDocs={CollectionDockingView.Instance?.searchFilterDocs ?? returnEmptyDoclist} fitContentsToBox={returnTrue} - xPadding={this.xPadding} - yPadding={this.yPadding} + xMargin={this.xPadding} + yMargin={this.yPadding} /> <div className="miniOverlay" onPointerDown={this.miniDown}> <TabMiniThumb miniLeft={miniLeft} miniTop={miniTop} miniWidth={miniWidth} miniHeight={miniHeight} /> diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index fb2d0955f..5b2f1ff81 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -1042,8 +1042,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { disableBrushing={this.treeView._props.disableBrushing} hideLinkButton={BoolCast(this.treeView.Document.childHideLinkButton)} dontRegisterView={BoolCast(this.treeView.Document.childDontRegisterViews, this._props.dontRegisterView)} - xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} - yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} + xMargin={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} + yMargin={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} searchFilterDocs={returnEmptyDoclist} @@ -1148,8 +1148,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { moveDocument={this.move} removeDocument={this._props.removeDoc} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} - xPadding={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} - yPadding={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} + xMargin={NumCast(this.treeView.Document.childXPadding, this.treeView._props.childXPadding)} + yMargin={NumCast(this.treeView.Document.childYPadding, this.treeView._props.childYPadding)} addDocTab={this._props.addDocTab} pinToPres={this.treeView._props.pinToPres} disableBrushing={this.treeView._props.disableBrushing} diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 2364fd74a..3571dab1a 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -176,7 +176,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return renderableEles; } @computed get fitContentsToBox() { - return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay; + return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox || this.Document.freeform_isGroup) && !this.isAnnotationOverlay; } @computed get nativeWidth() { return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document); @@ -256,8 +256,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection override contentBounds = () => { const { x, y, r, b } = aggregateBounds( this._layoutElements.filter(e => e.bounds?.width && !e.bounds.z).map(e => e.bounds!), - NumCast(this.layoutDoc._xPadding, NumCast(this.layoutDoc._xMargin, this._props.xPadding ?? 0)), - NumCast(this.layoutDoc._yPadding, NumCast(this.layoutDoc._yMargin, this._props.yPadding ?? 0)) + NumCast(this.layoutDoc._xMargin, this._props.xMargin ?? 0), + NumCast(this.layoutDoc._yMargin, this._props.yMargin ?? 0) ); const [width, height] = [r - x, b - y]; return { @@ -342,7 +342,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection */ focusOnPoint = (options: FocusViewOptions) => { const { pointFocus, zoomTime, didMove } = options; - if (!this.Document.isGroup && pointFocus && !didMove) { + if (!this.Document.freeform_isGroup && pointFocus && !didMove) { const dfltScale = this.isAnnotationOverlay ? 1 : 0.25; if (this.layoutDoc[this.scaleFieldKey] !== dfltScale) { this.zoomSmoothlyAboutPt(this.screenToFreeformContentsXf.transformPoint(pointFocus.X, pointFocus.Y), dfltScale, zoomTime); @@ -380,7 +380,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection * @returns */ focus = (anchor: Doc, options: FocusViewOptions) => { - if (anchor.isGroup && !options.docTransform && options.contextPath?.length) { + if (Doc.IsFreeformGroup(anchor) && !options.docTransform && options.contextPath?.length) { // don't focus on group if there's a context path because we're about to focus on a group item // which will override any group focus. (If we allowed the group to focus, it would mark didMove even if there were no net movement) return undefined; @@ -395,7 +395,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } const xfToCollection = options?.docTransform ?? Transform.Identity(); const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; - const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !DocumentView.LightboxDoc()); + const cantTransform = this.fitContentsToBox || ((this.Document.freeform_isGroup || this.layoutDoc._lockedTransform) && !DocumentView.LightboxDoc()); const { panX, panY, scale } = cantTransform || (!options.willPan && !options.willZoomCentered) ? savedState : this.calculatePanIntoView(anchor, xfToCollection, options?.willZoomCentered ? (options?.zoomScale ?? 0.75) : undefined); // focus on the document in the collection @@ -514,7 +514,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._downTime = Date.now(); const scrollMode = e.altKey ? (Doc.UserDoc().freeformScrollMode === freeformScrollMode.Pan ? freeformScrollMode.Zoom : freeformScrollMode.Pan) : Doc.UserDoc().freeformScrollMode; if (e.button === 0 && (!(e.ctrlKey && !e.metaKey) || scrollMode !== freeformScrollMode.Pan) && this._props.isContentActive()) { - if (!this.Document.isGroup) { + if (!this.Document.freeform_isGroup) { // group freeforms don't pan when dragged -- instead let the event go through to allow the group itself to drag // prettier-ignore const hit = this._clusters.handlePointerDown(this.screenToFreeformContentsXf.transformPoint(e.clientX, e.clientY)); @@ -1259,15 +1259,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection /** * Adds the created drawing to the freeform canvas and sets the metadata. */ - addDrawing = (doc: Doc, opts: DrawingOptions, gptRes: string, x?: number, y?: number) => { - doc.$title = opts.text; - doc.$width = opts.size; - doc.$ai_drawing_input = opts.text; - doc.$ai_drawing_complexity = opts.complexity; - doc.$ai_drawing_colored = opts.autoColor; - doc.$ai_drawing_size = opts.size; - doc.$ai_drawing_data = gptRes; - doc.$ai = 'gpt'; + addDrawing = (doc: Doc, opts: DrawingOptions, x?: number, y?: number) => { + doc.$ai_prompt = opts.text; this._drawingContainer = doc; if (x !== undefined && y !== undefined) { [doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(x, y); @@ -1278,7 +1271,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action zoom = (pointX: number, pointY: number, deltaY: number): void => { - if (this.Document.isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return; + if (this.Document.freeform_isGroup || this.Document[(this._props.viewField ?? '_') + 'freeform_noZoom']) return; let deltaScale = deltaY > 0 ? 1 / 1.05 : 1.05; if (deltaScale < 0) deltaScale = -deltaScale; const [x, y] = this.screenToFreeformContentsXf.transformPoint(pointX, pointY); @@ -1305,7 +1298,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action onPointerWheel = (e: React.WheelEvent): void => { - if (this.Document.isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom + if (this.Document.freeform_isGroup || !this.isContentActive()) return; // group style collections neither pan nor zoom SnappingManager.TriggerUserPanned(); if (this.layoutDoc._Transform || this.Document.treeView_OutlineMode === TreeViewType.outline) return; e.stopPropagation(); @@ -1429,7 +1422,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @action zoomSmoothlyAboutPt(docpt: number[], scale: number, transitionTime = 500) { - if (this.Document.isGroup) return; + if (this.Document.freeform_isGroup) return; this.setPanZoomTransition(transitionTime); const screenXY = this.screenToFreeformContentsXf.inverse().transformPoint(docpt[0], docpt[1]); this.layoutDoc[this.scaleFieldKey] = scale; @@ -1512,7 +1505,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection removeDocument = (docs: Doc | Doc[], annotationKey?: string | undefined) => { const ret = !!this._props.removeDocument?.(docs, annotationKey); // if this is a group and we have fewer than 2 Docs, then just promote what's left to our parent and get rid of the group. - if (ret && DocListCast(this.dataDoc[annotationKey ?? this.fieldKey]).length < 2 && this.Document.isGroup) { + if (ret && DocListCast(this.dataDoc[annotationKey ?? this.fieldKey]).length < 2 && this.Document.freeform_isGroup) { this.promoteCollection(); } return ret; @@ -1555,7 +1548,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection searchFilterDocs={this.searchFilterDocs} isDocumentActive={childLayout.pointerEvents === 'none' ? returnFalse : this._props.childDocumentsActive?.() ? this._props.isDocumentActive : this.isContentActive} isContentActive={this.childContentsActive} - focus={this.Document.isGroup ? this.groupFocus : this.isAnnotationOverlay ? this._props.focus : this.focus} + focus={this.Document.freeform_isGroup ? this.groupFocus : this.isAnnotationOverlay ? this._props.focus : this.focus} addDocTab={this.addDocTab} addDocument={this._props.addDocument} removeDocument={this.removeDocument} @@ -1740,15 +1733,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }); PinDocView( anchor, - { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ? { ...pinProps.pinData, poslayoutview: pinProps.pinData.dataview } : {}), pannable: !this.Document.isGroup, collectionType: true, filters: true } }, + { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ? { ...pinProps.pinData, poslayoutview: pinProps.pinData.dataview } : {}), pannable: !this.Document.freeform_isGroup, collectionType: true, filters: true } }, this.Document ); if (addAsAnnotation) { - if (Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), null) !== undefined) { - Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), [])?.push(anchor); + const fieldKey = this._props.isAnnotationOverlay ? this._props.fieldKey : this._props.fieldKey + '_annotations'; + if (Cast(this.dataDoc[fieldKey], listSpec(Doc), null) !== undefined) { + Cast(this.dataDoc[fieldKey], listSpec(Doc), [])?.push(anchor); } else { - this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]); + this.dataDoc[fieldKey] = new List<Doc>([anchor]); } } return anchor; @@ -1773,9 +1767,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._firstRender = false; this._disposers.groupBounds = reaction( () => { - if (this.Document.isGroup && this.childDocs.length === this.childDocList?.length) { + if (this.Document.freeform_isGroup && this.childDocs.length === this.childDocList?.length) { const clist = this.childDocs.map(cd => ({ x: NumCast(cd.x), y: NumCast(cd.y), width: NumCast(cd._width), height: NumCast(cd._height) })); - return aggregateBounds(clist, NumCast(this.layoutDoc._xPadding), NumCast(this.layoutDoc._yPadding)); + return aggregateBounds(clist, NumCast(this.layoutDoc._xMargin), NumCast(this.layoutDoc._yMargin)); } return undefined; }, @@ -1935,8 +1929,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const appearance = ContextMenu.Instance.findByDescription('Appearance...'); const appearanceItems = appearance?.subitems ?? []; - !this.Document.isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' }); - !this.Document.isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' }); + !this.Document.freeform_isGroup && appearanceItems.push({ description: 'Reset View', event: this.resetView, icon: 'compress-arrows-alt' }); + !this.Document.freeform_isGroup && appearanceItems.push({ description: 'Toggle Auto Reset View', event: this.toggleResetView, icon: 'compress-arrows-alt' }); if (this._props.setContentViewBox === emptyFunction) { !appearance && ContextMenu.Instance.addItem({ description: 'Appearance...', subitems: appearanceItems, icon: 'eye' }); return; @@ -1954,7 +1948,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection !Doc.noviceMode && appearanceItems.push({ description: `update icon`, event: () => this.updateIcon(), icon: 'compress-arrows-alt' }); this._props.renderDepth && appearanceItems.push({ description: 'Ungroup collection', event: this.promoteCollection, icon: 'table' }); - this.Document.isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' }); + this.Document.freeform_isGroup && this.Document.transcription && appearanceItems.push({ description: 'Ink to text', event: this.transcribeStrokes, icon: 'font' }); !Doc.noviceMode ? appearanceItems.push({ description: 'Arrange contents in grid', event: this.layoutDocsInGrid, icon: 'table' }) : null; !Doc.noviceMode ? appearanceItems.push({ description: (this.Document._freeform_useClusters ? 'Hide' : 'Show') + ' Clusters', event: () => this._clusters.updateClusters(!this.Document._freeform_useClusters), icon: 'braille' }) : null; @@ -1977,7 +1971,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection event: action(() => { SmartDrawHandler.Instance.AddDrawing = this.addDrawing; SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing; - !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10) : SmartDrawHandler.Instance.hideRegenerate(); + !SmartDrawHandler.Instance.ShowRegenerate ? SmartDrawHandler.Instance.displayRegenerate(this._downX, this._downY - 10, NumCast(this.layoutDoc[this.scaleFieldKey])) : SmartDrawHandler.Instance.hideRegenerate(); }), icon: 'pen-to-square', }); @@ -2013,7 +2007,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; transcribeStrokes = undoable(() => { - if (this.Document.isGroup && this.Document.transcription) { + if (this.Document.freeform_isGroup && this.Document.transcription) { const text = StrCast(this.Document.transcription); const lines = text.split('\n'); const height = 30 + 15 * lines.length; @@ -2031,7 +2025,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection dragStarting = (snapToDraggedDoc: boolean = false, showGroupDragTarget: boolean = true, visited = new Set<Doc>()) => { if (visited.has(this.Document)) return; visited.add(this.Document); - showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.isGroup)); + showGroupDragTarget && (this.GroupChildDrag = BoolCast(this.Document.freeform_isGroup)); const activeDocs = this.getActiveDocuments(); const size = this.screenToFreeformContentsXf.transformDirection(this._props.PanelWidth(), this._props.PanelHeight()); const selRect = { left: this.panX() - size[0] / 2, top: this.panY() - size[1] / 2, width: size[0], height: size[1] }; @@ -2039,13 +2033,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const isDocInView = (doc: Doc, rect: { left: number; top: number; width: number; height: number }) => intersectRect(docDims(doc), rect); const snappableDocs = activeDocs.filter(doc => doc.z === undefined && isDocInView(doc, selRect)); // first see if there are any foreground docs to snap to - activeDocs.filter(doc => doc.isGroup && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)).forEach(doc => DocumentView.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited)); + activeDocs + .filter(doc => Doc.IsFreeformGroup(doc) && SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)) + .forEach(doc => DocumentView.getDocumentView(doc)?.ComponentView?.dragStarting?.(snapToDraggedDoc, false, visited)); const horizLines: number[] = []; const vertLines: number[] = []; const invXf = this.screenToFreeformContentsXf.inverse(); snappableDocs - .filter(doc => !doc.isGroup && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)))) + .filter(doc => !Doc.IsFreeformGroup(doc) && (snapToDraggedDoc || (SnappingManager.IsResizing !== doc[Id] && !DragManager.docsBeingDragged.includes(doc)))) .forEach(doc => { const { left, top, width, height } = docDims(doc); const topLeftInScreen = invXf.transformPoint(left, top); @@ -2137,7 +2133,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection {...this._props} ref={this._marqueeViewRef} Doc={this.Document} - ungroup={this.Document.isGroup ? this.promoteCollection : undefined} + ungroup={this.Document.freeform_isGroup ? this.promoteCollection : undefined} nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge} addDocTab={this.addDocTab} slowLoadDocuments={this.slowLoadDocuments} @@ -2182,9 +2178,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection placeholder={this._drawingFillInput || StrCast(this.Document.title) || 'Describe image'} type="text" value={this._drawingFillInput} - onChange={action(e => { - this._drawingFillInput = e.target.value; - })} + onChange={action(e => (this._drawingFillInput = e.target.value))} /> <div className="collectionFreeFormView-aiView-strength"> <span className="collectionFreeFormView-aiView-similarity">Similarity</span> @@ -2213,11 +2207,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onClick={undoable( action(() => { this._drawingFillLoading = true; - DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._drawingFillInput || StrCast(this.Document.title))?.then( - action(() => { - this._drawingFillLoading = false; - }) - ); + DrawingFillHandler.drawingToImage(this.props.Document, this._fireflyRefStrength, this._drawingFillInput || StrCast(this.Document.title))?.then(action(() => (this._drawingFillLoading = false))); }), 'create image' )} @@ -2225,37 +2215,6 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection </div> </div> </div> - <div className="collectionfreeformview-aiView-regenerate-container"> - <span className="collectionfreeformview-aiView-subtitle">Regenerate</span> - <div className="collectionfreeformview-aiView-regenerate"> - <input - className="collectionfreeformview-aiView-input" - aria-label="Edit instructions input" - type="text" - value={this._regenInput} - onChange={action(e => { - this._regenInput = e.target.value; - })} - placeholder="..under development.." - /> - <div className="collectionFreeFormView-aiView-regenBtn"> - <Button - text="Regenerate" - type={Type.SEC} - icon={this._regenLoading ? <ReactLoading type="spin" color={SettingsManager.userVariantColor} width={16} height={20} /> : <AiOutlineSend />} - iconPlacement="right" - // onClick={action(async () => { - // this._regenLoading = true; - // SmartDrawHandler.Instance.CreateDrawingDoc = this.createDrawingDoc; - // SmartDrawHandler.Instance.AddDrawing = this.addDrawing; - // SmartDrawHandler.Instance.RemoveDrawing = this.removeDrawing; - // await SmartDrawHandler.Instance.regenerate([this.Document], undefined, undefined, this._regenInput, true); - // this._regenLoading = false; - // })} - /> - </div> - </div> - </div> </div> ); }; diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3cc7c0f2d..c120cddf0 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -190,6 +190,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps FormattedTextBox.SelectOnLoadChar = Doc.UserDoc().defaultTextLayout && !this._props.childLayoutString ? e.key : ''; FormattedTextBox.LiveTextUndo = UndoManager.StartBatch('type new note'); this._props.addLiveTextDocument(DocUtils.GetNewTextDoc('-typed text-', x, y, 200, 100)); + setTimeout(() => FormattedTextBox.LiveTextUndo?.end(), 100); e.stopPropagation(); } }; @@ -372,7 +373,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps ? creator(selected, { title: 'nested stack' }) : ((doc: Doc) => { doc.$data = new List<Doc>(selected); - doc.$isGroup = makeGroup; + doc.$freeform_isGroup = makeGroup; doc.$title = makeGroup ? 'grouping' : 'nested freeform'; doc._freeform_panX = doc._freeform_panY = 0; return doc; @@ -508,7 +509,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps _layout_showSidebar: true, title: 'overview', }); - const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' }); + const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, freeform_isGroup: true, backgroundColor: 'transparent' }); DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' }); portal.hidden = true; diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index 1d5e70be7..b837b3a86 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -58,7 +58,7 @@ export class CollectionGridView extends CollectionSubView() { 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() { return NumCast(this.Document._gridGap, 10); @@ -206,7 +206,7 @@ export class CollectionGridView extends CollectionSubView() { setContentViewBox={emptyFunction} whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} onClickScript={this.onChildClickHandler} - dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'} + dontCenter={StrCast(this.layoutDoc.layout_dontCenter, StrCast(childLayout.layout_dontCenter)) as 'x' | 'y' | 'xy'} showTags={BoolCast(this.layoutDoc.showChildTags) || BoolCast(this.Document._layout_showTags)} /> ); diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 3c2a99b1e..d0a1e6f0d 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -27,7 +27,7 @@ import './CollectionLinearView.scss'; /** * CollectionLinearView is the class for rendering the horizontal collection * of documents, it useful for horizontal menus. It can either be expandable - * or not using the linearView_Expandable field. + * or not using the linearView_expandable field. * It is used in the following locations: * - It is used in the popup menu on the bottom left (see docButtons() in MainView.tsx) * - It is used for the context sensitive toolbar at the top (see contMenuButtons() in CollectionMenu.tsx) @@ -52,7 +52,7 @@ export class CollectionLinearView extends CollectionSubView() { componentDidMount() { this._widthDisposer = reaction( - () => 5 + NumCast(this.dataDoc.linearBtnWidth, this.dimension()) + (this.layoutDoc.linearView_IsOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0), + () => 5 + NumCast(this.dataDoc.linearView_btnWidth, this.dimension()) + (this.layoutDoc.linearView_isOpen ? this.childDocs.filter(doc => !doc.hidden).reduce((tot, doc) => (NumCast(doc._width) || this.dimension()) + tot + 4, 0) : 0), width => { this.childDocs.length && (this.layoutDoc._width = width); }, @@ -208,7 +208,7 @@ export class CollectionLinearView extends CollectionSubView() { render() { const flexDir = StrCast(this.Document.flexDirection); // Specify direction of linear view content const flexGap = NumCast(this.Document.flexGap); // Specify the gap between linear view content - const isExpanded = BoolCast(this.layoutDoc.linearView_IsOpen); + const isExpanded = BoolCast(this.layoutDoc.linearView_isOpen); const menuOpener = ( <Toggle @@ -219,9 +219,9 @@ export class CollectionLinearView extends CollectionSubView() { type={Type.TERT} onPointerDown={e => e.stopPropagation()} toggleType={ToggleType.BUTTON} - toggleStatus={BoolCast(this.layoutDoc.linearView_IsOpen)} + toggleStatus={BoolCast(this.layoutDoc.linearView_isOpen)} onClick={() => { - this.layoutDoc.linearView_IsOpen = !isExpanded; + this.layoutDoc.linearView_isOpen = !isExpanded; ScriptCast(this.Document.onClick)?.script.run({ this: this.Document }, console.log); }} tooltip={isExpanded ? 'Close' : 'Open'} @@ -231,10 +231,10 @@ export class CollectionLinearView extends CollectionSubView() { ); return ( - <div className={`collectionLinearView-outer ${this.layoutDoc.linearView_SubMenu}`} style={{ backgroundColor: this.layoutDoc.linearView_IsOpen ? undefined : 'transparent' }}> + <div className="collectionLinearView-outer" style={{ backgroundColor: this.layoutDoc.linearView_isOpen ? undefined : 'transparent' }}> <div className="collectionLinearView" ref={this.createDashEventsTarget} onContextMenu={this.myContextMenu} style={{ minHeight: this.dimension(), pointerEvents: 'all' }}> - {!this.layoutDoc.linearView_Expandable ? null : menuOpener} - {!this.layoutDoc.linearView_IsOpen ? null : ( + {!this.layoutDoc.linearView_expandable ? null : menuOpener} + {!this.layoutDoc.linearView_isOpen && this.layoutDoc.linearView_expandable ? null : ( <div className="collectionLinearView-content" style={{ diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index c06391f35..6442385c0 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -359,7 +359,7 @@ export class CollectionSchemaView extends CollectionSubView() { @action addNewKey = (key: string, defaultVal: FieldType | undefined) => { this.childDocs.forEach(doc => { - doc[DocData][key] = defaultVal; + if (doc[DocData][key] === undefined) doc[DocData][key] = defaultVal; }); }; diff --git a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx index 16d33eb93..134f2ed31 100644 --- a/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx +++ b/src/client/views/collections/collectionSchema/SchemaColumnHeader.tsx @@ -115,12 +115,11 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea }; const readOnly = this.getFinfo(fieldKey)?.readOnly ?? false; const cursor = !readOnly ? 'text' : 'default'; - const pointerEvents: 'all' | 'none' = 'all'; - return { color, fieldProps, cursor, pointerEvents }; + return { color, fieldProps, cursor }; }; @computed get editableView() { - const { color, fieldProps, pointerEvents } = this.renderProps(this._props); + const { color, fieldProps } = this.renderProps(this._props); return ( <div @@ -132,7 +131,6 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea style={{ color, width: '100%', - pointerEvents, }}> <EditableView ref={r => { @@ -232,6 +230,7 @@ export class SchemaColumnHeader extends ObservableReactComponent<SchemaColumnHea className="schema-column-header" style={{ width: this._props.columnWidths[this._props.columnIndex], + pointerEvents: this.props.isContentActive() ? undefined : 'none', }} onPointerEnter={() => { this.handlePointerEnter(); |
