diff options
Diffstat (limited to 'src/client/views/collections')
37 files changed, 443 insertions, 469 deletions
diff --git a/src/client/views/collections/CollectionCardDeckView.scss b/src/client/views/collections/CollectionCardDeckView.scss index e6cc398af..981e528cc 100644 --- a/src/client/views/collections/CollectionCardDeckView.scss +++ b/src/client/views/collections/CollectionCardDeckView.scss @@ -18,7 +18,7 @@ } .collectionCardView-flashcardUI { - top: 0; + top: 0px; position: absolute; width: 100%; height: 100%; @@ -33,7 +33,7 @@ } .collectionCardView-cardSizeDragger { position: absolute; - top: 0; + top: 0px; width: 28px; height: 28px; > svg { diff --git a/src/client/views/collections/CollectionCarousel3DView.scss b/src/client/views/collections/CollectionCarousel3DView.scss index 13e6b54c2..361d88cb6 100644 --- a/src/client/views/collections/CollectionCarousel3DView.scss +++ b/src/client/views/collections/CollectionCarousel3DView.scss @@ -12,7 +12,7 @@ position: absolute; top: global.$CAROUSEL3D_TOP * 1%; height: (global.$CAROUSEL3D_SIDE_SCALE * 100) * 1%; - align-items: center; + //align-items: center; transition: transform 0.3s cubic-bezier(0.455, 0.03, 0.515, 0.955); .collectionCarousel3DView-item, @@ -67,8 +67,8 @@ .carousel3DView-fwd-scroll-hidden { position: absolute; display: flex; - width: 30; - height: 30; + width: 30px; + height: 30px; align-items: center; border-radius: 5px; justify-content: center; @@ -78,7 +78,7 @@ .carousel3DView-fwd, .carousel3DView-back { - top: 0; + top: 0px; background: transparent; width: calc((1 - #{global.$CAROUSEL3D_CENTER_SCALE} * 0.33) / 2 * 100%); height: 100%; @@ -94,13 +94,13 @@ .carousel3DView-fwd, .carousel3DView-fwd-scroll, .carousel3DView-fwd-scroll-hidden { - right: 0; + right: 0px; } .carousel3DView-back, .carousel3DView-back-scroll, .carousel3DView-back-scroll-hidden { - left: 0; + left: 0px; } .carousel3DView-fwd-scroll-hidden, diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index 962b590c8..4c999b6dd 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -6,10 +6,10 @@ transform-origin: top left; .collectionCarouselView-caption { - height: 50; + height: 50px; display: inline-block; width: 100%; - bottom: 0; + bottom: 0px; position: absolute; } .collectionCarouselView-image { @@ -18,8 +18,8 @@ width: 100%; user-select: none; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; } } .collectionCarouselView-recentlyMissed { @@ -34,8 +34,8 @@ .carouselView-fwd { position: absolute; display: flex; - width: 30; - height: 30; + width: 30px; + height: 30px; align-items: center; border-radius: 5px; justify-content: center; @@ -47,12 +47,12 @@ } .carouselView-fwd { top: calc(50% - 15px); - right: 0; + right: 0px; transform-origin: right top; } .carouselView-back { top: calc(50% - 15px); - left: 0; + left: 0px; transform-origin: top left; } .carouselView-back:hover, diff --git a/src/client/views/collections/CollectionDockingView.scss b/src/client/views/collections/CollectionDockingView.scss index 7c19d39da..de214e2ef 100644 --- a/src/client/views/collections/CollectionDockingView.scss +++ b/src/client/views/collections/CollectionDockingView.scss @@ -17,8 +17,8 @@ } .lm_maximised { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 40; } .lm_maximise_placeholder { @@ -62,8 +62,8 @@ text-align: center; } .lm_header ul { - margin: 0; - padding: 0; + margin: 0px; + padding: 0px; list-style-type: none; } .lm_header .lm_tab { @@ -81,11 +81,11 @@ position: absolute; } .lm_header .lm_tab i.lm_left { - top: 0; + top: 0px; left: -2px; } .lm_header .lm_tab i.lm_right { - top: 0; + top: 0px; right: -2px; } .lm_header .lm_tab .lm_title { @@ -97,8 +97,8 @@ width: 14px; height: 14px; position: absolute; - top: 0; - right: 0; + top: 0px; + right: 0px; text-align: center; } .lm_stack.lm_left .lm_header, @@ -118,14 +118,14 @@ .lm_stack.lm_left .lm_header .lm_tabs, .lm_stack.lm_right .lm_header .lm_tabs { transform-origin: left top; - top: 0; + top: 0px; width: 1000px; } .lm_dragProxy.lm_left .lm_header .lm_controls, .lm_dragProxy.lm_right .lm_header .lm_controls, .lm_stack.lm_left .lm_header .lm_controls, .lm_stack.lm_right .lm_header .lm_controls { - bottom: 0; + bottom: 0px; } .lm_dragProxy.lm_left .lm_items, .lm_dragProxy.lm_right .lm_items, @@ -136,7 +136,7 @@ .lm_dragProxy.lm_left .lm_header .lm_tabs, .lm_stack.lm_left .lm_header .lm_tabs { transform: rotate(-90deg) scaleX(-1); - left: 0; + left: 0px; } .lm_dragProxy.lm_left .lm_header .lm_tabs .lm_tab, .lm_stack.lm_left .lm_header .lm_tabs .lm_tab { @@ -156,7 +156,7 @@ .lm_stack.lm_right .lm_header .lm_tabs { transform: rotate(90deg) scaleX(1); left: 100%; - margin-left: 0; + margin-left: 0px; } .lm_dragProxy.lm_right .lm_header .lm_controls, .lm_stack.lm_right .lm_header .lm_controls { @@ -169,7 +169,7 @@ } .lm_dragProxy.lm_bottom .lm_header .lm_tab, .lm_stack.lm_bottom .lm_header .lm_tab { - margin-top: 0; + margin-top: 0px; border-top: none; } .lm_dragProxy.lm_bottom .lm_header .lm_controls, @@ -189,8 +189,8 @@ } .lm_header .lm_controls .lm_tabdropdown:before { content: ''; - width: 0; - height: 0; + width: 0px; + height: 0px; vertical-align: middle; display: inline-block; border-top: 5px dashed; @@ -201,14 +201,14 @@ .lm_header .lm_tabdropdown_list { position: absolute; top: 20px; - right: 0; + right: 0px; z-index: 5; overflow: hidden; } .lm_header .lm_tabdropdown_list .lm_tab { clear: both; padding-right: 10px; - margin: 0; + margin: 0px; } .lm_header .lm_tabdropdown_list .lm_tab .lm_title { width: 100px; @@ -218,8 +218,8 @@ } .lm_dragProxy { position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 30; } .lm_dragProxy .lm_header { @@ -238,32 +238,32 @@ width: 100%; height: 100%; position: relative; - top: 0; - left: 0; + top: 0px; + left: 0px; } .lm_transition_indicator { display: none; width: 20px; height: 20px; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 20; } .lm_popin { width: 20px; height: 20px; position: absolute; - bottom: 0; - right: 0; + bottom: 0px; + right: 0px; z-index: 9999; } .lm_popin > * { width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; } .lm_popin > .lm_bg { z-index: 10; @@ -307,7 +307,7 @@ width: max-content; height: 100%; display: flex; - max-width: 100; + max-width: 100px; text-overflow: ellipsis; } @@ -328,7 +328,7 @@ ul.lm_tabs::before { content: ' '; position: absolute; - bottom: 0; + bottom: 0px; width: 100%; z-index: 1; pointer-events: none; @@ -349,9 +349,9 @@ ul.lm_tabs::before { } } .lm_header .lm_tab.lm_active { - padding: 0; + padding: 0px; opacity: 1; - margin: 0; + margin: 0px; box-shadow: none; height: 27px; margin-right: 2px; @@ -405,7 +405,7 @@ ul.lm_tabs::before { } .lm_drag_tab { - padding: 0; + padding: 0px; width: 15px !important; height: 15px !important; position: relative !important; @@ -418,7 +418,7 @@ ul.lm_tabs::before { .lm_close_tab { display: inline-flex !important; - padding: 0; + padding: 0px; opacity: 1 !important; align-self: center; margin-right: 5px; @@ -455,7 +455,7 @@ ul.lm_tabs::before { content: 'x'; margin: auto; position: relative; - top: -2; + top: -2px; font-size: medium; font-family: sans-serif; } @@ -478,8 +478,8 @@ ul.lm_tabs::before { width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; .collectionDockingView-drag { touch-action: none; @@ -514,7 +514,7 @@ ul.lm_tabs::before { border-bottom-left-radius: 10px; background: #93939347; z-index: 100; - //left: -3; + //left: -3px; &:hover { background: gray; color: white !important; @@ -524,7 +524,7 @@ ul.lm_tabs::before { content: '+'; margin: auto; font-size: x-large; - top: -4; + top: -4px; position: relative; } .lm_maximise { @@ -548,10 +548,10 @@ ul.lm_tabs::before { } .flexlayout__layout { - left: 0; - top: 0; - right: 0; - bottom: 0; + left: 0px; + top: 0px; + right: 0px; + bottom: 0px; position: absolute; overflow: hidden; } @@ -691,8 +691,8 @@ ul.lm_tabs::before { .flexlayout__tabset_header { position: absolute; - left: 0; - right: 0; + left: 0px; + right: 0px; color: #eee; background-color: #212121; padding: 3px 3px 3px 5px; @@ -702,17 +702,17 @@ ul.lm_tabs::before { .flexlayout__tab_header_inner { position: absolute; - left: 0; - top: 0; - bottom: 0; + left: 0px; + top: 0px; + bottom: 0px; width: 10000px; } .flexlayout__tab_header_outer { background-color: global.$dark-gray; position: absolute; - left: 0; - right: 0; + left: 0px; + right: 0px; /*top: 0px;*/ /*height: 100px;*/ overflow: hidden; @@ -731,23 +731,23 @@ ul.lm_tabs::before { display: flex; flex-direction: row-reverse; align-items: center; - top: 0; - bottom: 0; - right: 0; + top: 0px; + bottom: 0px; + right: 0px; } .flexlayout__tab_toolbar_button-min { width: 20px; height: 20px; border: none; - outline-width: 0; + outline-width: 0px; } .flexlayout__tab_toolbar_button-max { width: 20px; height: 20px; border: none; - outline-width: 0; + outline-width: 0px; } .flexlayout__popup_menu_item { @@ -870,9 +870,9 @@ ul.lm_tabs::before { display: flex; flex-direction: column-reverse; align-items: center; - bottom: 0; - left: 0; - right: 0; + bottom: 0px; + left: 0px; + right: 0px; } .flexlayout__border_toolbar_right { @@ -880,9 +880,9 @@ ul.lm_tabs::before { display: flex; flex-direction: column-reverse; align-items: center; - bottom: 0; - left: 0; - right: 0; + bottom: 0px; + left: 0px; + right: 0px; } .flexlayout__border_toolbar_top { @@ -890,9 +890,9 @@ ul.lm_tabs::before { display: flex; flex-direction: row-reverse; align-items: center; - top: 0; - bottom: 0; - right: 0; + top: 0px; + bottom: 0px; + right: 0px; } .flexlayout__border_toolbar_bottom { @@ -900,8 +900,8 @@ ul.lm_tabs::before { display: flex; flex-direction: row-reverse; align-items: center; - top: 0; - bottom: 0; - right: 0; + top: 0px; + bottom: 0px; + right: 0px; } } diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 89ccf5a0f..164c6e831 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -7,6 +7,7 @@ import { emptyFunction, numberRange } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; +import { StrCast } from '../../../fields/Types'; import { Docs } from '../../documents/Documents'; import { DragManager } from '../../util/DragManager'; import { CompileScript } from '../../util/Scripting'; @@ -16,6 +17,7 @@ import { undoBatch, undoable } from '../../util/UndoManager'; import { EditableView } from '../EditableView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import { DocumentView } from '../nodes/DocumentView'; +import { ImportElementBox } from '../nodes/importBox/ImportElementBox'; import { CollectionStackingView } from './CollectionStackingView'; import './CollectionStackingView.scss'; @@ -28,12 +30,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 +78,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 +86,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 +132,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 +253,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 && !StrCast(this._props.Doc.childLayoutString).includes(ImportElementBox.name) ? ( + <div className="collectionStackingView-addDocumentButton"> <EditableView GetValue={returnEmptyString} SetValue={this.addDocument} textCallback={this.textCallback} contents="+ NEW" /> </div> ) : null} @@ -272,11 +265,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 +330,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/CollectionNoteTakingView.scss b/src/client/views/collections/CollectionNoteTakingView.scss index 0d24a56b5..231085338 100644 --- a/src/client/views/collections/CollectionNoteTakingView.scss +++ b/src/client/views/collections/CollectionNoteTakingView.scss @@ -82,7 +82,7 @@ height: 100%; width: 100%; position: absolute; - top: 0; + top: 0px; overflow-y: auto; overflow-x: hidden; transition: top 0.5s; @@ -130,8 +130,8 @@ display: flex; flex-direction: column; align-items: center; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; position: absolute; margin: auto; @@ -152,20 +152,20 @@ } .collectionNoteTakingView-columnDragger { - width: 15; - height: 15; + width: 15px; + height: 15px; position: absolute; - margin-left: -5; + margin-left: -5px; } .collectionNoteTakingView-sectionDelete { display: none; position: absolute; - right: 0; + right: 0px; width: max-content; height: max-content; - top: 10; - padding: 2; + top: 10px; + padding: 2px; } // Documents in NoteTaking view @@ -210,8 +210,8 @@ height: 5px; &.active { - margin-left: 0; - margin-right: 0; + margin-left: 0px; + margin-right: 0px; background: red; } } @@ -303,8 +303,8 @@ .collectionNoteTakingView-sectionColor { position: absolute; - left: 0; - top: 0; + left: 0px; + top: 0px; height: 100%; display: none; @@ -345,8 +345,8 @@ .collectionNoteTakingView-sectionOptions { position: absolute; - right: 0; - top: 0; + right: 0px; + top: 0px; height: 100%; display: none; 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/CollectionStackedTimeline.scss b/src/client/views/collections/CollectionStackedTimeline.scss index d05c0ffde..e71df2164 100644 --- a/src/client/views/collections/CollectionStackedTimeline.scss +++ b/src/client/views/collections/CollectionStackedTimeline.scss @@ -38,7 +38,7 @@ height: 100%; background-color: global.$dark-gray; opacity: 0.3; - top: 0; + top: 0px; } .collectionStackedTimeline-trim-controls { @@ -49,8 +49,8 @@ display: flex; justify-content: space-between; max-width: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; .collectionStackedTimeline-trim-handle { background-color: global.$medium-blue; @@ -106,18 +106,18 @@ .collectionStackedTimeline-resizer { background: global.$dark-gray; position: absolute; - top: 0; + top: 0px; height: 100%; width: 10px; pointer-events: all; z-index: 100; } .collectionStackedTimeline-resizer { - right: 0; + right: 0px; cursor: e-resize; } .collectionStackedTimeline-left-resizer { - left: 0; + left: 0px; cursor: w-resize; } } @@ -126,8 +126,8 @@ position: absolute; width: 100%; height: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; pointer-events: none; } } diff --git a/src/client/views/collections/CollectionStackingView.scss b/src/client/views/collections/CollectionStackingView.scss index 05ac52ff9..2cf361847 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 { @@ -14,10 +16,10 @@ width: 100%; .collectionStackingView-columnDragger { - width: 28; - height: 28; - position: absolute; - margin-left: -5; + width: 28px; + height: 28px; + position: relative; + margin-left: -5px; z-index: 10; > svg { width: 100%; @@ -53,10 +55,9 @@ height: 100%; width: 100%; position: absolute; - top: 0; + top: 0px; overflow-y: auto; overflow-x: hidden; - flex-wrap: wrap; transition: top 0.5s; > div { @@ -92,8 +93,8 @@ .collectionStackingView-masonryGrid { width: 100%; display: grid; - top: 0; - left: 0; + top: 0px; + left: 0px; } .collectionStackingView-masonrySingle { @@ -114,8 +115,8 @@ position: absolute; display: flex; flex-direction: column; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; position: absolute; } @@ -158,12 +159,12 @@ width: 100%; display: none; position: absolute; - top: 0; + top: 0px; cursor: default; &.active { - margin-left: 0; - margin-right: 0; + margin-left: 0px; + margin-right: 0px; background: red; } } @@ -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 @@ -262,8 +262,8 @@ .collectionStackingView-sectionColor { position: absolute; - left: 0; - top: 0; + left: 0px; + top: 0px; height: 100%; display: none; @@ -304,8 +304,8 @@ .collectionStackingView-sectionOptions { position: absolute; - right: 0; - top: 0; + right: 0px; + top: 0px; height: 100%; display: none; @@ -339,7 +339,7 @@ .collectionStackingView-sectionDelete { position: absolute; right: 0px; - top: 0; + top: 0px; height: 100%; display: none; } @@ -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..bdeb7d944 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,28 +49,43 @@ 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 @observable _cursor: CSS.Property.Cursor = 'ew-resize'; // gets reset whenever we scroll. Not sure what it is @observable _scroll = 0; // used to force the document decoration to update when scrolling - // does this mean whether the browser is hidden? Or is chrome something else entirely? + // whether ui/editing controls are shown @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() { @@ -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} @@ -608,13 +566,15 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return ( <div key={(heading?.heading ?? '') + 'head'}> {this._props.isContentActive() && !this.isStackingView && !this.chromeHidden ? this.columnDragger : null} - <div style={{ top: this.yMargin }}> + <div style={{ position: 'relative' }}> <CollectionMasonryViewFieldRow 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,22 +707,25 @@ 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, }} - onScroll={action(e => { - this._scroll = e.currentTarget.scrollTop; - })} + onScroll={action(e => (this._scroll = e.currentTarget.scrollTop))} onDrop={this.onExternalDrop.bind(this)} 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 345f60e75..8c535534a 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -1,9 +1,10 @@ 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'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { RichTextField } from '../../../fields/RichTextField'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; @@ -26,7 +27,6 @@ import { EditableView } from '../EditableView'; import { DocumentView } from '../nodes/DocumentView'; import { ObservableReactComponent } from '../ObservableReactComponent'; import './CollectionStackingView.scss'; -import { DocData } from '../../../fields/DocSymbols'; // So this is how we are storing a column interface CSVFieldColumnProps { @@ -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/collections/CollectionTimeView.scss b/src/client/views/collections/CollectionTimeView.scss index d995cbcd2..d56999974 100644 --- a/src/client/views/collections/CollectionTimeView.scss +++ b/src/client/views/collections/CollectionTimeView.scss @@ -27,7 +27,7 @@ transform: rotate(45deg); display: inline-block; background: gray; - bottom: 0; + bottom: 0px; margin-bottom: -17px; border-radius: 9px; opacity: 0.25; @@ -67,9 +67,9 @@ pointer-events: all; padding: 5px; border: 1px solid black; - display:none; + display: none; span { - margin-left : 10px; + margin-left: 10px; } } @@ -86,8 +86,9 @@ } } -.collectionTimeView:hover, .collectionTimeView-pivot:hover { +.collectionTimeView:hover, +.collectionTimeView-pivot:hover { .pivotKeyEntry { - display:unset; + display: unset; } -}
\ No newline at end of file +} diff --git a/src/client/views/collections/CollectionTreeView.scss b/src/client/views/collections/CollectionTreeView.scss index 2a03ea708..95faaa3f0 100644 --- a/src/client/views/collections/CollectionTreeView.scss +++ b/src/client/views/collections/CollectionTreeView.scss @@ -11,7 +11,7 @@ height: 100%; width: 100%; position: relative; - top: 0; + top: 0px; // background: global.$light-gray; font-size: 13px; overflow: auto; @@ -33,12 +33,12 @@ } .no-indent { - padding-left: 0; + padding-left: 0px; //width: max-content; } .no-indent-outline { - padding-left: 0; + padding-left: 0px; width: 100%; } diff --git a/src/client/views/collections/CollectionView.scss b/src/client/views/collections/CollectionView.scss index 06c324bd0..837219e1d 100644 --- a/src/client/views/collections/CollectionView.scss +++ b/src/client/views/collections/CollectionView.scss @@ -1,7 +1,7 @@ @use '../global/globalCssVariables.module.scss' as global; .collectionView { - border-width: 0; + border-width: 0px; border-color: global.$light-gray; border-style: solid; border-radius: 0 0 global.$border-radius global.$border-radius; @@ -18,7 +18,7 @@ position: absolute; top: 55%; border: 1px black solid; - border-radius: 0; + border-radius: 0px; border-top-left-radius: 20px; border-bottom-left-radius: 20px; border-right: unset; @@ -31,8 +31,8 @@ width: 200px; height: 100%; position: absolute; - right: 0; - top: 0; + right: 0px; + top: 0px; border-left: solid 1px; z-index: 1; 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.scss b/src/client/views/collections/FlashcardPracticeUI.scss index 210c6798f..0cc4711b3 100644 --- a/src/client/views/collections/FlashcardPracticeUI.scss +++ b/src/client/views/collections/FlashcardPracticeUI.scss @@ -8,8 +8,8 @@ .FlashcardPracticeUI-check { position: absolute; display: flex; - width: 30; - height: 30; + width: 30px; + height: 30px; align-items: center; border-radius: 5px; justify-content: center; @@ -39,7 +39,7 @@ display: flex; top: 0px; left: 0px; - width: 30; + width: 30px; transform-origin: top left; border-radius: 5px; color: rgba(255, 255, 255, 0.5); @@ -49,7 +49,7 @@ width: 100%; display: flex; flex-direction: column; - top: 0; + top: 0px; position: relative; .FlashcardPracticeUI-quiz, .FlashcardPracticeUI-practice { 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/KeyRestrictionRow.tsx b/src/client/views/collections/KeyRestrictionRow.tsx deleted file mode 100644 index 7dc08389b..000000000 --- a/src/client/views/collections/KeyRestrictionRow.tsx +++ /dev/null @@ -1,72 +0,0 @@ -/* eslint-disable react/button-has-type */ -import { observable, runInAction } from 'mobx'; -import { observer } from 'mobx-react'; -import * as React from 'react'; - -interface IKeyRestrictionProps { - contains: boolean; - script: (value: string) => void; - field: string; - value: string; -} - -@observer -export default class KeyRestrictionRow extends React.Component<IKeyRestrictionProps> { - @observable private _key = this.props.field; - @observable private _value = this.props.value; - @observable private _contains = this.props.contains; - - render() { - if (this._key && this._value) { - let parsedValue: string | number = `"${this._value}"`; - const parsed = parseInt(this._value); - let type = 'string'; - if (!isNaN(parsed)) { - parsedValue = parsed; - type = 'number'; - } - const scriptText = `${this._contains ? '' : '!'}(((doc.${this._key} && (doc.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))) || - ((doc.data_ext && doc.data_ext.${this._key}) && (doc.data_ext.${this._key} as ${type})${type === 'string' ? '.includes' : '<='}(${parsedValue}))))`; - // let doc = new Doc(); - // ((doc.data_ext && doc.data_ext!.text) && (doc.data_ext!.text as string).includes("hello")); - this.props.script(scriptText); - } else { - this.props.script(''); - } - - return ( - <div className="collectionViewBaseChrome-viewSpecsMenu-row"> - <input - className="collectionViewBaseChrome-viewSpecsMenu-rowLeft" - value={this._key} - onChange={e => - runInAction(() => { - this._key = e.target.value; - }) - } - placeholder="KEY" - /> - <button - className="collectionViewBaseChrome-viewSpecsMenu-rowMiddle" - style={{ background: this._contains ? '#77dd77' : '#ff6961' }} - onClick={() => - runInAction(() => { - this._contains = !this._contains; - }) - }> - {this._contains ? 'CONTAINS' : 'DOES NOT CONTAIN'} - </button> - <input - className="collectionViewBaseChrome-viewSpecsMenu-rowRight" - value={this._value} - onChange={e => - runInAction(() => { - this._value = e.target.value; - }) - } - placeholder="VALUE" - /> - </div> - ); - } -} diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss index 931cdac2b..b705d17f3 100644 --- a/src/client/views/collections/TabDocView.scss +++ b/src/client/views/collections/TabDocView.scss @@ -92,6 +92,6 @@ input.lm_title { .miniMap-hidden { cursor: pointer; position: absolute; - bottom: 5; - right: 5; + bottom: 5px; + right: 5px; } diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index c4373aaa7..897a86e7f 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -584,9 +584,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { miniMapColor = () => Colors.MEDIUM_GRAY; tabView = () => this._view; disableMinimap = () => !this._document; - whenChildContentActiveChanges = (isActive: boolean) => { - this._isAnyChildContentActive = isActive; - }; + whenChildContentActiveChanges = (isActive: boolean) => (this._isAnyChildContentActive = isActive); isContentActiveFunc = () => this.isContentActive; waitForDoubleClick = () => (SnappingManager.ExploreMode ? 'never' : undefined); renderDocView = (doc: Doc) => ( diff --git a/src/client/views/collections/TreeView.scss b/src/client/views/collections/TreeView.scss index 78794d112..542b0cc87 100644 --- a/src/client/views/collections/TreeView.scss +++ b/src/client/views/collections/TreeView.scss @@ -136,8 +136,8 @@ width: 100%; height: 100%; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; z-index: 0; filter: opacity(0); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss index 7951aff65..32cf3586f 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.scss @@ -18,5 +18,5 @@ color: black; // fontStyle: "italic", margin-left: -12; - margin-top: 4; + margin-top: 4px; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 6c47a71b0..ac1ef7d65 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -2,8 +2,8 @@ .collectionfreeformview-none { position: inherit; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; transform-origin: left top; @@ -12,10 +12,10 @@ border-radius: inherit; } .collectionFreeForm-groupDropper { - width: 10000; - height: 10000; - left: -5000; - top: -5000; + width: 10000px; + height: 10000px; + left: -5000px; + top: -5000px; position: absolute; background: transparent; pointer-events: all; @@ -24,8 +24,8 @@ .collectionfreeformview-grid { transform-origin: top left; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; pointer-events: none; } @@ -219,8 +219,8 @@ border-radius: inherit; box-sizing: border-box; position: absolute; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; align-items: center; @@ -264,7 +264,7 @@ .collectionFreeform-infoUI { position: absolute; display: block; - top: 0; + top: 0px; color: white; background-color: #5075ef; @@ -275,19 +275,19 @@ padding: 10px; .collectionFreeform-infoUI-close { position: absolute; - top: -10; - left: -10; + top: -10px; + left: -10px; } .collectionFreeform-infoUI-msg { position: relative; - max-width: 500; - margin: 10; + max-width: 500px; + margin: 10px; } .collectionFreeform-infoUI-button { border-radius: 50px; font-size: 12px; - padding: 6; + padding: 6px; position: relative; } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 5bbe93a90..8f9b132e8 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -8,7 +8,7 @@ import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { AiOutlineSend } from 'react-icons/ai'; import ReactLoading from 'react-loading'; -import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils'; +import { ClientUtils, DashColor, lightOrDark, OmitKeys, returnFalse, returnTrue, returnZero, setupMoveUpEvents, UpdateIcon } from '../../../../ClientUtils'; import { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; import { DocData, DocLayout, Height, Width } from '../../../../fields/DocSymbols'; @@ -178,7 +178,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); @@ -344,7 +344,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); @@ -382,7 +382,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; @@ -397,7 +397,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 @@ -516,7 +516,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)); @@ -1273,7 +1273,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); @@ -1300,7 +1300,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(); @@ -1424,7 +1424,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; @@ -1507,7 +1507,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; @@ -1550,7 +1550,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} @@ -1735,15 +1735,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; @@ -1806,7 +1807,7 @@ 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._xMargin), NumCast(this.layoutDoc._yMargin)); } @@ -1968,8 +1969,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; @@ -1987,7 +1988,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; @@ -2046,7 +2047,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; @@ -2064,7 +2065,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] }; @@ -2072,13 +2073,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); @@ -2102,6 +2105,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } this.childDocs.some(doc => !this._renderCutoffData.get(doc[Id])) && setTimeout(this.incrementalRender, 1); }); + showBorderRounding = returnTrue; showPresPaths = () => SnappingManager.ShowPresPaths; brushedView = () => this._brushedView; gridColor = () => DashColor(lightOrDark(this.backgroundColor)).fade(0.5).toString(); // prettier-ignore @@ -2170,7 +2174,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} diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss index 0a001d84c..d0685e419 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.scss @@ -2,7 +2,7 @@ display: flex; height: max-content; flex-direction: column; - top: 0; + top: 0px; position: absolute; width: 100%; height: 100%; @@ -31,9 +31,9 @@ } .face-document-top { position: relative; - top: 0; + top: 0px; width: 100%; - left: 0; + left: 0px; } .face-document-image-container { @@ -69,8 +69,8 @@ .remove-item { position: absolute; - bottom: -5; - right: -5; + bottom: -5px; + right: -5px; background-color: rgba(0, 0, 0, 0.5); // Optional: to add a background behind the icon for better visibility border-radius: 30%; width: 10px; // Adjust size as needed @@ -98,7 +98,7 @@ .faceCollectionBox { width: 100%; height: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; position: absolute; } diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx index ff9fb14e7..e3a3f9b05 100644 --- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx +++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx @@ -160,15 +160,16 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { classifyImagesInBox = async () => { this.startLoading(); + const selectedImages = this._selectedImages; // Converts the images into a Base64 format, afterwhich the information is sent to GPT to label them. - const imageInfos = this._selectedImages.map(async doc => { + const imageInfos = selectedImages.map(async doc => { if (!doc.$tags_chat) { const url = ImageCastWithSuffix(doc[Doc.LayoutDataKey(doc)], '_o') ?? ''; return imageUrlToBase64(url).then(hrefBase64 => !hrefBase64 ? undefined : - gptImageLabel(hrefBase64,'Give three labels to describe this image.').then(labels => + gptImageLabel(hrefBase64, 'Give three labels to describe this image.').then(labels => ({ doc, labels }))) ; // prettier-ignore } }); diff --git a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx index abd828945..2ec59e5d5 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeOptionsMenu.tsx @@ -16,6 +16,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { public createCollection: (e: KeyboardEvent | React.PointerEvent | undefined, group?: boolean, selection?: Doc[]) => Doc | void = unimplementedFunction; public delete: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public summarize: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; + public generateScrapbook: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; public showMarquee: () => void = unimplementedFunction; public hideMarquee: () => void = unimplementedFunction; public pinWithView: (e: KeyboardEvent | React.PointerEvent | undefined) => void = unimplementedFunction; @@ -38,6 +39,7 @@ export class MarqueeOptionsMenu extends AntimodeMenu<AntimodeMenuProps> { <IconButton tooltip="Create a Collection" onPointerDown={this.createCollection} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} /> <IconButton tooltip="Create a Grouping" onPointerDown={e => this.createCollection(e, true)} icon={<FontAwesomeIcon icon="layer-group" />} color={this.userColor} /> <IconButton tooltip="Summarize Documents" onPointerDown={this.summarize} icon={<FontAwesomeIcon icon="compress-arrows-alt" />} color={this.userColor} /> + <IconButton tooltip="Generate Scrapbook" onPointerDown={this.generateScrapbook} icon={<FontAwesomeIcon icon="palette" />} color={this.userColor} /> <IconButton tooltip="Delete Documents" onPointerDown={this.delete} icon={<FontAwesomeIcon icon="trash-alt" />} color={this.userColor} /> <IconButton tooltip="Pin selected region" onPointerDown={this.pinWithView} icon={<FontAwesomeIcon icon="map-pin" />} color={this.userColor} /> <IconButton tooltip="Classify and Sort Images" onPointerDown={this.classifyImages} icon={<FontAwesomeIcon icon="object-group" />} color={this.userColor} /> diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.scss b/src/client/views/collections/collectionFreeForm/MarqueeView.scss index 7c9d0f6e1..135f4deac 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.scss +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.scss @@ -1,7 +1,7 @@ .marqueeView { position: inherit; - top: 0; - left: 0; + top: 0px; + left: 0px; width: 100%; height: 100%; overflow: hidden; @@ -20,7 +20,7 @@ pointer-events: none; .marquee-legend { bottom: -18px; - left: 0; + left: 0px; position: absolute; font-size: 9; white-space: nowrap; @@ -28,4 +28,4 @@ .marquee-legend::after { content: 'Press <space> for lasso'; } -} +}
\ No newline at end of file diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 3cc7c0f2d..ff78b332a 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -28,6 +28,9 @@ import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { SubCollectionViewProps } from '../CollectionSubView'; import { ImageLabelBoxData } from './ImageLabelBox'; import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; +import { StrListCast } from '../../../../fields/Doc'; +import { requestAiGeneratedPreset, DocumentDescriptor } from '../../nodes/scrapbook/AIPresetGenerator'; +import { buildPlaceholdersFromConfigs, slotRealDocIntoPlaceholders } from '../../nodes/scrapbook/ScrapbookBox'; import './MarqueeView.scss'; interface MarqueeViewProps { @@ -76,6 +79,11 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps @observable _labelsVisibile: boolean = false; @observable _lassoPts: [number, number][] = []; @observable _lassoFreehand: boolean = false; + // ─── New Observables for “Pick 1 of N AI Scrapbook” ─── + @observable aiChoices: Doc[] = []; // temporary hidden Scrapbook docs + @observable pickerX = 0; // popup x coordinate + @observable pickerY = 0; // popup y coordinate + @observable pickerVisible = false; // show/hide ScrapbookPicker @computed get Transform() { return this._props.getTransform(); @@ -190,6 +198,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(); } }; @@ -275,6 +284,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps MarqueeOptionsMenu.Instance.createCollection = this.collection; MarqueeOptionsMenu.Instance.delete = this.delete; MarqueeOptionsMenu.Instance.summarize = this.summary; + MarqueeOptionsMenu.Instance.generateScrapbook = this.generateScrapbook; MarqueeOptionsMenu.Instance.showMarquee = this.showMarquee; MarqueeOptionsMenu.Instance.hideMarquee = this.hideMarquee; MarqueeOptionsMenu.Instance.jumpTo(e.clientX, e.clientY); @@ -372,7 +382,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 +518,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; @@ -517,6 +527,77 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps MarqueeOptionsMenu.Instance.fadeOut(true); }); + getAiPresetsDescriptors = (): DocumentDescriptor[] => + this.marqueeSelect(false).map(doc => ({ + type: typeof doc.$type === 'string' ? doc.$type : 'UNKNOWN', + tags: Array.from(new Set(StrListCast(doc.$tags_chat))), + })); + + generateScrapbook = action(async () => { + const selectedDocs = this.marqueeSelect(false); + if (!selectedDocs.length) return; + + const descriptors = this.getAiPresetsDescriptors(); + if (descriptors.length === 0) { + alert('No documents selected to generate a scrapbook from!'); + return; + } + + const aiPreset = await requestAiGeneratedPreset(descriptors); + if (!aiPreset.length) { + alert('Failed to generate preset'); + return; + } + const scrapbookPlaceholders: Doc[] = buildPlaceholdersFromConfigs(aiPreset); + /* + const scrapbookPlaceholders: Doc[] = aiPreset.map(cfg => { + const placeholderDoc = Docs.Create.TextDocument(cfg.tag); + placeholderDoc.placeholder_docType = cfg.type as DocumentType; + placeholderDoc.placeholder_acceptTags = new List<string>(cfg.acceptTags ?? [cfg.tag]); + + const placeholder = new Doc(); + placeholder.proto = placeholderDoc; + placeholder.original = placeholderDoc; + placeholder.x = cfg.x; + placeholder.y = cfg.y; + if (cfg.width != null) placeholder._width = cfg.width; + if (cfg.height != null) placeholder._height = cfg.height; + + return placeholder; + });*/ + + const scrapbook = Docs.Create.ScrapbookDocument(scrapbookPlaceholders, { + backgroundColor: '#e2ad32', + x: this.Bounds.left, + y: this.Bounds.top, + _width: 500, + _height: 500, + title: 'AI-generated Scrapbook', + }); + + // 3) Now grab that new scrapbook’s flat placeholders + const allPlaceholders = DocUtils.unwrapPlaceholders(scrapbookPlaceholders); + + // 4) Slot each selectedDocs[i] into the first matching placeholder + selectedDocs.forEach(realDoc => slotRealDocIntoPlaceholders(realDoc, allPlaceholders)); + + const selected = selectedDocs.map(d => { + this._props.removeDocument?.(d); + d.x = NumCast(d.x) - this.Bounds.left; + d.y = NumCast(d.y) - this.Bounds.top; + return d; + }); + + this._props.addDocument?.(scrapbook); + const portal = Docs.Create.FreeformDocument(selected, { title: 'docs in scrapbook', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' }); + DocUtils.MakeLink(scrapbook, portal, { link_relationship: 'scrapbook of:in scrapbook' }); + + portal.hidden = true; + this._props.addDocument?.(portal); + MarqueeOptionsMenu.Instance.fadeOut(true); + this.hideMarquee(); + }); + @action marqueeCommand = (e: KeyboardEvent) => { const ee = e as unknown as KeyboardEvent & { propagationIsStopped?: boolean }; @@ -538,6 +619,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps if (e.key === 'g') this.collection(e, true); if (e.key === 'c' || e.key === 't') this.collection(e); if (e.key === 's' || e.key === 'S') this.summary(); + if (e.key === 'g' || e.key === 'G') this.generateScrapbook(); // ← scrapbook shortcut if (e.key === 'p') this.pileup(); this.cleanupInteractions(false); } @@ -682,25 +764,27 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }; render() { return ( - <div - className="marqueeView" - ref={r => { - r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject); - this.MarqueeRef = r; - }} - style={{ - overflow: StrCast(this._props.Document._overflow), - cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer', - }} - onDragOver={e => e.preventDefault()} - onScroll={e => { - e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0; - }} - onClick={this.onClick} - onPointerDown={this.onPointerDown}> - {this._visible ? this.marqueeDiv : null} - {this.props.children} - </div> + <> + <div + className="marqueeView" + ref={r => { + r?.addEventListener('dashDragMovePause', this.onDragMovePause as EventListenerOrEventListenerObject); + this.MarqueeRef = r; + }} + style={{ + overflow: StrCast(this._props.Document._overflow), + cursor: Doc.ActiveTool === InkTool.Ink || this._visible ? 'crosshair' : 'pointer', + }} + onDragOver={e => e.preventDefault()} + onScroll={e => { + e.currentTarget.scrollTop = e.currentTarget.scrollLeft = 0; + }} + onClick={this.onClick} + onPointerDown={this.onPointerDown}> + {this._visible ? this.marqueeDiv : null} + {this.props.children} + </div> + </> ); } } diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.scss b/src/client/views/collections/collectionGrid/CollectionGridView.scss index 4edaf9745..b95d3ea44 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.scss +++ b/src/client/views/collections/collectionGrid/CollectionGridView.scss @@ -39,8 +39,8 @@ background: #d3d3d3; position: absolute; - height: 3; - left: 5; + height: 3px; + left: 5px; transform-origin: left; transform: rotate(90deg); outline: none; @@ -133,7 +133,7 @@ input::-webkit-outer-spin-button, input::-webkit-inner-spin-button { -webkit-appearance: none; - margin: 0; + margin: 0px; } /* Firefox */ diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.scss b/src/client/views/collections/collectionLinear/CollectionLinearView.scss index 0dfaed38a..0053d3e60 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.scss +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.scss @@ -47,7 +47,7 @@ background: global.$medium-blue; display: flex; border-radius: 10px; - height: 35; + height: 35px; transform: translate3d(6px, 0px, 0px); align-content: center; justify-content: center; @@ -95,11 +95,11 @@ pointer-events: all; cursor: pointer; background-color: global.$medium-blue; - padding: 5; + padding: 5px; border-radius: 2px; height: 100%; - min-width: 25; - margin: 0; + min-width: 25px; + margin: 0px; color: global.$white; display: flex; font-weight: 100; diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss index 9ed247d50..1dc46102f 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.scss @@ -1,8 +1,8 @@ .collectionMulticolumnView_drop { height: 100%; width: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; position: absolute; .collectionMulticolumnView_contents { @@ -18,8 +18,8 @@ align-items: center; position: relative; > .iconButton-container { - top: 0; - left: 0; + top: 0px; + left: 0px; position: absolute; } diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss index 91779065d..eb157d155 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.scss @@ -1,8 +1,8 @@ .collectionMultirowView_drop { height: 100%; width: 100%; - top: 0; - left: 0; + top: 0px; + left: 0px; position: absolute; .collectionMultirowView_contents { diff --git a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx index 10a6fa2e9..2ff99f134 100644 --- a/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MulticolumnResizer.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx index 66215f109..4f57e1656 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowHeightLabel.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { computed } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; diff --git a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx index 918365700..1954b4743 100644 --- a/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx +++ b/src/client/views/collections/collectionMulticolumn/MultirowResizer.tsx @@ -1,4 +1,3 @@ -/* eslint-disable react/require-default-props */ import { action } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; @@ -66,7 +65,7 @@ export default class ResizeBar extends React.Component<ResizerProps> { style={{ pointerEvents: this.props.isContentActive?.() ? 'all' : 'none', height: this.props.height, - backgroundColor: !this.props.isContentActive?.() ? '' : this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor) as string, + backgroundColor: !this.props.isContentActive?.() ? '' : (this.props.styleProvider?.(undefined, undefined, StyleProp.WidgetColor) as string), }}> <div className="multiRowResizer-hdl" onPointerDown={e => this.registerResizing(e)} /> </div> diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 53c0823ea..e975ae6f6 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -18,7 +18,7 @@ .schema-add { position: relative; - height: 35; + height: 35px; display: flex; align-items: center; top: -10px; @@ -147,7 +147,7 @@ flex-direction: row; justify-content: space-between; align-items: center; - padding: 0; + padding: 0px; z-index: 1; border: 1px solid global.$medium-gray; @@ -231,7 +231,7 @@ overflow-x: hidden; overflow-y: auto; display: inline-flex; - padding: 0; + padding: 0px; align-items: center; input[type='text'] { border: unset; @@ -272,8 +272,8 @@ .row-menu-infos { position: absolute; - top: 3; - left: 3; + top: 3px; + left: 3px; z-index: 1; display: flex; justify-content: flex-end; |
