diff options
Diffstat (limited to 'src/client/views/collections')
39 files changed, 626 insertions, 557 deletions
diff --git a/src/client/views/collections/CollectionCardDeckView.tsx b/src/client/views/collections/CollectionCardDeckView.tsx index 756b37f99..50de7c601 100644 --- a/src/client/views/collections/CollectionCardDeckView.tsx +++ b/src/client/views/collections/CollectionCardDeckView.tsx @@ -23,7 +23,8 @@ import { undoable, UndoManager } from '../../util/UndoManager'; import { PinDocView, PinProps } from '../PinFuncs'; import { StyleProp } from '../StyleProp'; import { TagItem } from '../TagsView'; -import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { DocumentViewProps } from '../nodes/DocumentContentsView'; +import { DocumentView } from '../nodes/DocumentView'; import { FocusViewOptions } from '../nodes/FocusViewOptions'; import './CollectionCardDeckView.scss'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; diff --git a/src/client/views/collections/CollectionCarouselView.scss b/src/client/views/collections/CollectionCarouselView.scss index 544b3e262..962b590c8 100644 --- a/src/client/views/collections/CollectionCarouselView.scss +++ b/src/client/views/collections/CollectionCarouselView.scss @@ -1,12 +1,16 @@ .collectionCarouselView-outer { height: 100%; - position: relative; + position: absolute; overflow: hidden; display: flex; + transform-origin: top left; + .collectionCarouselView-caption { height: 50; display: inline-block; width: 100%; + bottom: 0; + position: absolute; } .collectionCarouselView-image { height: calc(100% - 50px); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index a7d217076..975dc52fe 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -77,7 +77,8 @@ export class CollectionCarouselView extends CollectionSubView() { focus = (anchor: Doc, options: FocusViewOptions): Opt<number> => { const docs = DocListCast(this.Document[this.fieldKey]); if (anchor.type === DocumentType.CONFIG || docs.includes(anchor)) { - const newIndex = anchor.config_carousel_index ?? docs.getIndex(DocCast(anchor.annotationOn, anchor)); + const annoOn = DocCast(anchor.annotationOn, anchor); + const newIndex = NumCast(anchor.config_carousel_index, (annoOn && docs.getIndex(annoOn)) ?? 0); options.didMove = newIndex !== this.layoutDoc._carousel_index; options.didMove && (this.layoutDoc._carousel_index = newIndex); } @@ -115,12 +116,14 @@ export class CollectionCarouselView extends CollectionSubView() { ? false : undefined; // prettier-ignore onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); + renderDoc = (doc: Doc, showCaptions: boolean, overlayFunc?: (r: DocumentView | null) => void) => { return ( <DocumentView {...this._props} ref={overlayFunc} Document={doc} + TemplateDataDocument={doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined} NativeWidth={returnZero} NativeHeight={returnZero} fitWidth={this._props.childLayoutFitWidth} @@ -136,7 +139,6 @@ export class CollectionCarouselView extends CollectionSubView() { renderDepth={this._props.renderDepth + 1} LayoutTemplate={this._props.childLayoutTemplate} LayoutTemplateString={this._props.childLayoutString} - TemplateDataDocument={DocCast(Doc.Layout(doc).resolvedDataDoc)} childFilters={this.childDocFilters} focus={this.focus} hideDecorations={BoolCast(this.layoutDoc.layout_hideDecorations)} @@ -182,7 +184,15 @@ export class CollectionCarouselView extends CollectionSubView() { } @computed get content() { - const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined }; + const captionProps = { + ...this._props, // + NativeScaling: returnOne, + PanelWidth: this.captionWidth, + fieldKey: 'caption', + setHeight: undefined, + setContentView: undefined, + noSidebar: true, + }; const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption); return !this.curDoc() ? null : ( <> @@ -242,11 +252,9 @@ export class CollectionCarouselView extends CollectionSubView() { color: this._props.styleProvider?.(this.layoutDoc, this._props, StyleProp.Color) as string, left: NumCast(this.layoutDoc._xMargin), top: NumCast(this.layoutDoc._yMargin), - transformOrigin: 'top left', transform: `scale(${this.nativeScaling()})`, width: `calc(${100 / this.nativeScaling()}% - ${(2 * NumCast(this.layoutDoc._xMargin)) / this.nativeScaling()}px)`, height: `calc(${100 / this.nativeScaling()}% - ${(2 * NumCast(this.layoutDoc._yMargin)) / this.nativeScaling()}px)`, - position: 'relative', }}> {this.content} {this.navButtons} diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index e51bc18ef..ea6259a32 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -5,7 +5,7 @@ import * as ReactDOM from 'react-dom/client'; import ResizeObserver from 'resize-observer-polyfill'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, incrementTitleCopy, returnTrue, UpdateIcon } from '../../../ClientUtils'; import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; -import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols'; +import { AclAdmin, AclEdit } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; @@ -60,7 +60,7 @@ export class CollectionDockingView extends CollectionSubView() { public get HasFullScreen() { return this._goldenLayout._maximisedItem !== null; } - static _highlightStyleSheet = addStyleSheet(); + static _highlightStyleSheet = addStyleSheet().sheet; constructor(props: SubCollectionViewProps) { super(props); @@ -473,15 +473,15 @@ export class CollectionDockingView extends CollectionSubView() { } return undefined; } - public static async TakeSnapshot(doc: Doc | undefined, clone = false) { + public static TakeSnapshot(doc: Doc | undefined, clone = false) { if (!doc) return undefined; let json = StrCast(doc.dockingConfig); if (clone) { - const cloned = await Doc.MakeClone(doc); + const cloned = Doc.MakeClone(doc); Array.from(cloned.map.entries()).forEach(entry => { json = json.replace(entry[0], entry[1][Id]); }); - cloned.clone[DocData].dockingConfig = json; + cloned.clone.$dockingConfig = json; return DashboardView.openDashboard(cloned.clone); } const matches = json.match(/"documentId":"[a-z0-9-]+"/g); @@ -495,7 +495,7 @@ export class CollectionDockingView extends CollectionSubView() { const newtab = origtabdocs.length ? Doc.MakeCopy(origtab, true, undefined, true) : Doc.MakeEmbedding(origtab); const newtabdocs = origtabdocs.map(origtabdoc => Doc.MakeEmbedding(origtabdoc)); if (newtabdocs.length) { - newtab[DocData].data = new List<Doc>(newtabdocs); + newtab.$data = new List<Doc>(newtabdocs); newtabdocs.forEach(ntab => Doc.SetContainer(ntab, newtab)); } json = json.replace(origtab[Id], newtab[Id]); @@ -503,9 +503,8 @@ export class CollectionDockingView extends CollectionSubView() { }); const dashboardDoc = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); - dashboardDoc.pane_count = 1; - dashboardDoc.myOverlayDocs = new List<Doc>(); - dashboardDoc.myPublishedDocs = new List<Doc>(); + dashboardDoc.$myOverlayDocs = new List<Doc>(); + dashboardDoc.$myPublishedDocs = new List<Doc>(); DashboardView.SetupDashboardTrails(); DashboardView.SetupDashboardCalendars(); // Zaul TODO: needed? @@ -525,11 +524,11 @@ export class CollectionDockingView extends CollectionSubView() { this._flush = this._flush ?? UndoManager.StartBatch('tab movement'); const dashDoc = tab.DashDoc; if (dashDoc && ![DocumentType.PRES].includes(dashDoc.type) && !tab.contentItem.config.props.keyValue) { - Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true); + Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', dashDoc, undefined, undefined, true); // if you close a tab that is not embedded somewhere else (an embedded Doc can be opened simultaneously in a tab), then add the tab to recently closed if (dashDoc.embedContainer === this.Document) dashDoc.embedContainer = undefined; if (!dashDoc.embedContainer) { - Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true); + Doc.MyRecentlyClosed && Doc.AddDocToList(Doc.MyRecentlyClosed, 'data', dashDoc, undefined, true, true); Doc.RemoveEmbedding(dashDoc, dashDoc); } } @@ -555,15 +554,15 @@ export class CollectionDockingView extends CollectionSubView() { stack.header?.element.on('mousedown', (e: MouseEvent) => { const dashboard = Doc.ActiveDashboard; if (dashboard && e.target === stack.header?.element[0] && e.button === 2) { - dashboard.pane_count = NumCast(dashboard.pane_count) + 1; + dashboard.$myPaneCount = NumCast(dashboard.$myPaneCount) + 1; const docToAdd = Docs.Create.FreeformDocument([], { _width: this._props.PanelWidth(), _height: this._props.PanelHeight(), _freeform_backgroundGrid: true, _layout_fitWidth: true, - title: `Untitled Tab ${NumCast(dashboard.pane_count)}`, + title: `Untitled Tab ${NumCast(dashboard.$myPaneCount)}`, }); - Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); + Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); inheritParentAcls(this.Document, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } @@ -572,15 +571,15 @@ export class CollectionDockingView extends CollectionSubView() { const addNewDoc = undoable(() => { const dashboard = Doc.ActiveDashboard; if (dashboard) { - dashboard.pane_count = NumCast(dashboard.pane_count) + 1; + dashboard.$myPaneCount = NumCast(dashboard.$myPaneCount) + 1; const docToAdd = Docs.Create.FreeformDocument([], { _width: this._props.PanelWidth(), _height: this._props.PanelHeight(), _layout_fitWidth: true, _freeform_backgroundGrid: true, - title: `Untitled Tab ${NumCast(dashboard.pane_count)}`, + title: `Untitled Tab ${NumCast(dashboard.$myPaneCount)}`, }); - Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); + Doc.MyHeaderBar && Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); inheritParentAcls(this.dataDoc, docToAdd, false); CollectionDockingView.AddSplit(docToAdd, OpenWhereMod.none, stack); } @@ -669,7 +668,5 @@ ScriptingGlobals.add( ); // eslint-disable-next-line prefer-arrow-callback ScriptingGlobals.add(async function snapshotDashboard() { - const batch = UndoManager.StartBatch('snapshot'); - await CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard); - batch.end(); + undoable(() => CollectionDockingView.TakeSnapshot(Doc.ActiveDashboard), 'snapshot dashboard'); }, 'creates a snapshot copy of a dashboard'); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 996626575..89ccf5a0f 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -5,7 +5,6 @@ import * as React from 'react'; import { returnEmptyString, returnFalse, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction, numberRange } from '../../../Utils'; import { Doc } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; import { Docs } from '../../documents/Documents'; @@ -23,7 +22,7 @@ import './CollectionStackingView.scss'; interface CMVFieldRowProps { rows: () => number; headings: () => object[]; - Document: Doc; + Doc: Doc; chromeHidden?: boolean; heading: string; headingObject: SchemaHeaderField | undefined; @@ -74,7 +73,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.Document); + 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); this._ele = ele; }; @@ -164,7 +163,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF const { pivotField } = this._props; const newDoc = Docs.Create.TextDocument(value, { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value }); DocumentView.SetSelectOnLoad(newDoc); - pivotField && (newDoc[DocData][pivotField] = this.getValue(this._props.heading)); + pivotField && (newDoc['$' + pivotField] = this.getValue(this._props.heading)); const docs = this._props.parent.childDocList; return docs ? !!docs.splice(0, 0, newDoc) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list) }; @@ -190,7 +189,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF }; headerMove = (e: PointerEvent) => { - const embedding = Doc.MakeEmbedding(this._props.Document); + const embedding = Doc.MakeEmbedding(this._props.Doc); const key = this._props.pivotField; let value = this.getValue(this.heading); value = typeof value === 'string' ? `"${value}"` : value; @@ -290,7 +289,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF const key = this._props.pivotField; const evContents = this.heading ? this.heading : this._props.type && this._props.type === 'number' ? '0' : `NO ${key.toUpperCase()} VALUE`; const editableHeaderView = <EditableView GetValue={() => evContents} SetValue={this.headingChanged} contents={evContents} oneLine />; - return this._props.Document.miniHeaders ? ( + return this._props.Doc.miniHeaders ? ( <div className="collectionStackingView-miniHeader">{editableHeaderView}</div> ) : !this._props.headingObject ? null : ( <div className="collectionStackingView-sectionHeader" ref={this._headerRef}> diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index de999c91a..1576a8e4a 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1,5 +1,3 @@ -/* eslint-disable react/no-unused-class-component-methods */ -/* eslint-disable react/sort-comp */ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from '@dash/components'; @@ -9,7 +7,6 @@ import * as React from 'react'; import { ClientUtils, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Opt, returnEmptyDoclist } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; @@ -26,6 +23,7 @@ import { DefaultStyleProvider, returnEmptyDocViewList } from '../StyleProvider'; import { DocumentView, DocumentViewInternal } from '../nodes/DocumentView'; import './CollectionMenu.scss'; import { CollectionLinearView } from './collectionLinear'; +import { SettingsManager } from '../../util/SettingsManager'; interface CollectionMenuProps { panelHeight: () => number; @@ -121,7 +119,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { <div className="hardCodedButtons"> <Toggle toggleType={ToggleType.BUTTON} - type={Type.PRIM} + type={Type.TERT} color={SnappingManager.userColor} onClick={this.props.toggleTopBar} toggleStatus={this.props.topBarHeight() > 0} @@ -130,7 +128,7 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { /> <Toggle toggleType={ToggleType.BUTTON} - type={Type.PRIM} + type={Type.TERT} color={SnappingManager.userColor} onClick={this._props.togglePropertiesFlyout} toggleStatus={SnappingManager.PropertiesWidth > 0} @@ -141,26 +139,22 @@ export class CollectionMenu extends AntimodeMenu<CollectionMenuProps> { ); // dash col linear view buttons - const contMenuButtons = ( + return ( <div className="collectionMenu-container" style={{ - background: SnappingManager.userBackgroundColor, + background: SettingsManager.userBackgroundColor, // borderColor: SettingsManager.userColor }}> {this.contMenuButtons} {hardCodedButtons} </div> ); - - return contMenuButtons; } } interface CollectionViewMenuProps { - // eslint-disable-next-line react/no-unused-prop-types type: CollectionViewType; - // eslint-disable-next-line react/no-unused-prop-types fieldKey: string; docView: DocumentView; } @@ -185,7 +179,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu let formatStr = source.length && Cast(source[0].text, RichTextField, null)?.Text; try { formatStr && JSON.parse(formatStr); - } catch (e) { + } catch { formatStr = ''; } if (source.length === 1 && formatStr) { @@ -213,7 +207,7 @@ export class CollectionViewBaseChrome extends React.Component<CollectionViewMenu title: 'set content', script: 'getProto(this.target).data = copyField(this.source);', immediate: undoable((source: Doc[]) => { - this.target[DocData].data = new List<Doc>(source); + this.target.$data = new List<Doc>(source); }, ''), initialize: emptyFunction, }; diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index c499bd288..7f639a11e 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -32,6 +32,7 @@ import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { Property } from 'csstype'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; /** * CollectionNoteTakingView is a column-based view for displaying documents. In this view, the user can (1) @@ -257,11 +258,9 @@ export class CollectionNoteTakingView extends CollectionSubView() { const noteTakingDocTransform = () => this.getDocTransform(doc, dref); return ( <DocumentView - ref={r => { - dref = r || undefined; - }} + ref={r => (dref = r || undefined)} Document={doc} - TemplateDataDocument={dataDoc ?? (!Doc.AreProtosEqual(doc[DocData], doc) ? doc[DocData] : undefined)} + TemplateDataDocument={doc.isTemplateDoc || doc.isTemplateForField ? this._props.TemplateDataDocument : undefined} pointerEvents={this.blockPointerEventsWhenDragging} renderDepth={this._props.renderDepth + 1} PanelWidth={width} @@ -270,7 +269,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { containerViewPath={this.childContainerViewPath} fitWidth={this._props.childLayoutFitWidth} isContentActive={emptyFunction} - onKey={this.onKeyDown} + onKey={this.onKey} // TODO: change this from a prop to a parameter passed into a function dontHideOnDrag isDocumentActive={this.isContentActive} @@ -322,11 +321,10 @@ export class CollectionNoteTakingView extends CollectionSubView() { return Math.min(maxWidth - CollectionNoteTakingViewColumn.ColumnMargin, width < maxWidth ? width : maxWidth); }; - // how to get the height of a document. Nothing special here. getDocHeight(d?: Doc) { if (!d || d.hidden) return 0; - const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); - const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument; + const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.()); + const childDataDoc = d.isTemplateDoc || d.isTemplateForField ? this._props.TemplateDataDocument : undefined; const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); const nw = Doc.NativeWidth(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this._props.childLayoutFitWidth?.(d)) ? NumCast(d._width) : 0); const nh = Doc.NativeHeight(childLayoutDoc, childDataDoc) || (!(childLayoutDoc._layout_fitWidth || this._props.childLayoutFitWidth?.(d)) ? NumCast(d._height) : 0); @@ -437,11 +435,11 @@ export class CollectionNoteTakingView extends CollectionSubView() { }; @undoBatch - onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { - if ((e.ctrlKey || fieldProps.Document._createDocOnCR) && ['Enter'].includes(e.key)) { + onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => { + if ((e.ctrlKey || textBox.Document._createDocOnCR) && ['Enter'].includes(e.key)) { e.stopPropagation?.(); - const newDoc = Doc.MakeCopy(fieldProps.Document, true); - newDoc[DocData].text = undefined; + const newDoc = Doc.MakeCopy(textBox.Document, true); + newDoc.$text = undefined; DocumentView.SetSelectOnLoad(newDoc); return this.addDocument?.(newDoc); } @@ -543,8 +541,8 @@ export class CollectionNoteTakingView extends CollectionSubView() { addDocument={this.addDocument} chromeHidden={this.chromeHidden} colHeaderData={this.colHeaderData} - Document={this.Document} - TemplateDataDocument={this._props.TemplateDataDocument} + Doc={this.Document} + TemplateDataDoc={this._props.TemplateDataDocument} resizeColumns={this.resizeColumns} renderChildren={this.children} numGroupColumns={this.numGroupColumns} @@ -567,7 +565,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { @undoBatch remColumn = (value: SchemaHeaderField) => { - const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); + const colHdrData = Array.from(Cast(this.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []); if (value) { const index = colHdrData.indexOf(value); index !== -1 && colHdrData.splice(index, 1); @@ -586,7 +584,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { } return undefined; }); - const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); + const columnHeaders = Array.from(Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null) ?? []); const newColWidth = 1 / (this.numGroupColumns + 1); columnHeaders.push(new SchemaHeaderField(value, undefined, undefined, newColWidth)); value && this.resizeColumns(columnHeaders); @@ -643,7 +641,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { leftHeader.setWidth(leftHeader.width + movementX / this.availableWidth); rightHeader.setWidth(rightHeader.width - movementX / this.availableWidth); const headers = Cast(this.dataDoc[this.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null); - headers.splice(headers.indexOf(leftHeader), 1, leftHeader[Copy]()); + headers?.splice(headers.indexOf(leftHeader), 1, leftHeader[Copy]()); }; // renderedSections returns a list of all of the JSX elements used (columns and dividers). If the view @@ -701,7 +699,7 @@ export class CollectionNoteTakingView extends CollectionSubView() { {this.renderedSections} <div className="collectionNotetaking-pivotField" style={{ right: 0, top: 0, position: 'absolute' }}> <FieldsDropdown - Document={this.Document} + Doc={this.Document} selectFunc={undoable(fieldKey => { this.layoutDoc._pivotField = fieldKey; this.removeEmptyColumns(); diff --git a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx index 40b3f9ef2..f283b0abe 100644 --- a/src/client/views/collections/CollectionNoteTakingViewColumn.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewColumn.tsx @@ -21,8 +21,8 @@ import './CollectionNoteTakingView.scss'; import { DocumentView } from '../nodes/DocumentView'; interface CSVFieldColumnProps { - Document: Doc; - TemplateDataDocument: Opt<Doc>; + Doc: Doc; + TemplateDataDoc: Opt<Doc>; backgroundColor?: () => string | undefined; docList: Doc[]; heading: string; @@ -65,7 +65,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV // columnWidth returns the width of a column in absolute pixels @computed get columnWidth() { - if (this._props.Document._notetaking_columns_autoSize) return this._props.availableWidth / (this._props.colHeaderData?.length || 1); + if (this._props.Doc._notetaking_columns_autoSize) return this._props.availableWidth / (this._props.colHeaderData?.length || 1); if (!this._props.colHeaderData || !this._props.headingObject || this._props.colHeaderData.length === 1) return `${(this._props.availableWidth / this._props.PanelWidth()) * 100}%`; const i = this._props.colHeaderData.findIndex(hd => hd.heading === this._props.headingObject?.heading && hd.color === this._props.headingObject.color); return ((this._props.colHeaderData[i].width * this._props.availableWidth) / this._props.PanelWidth()) * 100 + '%'; @@ -81,7 +81,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); - if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document); + if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Doc); else if (this._ele) this.props.refList.slice(this.props.refList.indexOf(this._ele), 1); this._ele = ele; }; @@ -155,9 +155,9 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV // all docs are added to the column directly to the left. @undoBatch deleteColumn = () => { - const colHdrData = Array.from(Cast(this._props.Document[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); + const colHdrData = Array.from(Cast(this._props.Doc[this._props.fieldKey + '_columnHeaders'], listSpec(SchemaHeaderField), null)); if (this._props.headingObject) { - // this._props.docList.forEach(d => (d[DocData][this._props.pivotField] = undefined)); + // this._props.docList.forEach(d => (d['$'+this._props.pivotField] = undefined)); colHdrData.splice(colHdrData.indexOf(this._props.headingObject), 1); this._props.resizeColumns(colHdrData); } @@ -184,11 +184,11 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV ); ContextMenu.Instance.setDefaultItem('::', (name: string): void => { - Doc.GetProto(this._props.Document)[name] = ''; + Doc.GetProto(this._props.Doc)[name] = ''; const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true }); if (created) { - if (this._props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this._props.Document); + if (this._props.Doc.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this._props.Doc); } this._props.addDocument?.(created); } @@ -267,7 +267,7 @@ export class CollectionNoteTakingViewColumn extends ObservableReactComponent<CSV style={{ width: this.columnWidth, background: this._hover && SnappingManager.IsDragging ? '#b4b4b4' : 'inherit', - marginLeft: this._props.headings().findIndex(h => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Document.xMargin) : 0, + marginLeft: this._props.headings().findIndex(h => h[0] === this._props.headingObject) === 0 ? NumCast(this._props.Doc.xMargin) : 0, }}> <div className="collectionNoteTakingViewFieldColumn" key={this._heading} ref={this.createColumnDropRef}> {this.innards} diff --git a/src/client/views/collections/CollectionPivotView.tsx b/src/client/views/collections/CollectionPivotView.tsx index 2600c0f57..4736070c3 100644 --- a/src/client/views/collections/CollectionPivotView.tsx +++ b/src/client/views/collections/CollectionPivotView.tsx @@ -103,7 +103,7 @@ export class CollectionPivotView extends CollectionSubView() { {this.contents} <div style={{ right: 0, top: 0, position: 'absolute' }}> <FieldsDropdown - Document={this.Document} + Doc={this.Document} selectFunc={fieldKey => { this.layoutDoc._pivotField = fieldKey; }} diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index c3047e5fb..fd4bdf364 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -5,7 +5,6 @@ import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { returnEmptyFilter, returnFalse, returnNone, returnTrue, returnZero, setupMoveUpEvents, smoothScrollHorizontal, StopEvent } from '../../../ClientUtils'; import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; @@ -187,13 +186,12 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack @computed get rangeClick() { // prettier-ignore return ScriptField.MakeFunction('stackedTimeline.clickAnchor(this, clientX)', - { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: 'string' /* should be CollectionStackedTimeline */ } - )!; + { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as unknown as string })!; // NOTE: scripts can't serialize a run-time React component as captured variable BUT this script will not be serialized so we can "stuff" anything we want in the capture variable } @computed get rangePlay() { // prettier-ignore return ScriptField.MakeFunction('stackedTimeline.playOnClick(this, clientX)', - { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: 'string' /* should be CollectionStackedTimeline */})!; + { stackedTimeline: 'any', clientX: 'number' }, { stackedTimeline: this as unknown as string })!; // NOTE: scripts can't serialize a run-time React component as captured variable BUT this script will not be serialized so we can "stuff" anything we want in the capture variable } rangeClickScript = () => this.rangeClick; rangePlayScript = () => this.rangePlay; @@ -268,13 +266,13 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack setupMoveUpEvents( this, e, - action(() => { + action(movEv => { if (!wasSelecting) { this._markerStart = this._markerEnd = this.toTimeline(clientX - rect.x, rect.width); wasSelecting = true; this._timelineWrapper && (this._timelineWrapper.style.cursor = 'ew-resize'); } - this._markerEnd = this.toTimeline(e.clientX - rect.x, rect.width); + this._markerEnd = this.toTimeline(movEv.clientX - rect.x, rect.width); return false; }), action((upEvent, movement, isClick) => { @@ -433,8 +431,8 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack _isTimelineLabel: true, layout_borderRounding: anchorEndTime === undefined ? '100%' : undefined, }); - anchor[DocData][startTag] = anchorStartTime; - anchor[DocData][endTag] = anchorEndTime; + anchor['$' + startTag] = anchorStartTime; + anchor['$' + endTag] = anchorEndTime; if (addAsAnnotation) { if (Cast(dataDoc[fieldKey], listSpec(Doc), null)) { Cast(dataDoc[fieldKey], listSpec(Doc), []).push(anchor); @@ -805,9 +803,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch }; resetTitle = () => { - this._props.mark[DocData].title = ComputedField.MakeFunction( - `["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]` - ); + this._props.mark.$title = ComputedField.MakeFunction(`["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"]`); }; // context menu contextMenuItems = () => { @@ -844,7 +840,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch styleProvider={this._props.styleProvider} renderDepth={this._props.renderDepth + 1} LayoutTemplate={undefined} - LayoutTemplateString={LabelBox.LayoutString('data')} + LayoutTemplateString={LabelBox.LayoutString('title')} isDocumentActive={this._props.isDocumentActive} PanelWidth={width} PanelHeight={height} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index 6bbd43b1b..9155227dd 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -5,7 +5,6 @@ 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 { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { listSpec } from '../../../fields/Schema'; @@ -35,6 +34,7 @@ import './CollectionStackingView.scss'; import { CollectionStackingViewFieldColumn } from './CollectionStackingViewFieldColumn'; import { CollectionSubView, SubCollectionViewProps } from './CollectionSubView'; import { computedFn } from 'mobx-utils'; +import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; export type collectionStackingViewProps = { sortFunc?: (a: Doc, b: Doc) => number; @@ -112,7 +112,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection @computed get columnWidth() { const availableWidth = this._props.PanelWidth() - 2 * this.xMargin; 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 ? Number.MAX_VALUE : cwid - this.gridGap); + return Math.min(availableWidth, this.isStackingView ? availableWidth / (this.numGroupColumns || 1) : cwid - this.gridGap); } @computed get NodeWidth() { @@ -310,17 +310,17 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return this._props.styleProvider?.(doc, props, property); }; @undoBatch - onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { + onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => { if (['Enter'].includes(e.key) && e.ctrlKey) { e.stopPropagation?.(); - const layoutFieldKey = StrCast(fieldProps.fieldKey); - const newDoc = Doc.MakeCopy(fieldProps.Document, true); - const dataField = fieldProps.Document[Doc.LayoutFieldKey(newDoc)]; - newDoc[DocData][Doc.LayoutFieldKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined; - if (layoutFieldKey !== 'layout' && fieldProps.Document[layoutFieldKey] instanceof Doc) { - newDoc[layoutFieldKey] = fieldProps.Document[layoutFieldKey]; + const layoutFieldKey = StrCast(textBox.fieldKey); + const newDoc = Doc.MakeCopy(textBox.Document, true); + const dataField = textBox.Document[Doc.LayoutDataKey(newDoc)]; + newDoc['$' + Doc.LayoutDataKey(newDoc)] = dataField === undefined || Cast(dataField, listSpec(Doc), null)?.length !== undefined ? new List<Doc>([]) : undefined; + if (layoutFieldKey !== 'layout' && textBox.Document[layoutFieldKey] instanceof Doc) { + newDoc[layoutFieldKey] = textBox.Document[layoutFieldKey]; } - newDoc[DocData].text = undefined; + newDoc.$text = undefined; DocumentView.SetSelectOnLoad(newDoc); return this.addDocument?.(newDoc); } @@ -340,7 +340,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection isChildButtonContentActive = () => (this._props.childDocumentsActive?.() === false || this.Document.childDocumentsActive === false ? false : undefined); @observable docRefs = new ObservableMap<Doc, DocumentView>(); - childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null)); + childFitWidth = (doc: Doc) => Cast(this.Document.childLayoutFitWidth, 'boolean', this._props.childLayoutFitWidth?.(doc) ?? Cast(doc.layout_fitWidth, 'boolean', null) ?? null); // this is what renders the document that you see on the screen // called in Children: this actually adds a document to our children list getDisplayDoc(doc: Doc, trans: () => string, count: number) { @@ -359,7 +359,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection containerViewPath={this.childContainerViewPath} fitWidth={this.childFitWidth} isContentActive={doc.onClick ? this.isChildButtonContentActive : this.isChildContentActive} - onKey={this.onKeyDown} + onKey={this.onKey} DataTransition={trans} isDocumentActive={this.isContentActive} LayoutTemplate={this._props.childLayoutTemplate} @@ -404,11 +404,11 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection const { translateX, translateY, scale } = ClientUtils.GetScreenTransform(dref?.ContentDiv); return new Transform(-translateX + (dref?.centeringX || 0) * scale, -translateY + (dref?.centeringY || 0) * scale, 1) - .scale(1 / scale); // prettier-ignore + .scale(1 / (scale||1)); // prettier-ignore }); getDocWidth = computedFn((d?: Doc) => () => { if (!d) return 0; - const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); + 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); @@ -418,8 +418,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection getDocTransition = computedFn((d?: Doc) => () => StrCast(d?.dataTransition)); getDocHeight = computedFn((d?: Doc) => () => { if (!d || d.hidden) return 0; - const childLayoutDoc = Doc.Layout(d, this._props.childLayoutTemplate?.()); - const childDataDoc = !d.isTemplateDoc && !d.isTemplateForField ? undefined : this._props.TemplateDataDocument; + const childLayoutDoc = Doc.LayoutDoc(d, this._props.childLayoutTemplate?.()); + const childDataDoc = d.isTemplateDoc || d.isTemplateForField ? this._props.TemplateDataDocument : undefined; const maxHeight = (lim => (lim === 0 ? this._props.PanelWidth() : lim === -1 ? 10000 : lim))(NumCast(this.layoutDoc.childLimitHeight, -1)); 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); @@ -575,8 +575,8 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection addDocument={this.addDocument} chromeHidden={this.chromeHidden} colHeaderData={this.colHeaderData} - Document={this.Document} - TemplateDataDocument={this._props.TemplateDataDocument} + Doc={this.Document} + TemplateDataDoc={this._props.TemplateDataDocument} renderChildren={this.children} columnWidth={this.columnWidth} numGroupColumns={this.numGroupColumns} @@ -611,7 +611,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection <div style={{ top: this.yMargin }}> <CollectionMasonryViewFieldRow showHandle={first} - Document={this.Document} + Doc={this.Document} chromeHidden={this.chromeHidden} pivotField={this.pivotField} refList={this._refList} @@ -675,12 +675,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection } return35 = () => 35; + @computed get menuBtnDoc() { return DocCast(this.layoutDoc.layout_headerButton); } // prettier-ignore @computed get buttonMenu() { - const menuDoc = DocCast(this.layoutDoc.layout_headerButton); - return !menuDoc ? null : ( - <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}> + return !this.menuBtnDoc ? null : ( + <div className="buttonMenu-docBtn" style={{ width: NumCast(this.menuBtnDoc._width, 30), height: NumCast(this.menuBtnDoc._height, 30) }}> <DocumentView - Document={menuDoc} + Document={this.menuBtnDoc} isContentActive={this.isContentActive} isDocumentActive={this.isContentActive} addDocument={this._props.addDocument} @@ -720,7 +720,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection return this._props.isContentActive() === false ? 'none' : undefined; } - onPassiveWheel = (e: WheelEvent) => e.stopPropagation(); + onPassiveWheel = (e: WheelEvent) => this._props.isContentActive() && e.stopPropagation(); render() { TraceMobx(); const editableViewProps = { @@ -728,13 +728,12 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection SetValue: this.addGroup, contents: '+ ADD A GROUP', }; - const buttonMenu = this.layoutDoc.layout_headerButton; const noviceExplainer = this.layoutDoc.layout_explainer; return ( <> - {buttonMenu || noviceExplainer ? ( + {this.menuBtnDoc || noviceExplainer ? ( <div className="documentButtonMenu"> - {buttonMenu ? this.buttonMenu : null} + {this.menuBtnDoc ? this.buttonMenu : null} {Doc.noviceMode && noviceExplainer ? <div className="documentExplanation">{StrCast(noviceExplainer)}</div> : null} </div> ) : null} diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 6f32dd2e0..994669734 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -7,7 +7,7 @@ import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { RichTextField } from '../../../fields/RichTextField'; import { PastelSchemaPalette, SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, NumCast } from '../../../fields/Types'; +import { BoolCast, DocCast, NumCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction } from '../../../Utils'; @@ -26,11 +26,12 @@ 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 { - Document: Doc; - TemplateDataDocument: Opt<Doc>; + Doc: Doc; + TemplateDataDoc: Opt<Doc>; docList: Doc[]; heading: string; pivotField: string; @@ -90,7 +91,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< // is that the only way to have drop targets? createColumnDropRef = (ele: HTMLDivElement | null) => { this.dropDisposer?.(); - if (ele) this.dropDisposer = DragManager.MakeDropTarget(ele, this.columnDrop.bind(this), this._props.Document, this.onInternalPreDrop.bind(this)); + 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); this._ele = ele; }; @@ -183,7 +184,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< // TODO: I think this is where I'm supposed to edit stuff startDrag = (e: PointerEvent) => { // is MakeEmbedding a way to make a copy of a doc without rendering it? - const embedding = Doc.MakeEmbedding(this._props.Document); + const embedding = Doc.MakeEmbedding(this._props.Doc); embedding._width = this._props.columnWidth / (this._props.colHeaderData?.length || 1); embedding._pivotField = undefined; let value = this.getValue(this._heading); @@ -230,7 +231,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< ContextMenu.Instance.clearItems(); const layoutItems: ContextMenuProps[] = []; const docItems: ContextMenuProps[] = []; - const dataDoc = this._props.TemplateDataDocument || this._props.Document; + const dataDoc = this._props.TemplateDataDoc || this._props.Doc; const width = this._ele ? DivWidth(this._ele) : 0; const height = this._ele ? DivHeight(this._ele) : 0; DocUtils.addDocumentCreatorMenuItems( @@ -250,10 +251,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< docItems.push({ description: ':' + fieldKey, event: () => { - const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Document)); + const created = DocumentFromField(dataDoc, fieldKey, Doc.GetProto(this._props.Doc)); if (created) { - if (this._props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this._props.Document); + if (this._props.Doc.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this._props.Doc); } return this._props.addDocument?.(created); } @@ -270,10 +271,10 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< event: () => { const created = Docs.Create.CarouselDocument([], { _width: 400, _height: 200, title: fieldKey }); if (created) { - const container = this._props.Document.resolvedDataDoc ? Doc.GetProto(this._props.Document) : this._props.Document; + const container = DocCast(this._props.Doc.rootDocument)?.[DocData] ? Doc.GetProto(this._props.Doc) : this._props.Doc; if (container.isTemplateDoc) { Doc.MakeMetadataFieldTemplate(created, container); - return Doc.AddDocToList(container, Doc.LayoutFieldKey(container), created); + return Doc.AddDocToList(container, Doc.LayoutDataKey(container), created); } return this._props.addDocument?.(created) || false; } @@ -285,11 +286,11 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Doc Fields ...', subitems: docItems, icon: 'eye' }); !Doc.noviceMode && ContextMenu.Instance.addItem({ description: 'Containers ...', subitems: layoutItems, icon: 'eye' }); ContextMenu.Instance.setDefaultItem('::', (name: string): void => { - Doc.GetProto(this._props.Document)[name] = ''; + Doc.GetProto(this._props.Doc)[name] = ''; const created = Docs.Create.TextDocument('', { title: name, _width: 250, _layout_autoHeight: true }); if (created) { - if (this._props.Document.isTemplateDoc) { - Doc.MakeMetadataFieldTemplate(created, this._props.Document); + if (this._props.Doc.isTemplateDoc) { + Doc.MakeMetadataFieldTemplate(created, this._props.Doc); } this._props.addDocument?.(created); } @@ -304,10 +305,8 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< @computed get innards() { TraceMobx(); const key = this._props.pivotField; - const headings = this._props.headings(); const heading = this._heading; const columnYMargin = this._props.headingObject ? 0 : this._props.yMargin; - const uniqueHeadings = headings.map((i, idx) => headings.indexOf(i) === idx); const noValueHeader = `NO ${key.toUpperCase()} VALUE`; const evContents = heading || (this._props?.type === 'number' ? '0' : noValueHeader); const headingView = this._props.headingObject ? ( @@ -317,7 +316,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< ref={this._headerRef} style={{ marginTop: this._props.yMargin, - width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1), + width: this._props.columnWidth, }}> {/* the default bucket (no key value) has a tooltip that describes what it is. Further, it does not have a color and cannot be deleted. */} @@ -350,17 +349,17 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< </div> ) : null; const templatecols = `${this._props.columnWidth / this._props.numGroupColumns}px `; - const { type } = this._props.Document; + const { type } = this._props.Doc; return ( <> - {this._props.Document._columnsHideIfEmpty ? null : headingView} + {this._props.Doc._columnsHideIfEmpty ? null : headingView} {this.collapsed ? null : ( <div style={{ margin: 'auto', marginTop: this._props.dontCenter.includes('y') ? undefined : 'auto', marginBottom: this._props.dontCenter.includes('y') ? undefined : 'auto', - width: this._props.columnWidth / (uniqueHeadings.length + (this._props.chromeHidden ? 0 : 1) || 1), + width: this._props.columnWidth, }}> <div key={`${heading}-stack`} @@ -368,7 +367,6 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< style={{ padding: `${columnYMargin}px ${0}px ${this._props.yMargin}px ${0}px`, margin: this._props.dontCenter.includes('x') ? undefined : 'auto', - // width: 'max-content', // singleColumn ? undefined : `${cols * (style.columnWidth + style.gridGap) + 2 * style.xMargin - style.gridGap}px`, height: 'max-content', position: 'relative', gridGap: this._props.gridGap, diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 655894e40..bc7d6f897 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -3,7 +3,7 @@ import * as React from 'react'; import * as rp from 'request-promise'; import { ClientUtils, DashColor, returnFalse } from '../../../ClientUtils'; import CursorField from '../../../fields/CursorField'; -import { Doc, DocListCast, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, expandedFieldName, GetDocFromUrl, GetHrefFromHTML, Opt, RTFIsFragment, StrListCast } from '../../../fields/Doc'; import { AclPrivate, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; @@ -13,6 +13,7 @@ import { BoolCast, Cast, DateCast, NumCast, ScriptCast, StrCast, toList } from ' import { WebField } from '../../../fields/URLField'; import { GetEffectiveAcl, TraceMobx } from '../../../fields/util'; import { GestureUtils } from '../../../pen-gestures/GestureUtils'; +import { Upload } from '../../../server/SharedMediaTypes'; import { DocServer } from '../../DocServer'; import { Networking } from '../../Network'; import { DocUtils } from '../../documents/DocUtils'; @@ -24,11 +25,11 @@ import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager } from '../../util/UndoManager'; import { ViewBoxBaseComponent } from '../DocComponent'; +import { DocumentViewProps } from '../nodes/DocumentContentsView'; +import { DocumentView } from '../nodes/DocumentView'; import { FieldViewProps } from '../nodes/FieldView'; -import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; -import { FlashcardPracticeUI } from './FlashcardPracticeUI'; import { OpenWhere, OpenWhereMod } from '../nodes/OpenWhere'; -import { Upload } from '../../../server/SharedMediaTypes'; +import { FlashcardPracticeUI } from './FlashcardPracticeUI'; export enum docSortings { Time = 'time', @@ -108,7 +109,11 @@ export function CollectionSubView<X>() { } get dataDoc() { - return this._props.TemplateDataDocument instanceof Doc && this.Document.isTemplateForField ? Doc.GetProto(this._props.TemplateDataDocument) : this.Document.resolvedDataDoc ? this.Document : this.Document[DocData]; // if the layout document has a resolvedDataDoc, then we don't want to get its parent which would be the unexpanded template + return this._props.TemplateDataDocument instanceof Doc && this.Document.isTemplateForField // + ? Doc.GetProto(this._props.TemplateDataDocument) + : this.Document.rootDocument + ? this.Document + : this.Document[DocData]; // if the layout document has a rootDocument, then we don't want to get its parent which would be the unexpanded template } get childContainerViewPath() { @@ -127,16 +132,23 @@ export function CollectionSubView<X>() { hasChildDocs = () => this.childLayoutPairs.map(pair => pair.layout); @computed get childLayoutPairs(): { layout: Doc; data: Doc }[] { - const { Document, TemplateDataDocument } = this._props; - const validPairs = this.childDocs - .map(doc => Doc.GetLayoutDataDocPair(Document, !this._props.isAnnotationOverlay ? TemplateDataDocument : undefined, doc)) - .filter( - pair => - // filter out any documents that have a proto that we don't have permissions to - !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate)) - ) - .filter(pair => !this._filterFunc?.(pair.layout!)); - return validPairs.map(({ data, layout }) => ({ data: data as Doc, layout: layout! })); // this mapping is a bit of a hack to coerce types + const lastEle = this.DocumentView?.(); + if (!lastEle?.IsInvalid(this.Document)) { + const rootTemplate = lastEle && Doc.LayoutDoc(lastEle.rootDoc).isTemplateDoc && Doc.LayoutDoc(lastEle.rootDoc); + const templateFieldKey = rootTemplate && + [expandedFieldName(rootTemplate), + ...this._props.docViewPath() + .filter(dv => dv.Document.isTemplateForField) + .map(dv => dv.Document.title), + ].join('_'); // prettier-ignore + return this.childDocs + .map(doc => Doc.GetLayoutDataDocPair(this.Document, !this._props.isAnnotationOverlay ? this._props.TemplateDataDocument : undefined, doc, templateFieldKey || "")) + .filter(pair => // filter out any documents that have a proto that we don't have permissions to + !pair.layout?.hidden && pair.layout && (!pair.layout.proto || (pair.layout.proto instanceof Doc && GetEffectiveAcl(pair.layout.proto) !== AclPrivate))) + .filter(pair => !this._filterFunc?.(pair.layout!)) + .map(({ data, layout }) => ({ data: data!, layout: layout! })); // prettier-ignore + } + return []; } /** * This is the raw, stored list of children on a collection. If you modify this list, the database will be updated @@ -156,7 +168,7 @@ export function CollectionSubView<X>() { }; collectionFilters = () => this._focusFilters ?? StrListCast(this.Document._childFilters); - collectionRangeDocFilters = () => this._focusRangeFilters ?? Cast(this.Document._childFiltersByRanges, listSpec('string'), []); + collectionRangeDocFilters = () => this._focusRangeFilters ?? StrListCast(this.Document._childFiltersByRanges); // child filters apply to the descendants of the documents in this collection childDocFilters = () => [...(this._props.childFilters?.().filter(f => ClientUtils.IsRecursiveFilter(f)) || []), ...this.collectionFilters()]; // unrecursive filters apply to the documents in the collection, but no their children. See Utils.noRecursionHack @@ -173,7 +185,7 @@ export function CollectionSubView<X>() { rawdocs = [this.dataField]; } else if (Cast(this.dataField, listSpec(Doc), null)) { // otherwise, if the collection data is a list, then use it. - rawdocs = Cast(this.dataField, listSpec(Doc), null); + rawdocs = DocListCast(this.dataField); } else if (this.dataField) { // Finally, if it's not a doc or a list and the document is a template, we try to render the root doc. // For example, if an image doc is rendered with a slide template, the template will try to render the data field as a collection. @@ -201,7 +213,7 @@ export function CollectionSubView<X>() { let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0; if (notFiltered) { notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0; - const fieldKey = Doc.LayoutFieldKey(d); + const fieldKey = Doc.LayoutDataKey(d); const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc)); const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey]; const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar']; @@ -214,7 +226,7 @@ export function CollectionSubView<X>() { newarray = []; // eslint-disable-next-line no-loop-func subDocs.forEach(t => { - const docFieldKey = Doc.LayoutFieldKey(t); + const docFieldKey = Doc.LayoutDataKey(t); const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc)); notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length)); @@ -295,7 +307,7 @@ export function CollectionSubView<X>() { const dragData = de.complete.docDragData; if (dragData) { const sourceDragAction = dragData.dropAction; - const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this._props.Document); + const sameCollection = !dragData.draggedDocuments.some(d => d.embedContainer !== this.Document); dragData.dropAction = !sameCollection // if doc from another tree ? sourceDragAction || targetDropAction // then use the source's dragAction otherwise the target's : sourceDragAction === dropActionType.inPlace // if source drag is inPlace @@ -585,7 +597,7 @@ export function CollectionSubView<X>() { /** * How much the content of the collection is being scaled based on its nesting and its fit-to-width settings */ - @computed get contentScaling() { return this.ScreenToLocalBoxXf().Scale; } // prettier-ignore + @computed get contentScaling() { return this.ScreenToLocalBoxXf().Scale * (!this._props.fitWidth?.(this.Document) ? this._props.NativeDimScaling?.()||1: 1); } // prettier-ignore /** * The maximum size a UI widget can be in collection coordinates based on not wanting the widget to visually obscure too much of the collection * This takes the desired screen space size and converts into collection coordinates. It then returns the smaller of the converted @@ -598,7 +610,12 @@ export function CollectionSubView<X>() { */ @computed get uiBtnScaling() { return this.maxWidgetSize / this._sideBtnWidth; } // prettier-ignore - screenXPadding = () => (this.uiBtnScaling * this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) / this._props.ScreenToLocalTransform().Scale; + screenXPadding = (docView?: DocumentView) => { + if (!docView) return 0; + const diff = this._props.PanelWidth() - docView.PanelWidth(); + const xpad1 = this.uiBtnScaling * (this._sideBtnWidth - NumCast(this.layoutDoc.xMargin)) - diff / 2; // this._sideBtnWidth; + return xpad1 / (docView.NativeDimScaling?.() || 1); + }; filteredChildDocs = () => this.childLayoutPairs.map(pair => pair.layout); childDocsFunc = () => this.childDocs; @action setFilterFunc = (func?: (doc: Doc) => boolean) => { this._filterFunc = func; }; // prettier-ignore diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index 98bd06221..fd562e64f 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -121,7 +121,7 @@ export class CollectionTimeView extends CollectionSubView() { {this.contents} <div style={{ right: 0, top: 0, position: 'absolute' }}> <FieldsDropdown - Document={this.Document} + Doc={this.Document} selectFunc={fieldKey => { this.layoutDoc._pivotField = fieldKey; }} diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index e93724dd4..3497a62cb 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -8,7 +8,7 @@ import { DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { listSpec } from '../../../fields/Schema'; import { ScriptField } from '../../../fields/ScriptField'; -import { BoolCast, Cast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; +import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast, toList } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; import { emptyFunction, Utils } from '../../../Utils'; import { Docs } from '../../documents/Documents'; @@ -187,7 +187,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree @action addDoc = (docs: Doc | Doc[], relativeTo: Opt<Doc>, before?: boolean): boolean => { const addDocRelativeTo = (adocs: Doc | Doc[]) => (adocs as Doc[]).reduce((flg, doc) => flg && Doc.AddDocToList(this.Document[DocData], this._props.fieldKey, doc, relativeTo, before), true); - if (this.Document.resolvedDataDoc instanceof Promise) return false; + if (this.Document.rootDocument instanceof Promise) return false; const doclist = toList(docs); const res = relativeTo === undefined ? this._props.addDocument?.(doclist) || false : addDocRelativeTo(doclist); res && @@ -200,7 +200,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree onContextMenu = (): void => { // need to test if propagation has stopped because GoldenLayout forces a parallel react hierarchy to be created for its top-level layout const layoutItems: ContextMenuProps[] = []; - const menuDoc = ScriptCast(Cast(this.layoutDoc.layout_headerButton, Doc, null)?.onClick)?.script.originalScript === CollectionTreeView.AddTreeFunc; + const menuDoc = ScriptCast(this.menuBtnDoc?.onClick)?.script.originalScript === CollectionTreeView.AddTreeFunc; menuDoc && layoutItems.push({ description: 'Create new folder', event: () => CollectionTreeView.addTreeFolder(this.Document), icon: 'paint-brush' }); if (!Doc.noviceMode) { layoutItems.push({ @@ -241,7 +241,7 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree ); } - onKey = (e: React.KeyboardEvent /* , fieldProps: FieldViewProps */) => { + onKey = (e: KeyboardEvent /* , textBox: FormattedTextBox */) => { if (this.outlineMode && e.key === 'Enter') { e.stopPropagation(); this.makeTextCollection(this.treeChildren); @@ -252,7 +252,6 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree get documentTitle() { return ( <FormattedTextBox - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} fieldKey="text" renderDepth={this._props.renderDepth + 1} @@ -348,14 +347,14 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree } return35 = () => 35; + @computed get menuBtnDoc() { return DocCast(this.layoutDoc.layout_headerButton); } // prettier-ignore @computed get buttonMenu() { - const menuDoc = Cast(this.layoutDoc.layout_headerButton, Doc, null); // To create a multibutton menu add a CollectionLinearView - return !menuDoc ? null : ( - <div className="buttonMenu-docBtn" style={{ width: NumCast(menuDoc._width, 30), height: NumCast(menuDoc._height, 30) }}> + return !this.menuBtnDoc ? null : ( + <div className="buttonMenu-docBtn" style={{ width: NumCast(this.menuBtnDoc._width, 30), height: NumCast(this.menuBtnDoc._height, 30) }}> <DocumentView - Document={menuDoc} - TemplateDataDocument={menuDoc} + Document={this.menuBtnDoc} + TemplateDataDocument={this.menuBtnDoc} isContentActive={this._props.isContentActive} isDocumentActive={returnTrue} addDocument={this._props.addDocument} @@ -468,10 +467,9 @@ export class CollectionTreeView extends CollectionSubView<Partial<collectionTree const scale = this._props.NativeDimScaling?.() || 1; return ( - <div style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}> + <div className="collectionTreeView" style={{ transform: `scale(${scale})`, transformOrigin: 'top left', width: `${100 / scale}%`, height: `${100 / scale}%` }}> {!(this.Document instanceof Doc) || !this.treeChildren ? null : this.Document.treeView_HasOverlay ? ( <CollectionFreeFormView - // eslint-disable-next-line react/jsx-props-no-spreading {...this._props} setContentViewBox={emptyFunction} NativeWidth={returnZero} diff --git a/src/client/views/collections/CollectionView.tsx b/src/client/views/collections/CollectionView.tsx index 7ba8989ce..c9af92a1b 100644 --- a/src/client/views/collections/CollectionView.tsx +++ b/src/client/views/collections/CollectionView.tsx @@ -130,7 +130,6 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr { description: 'Calendar', event: () => func(CollectionViewType.Calendar), icon: 'columns' }, { description: 'Pivot', event: () => func(CollectionViewType.Pivot), icon: 'columns' }, { description: 'Time', event: () => func(CollectionViewType.Time), icon: 'columns' }, - { description: 'Map', event: () => func(CollectionViewType.Map), icon: 'globe-americas' }, { description: 'Grid', event: () => func(CollectionViewType.Grid), icon: 'th-list' }, ]; @@ -162,7 +161,7 @@ export class CollectionView extends ViewBoxAnnotatableComponent<CollectionViewPr if (this.Document.childClickedOpenTemplateView instanceof Doc) { optionItems.push({ description: 'View Child Detailed Layout', event: () => this._props.addDocTab(this.Document.childClickedOpenTemplateView as Doc, OpenWhere.addRight), icon: 'project-diagram' }); } - !Doc.noviceMode && optionItems.push({ description: `${this.layoutDoc._isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => { this.layoutDoc._isLightbox = !this.layoutDoc._isLightbox; }, icon: 'project-diagram' }); // prettier-ignore + !Doc.noviceMode && optionItems.push({ description: `${this.dataDoc.$isLightbox ? 'Unset' : 'Set'} is Lightbox`, event: () => { this.dataDoc.$isLightbox = !this.dataDoc.$isLightbox; }, icon: 'project-diagram' }); // prettier-ignore !options && cm.addItem({ description: 'Options...', subitems: optionItems, icon: 'hand-point-right' }); diff --git a/src/client/views/collections/FlashcardPracticeUI.tsx b/src/client/views/collections/FlashcardPracticeUI.tsx index c071c5fb8..f24a8acb7 100644 --- a/src/client/views/collections/FlashcardPracticeUI.tsx +++ b/src/client/views/collections/FlashcardPracticeUI.tsx @@ -7,13 +7,16 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import { returnFalse, returnZero, setupMoveUpEvents } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; -import { Doc, DocListCast } from '../../../fields/Doc'; +import { Doc, DocListCast, Opt } from '../../../fields/Doc'; import { BoolCast, NumCast, StrCast } from '../../../fields/Types'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; import { ObservableReactComponent } from '../ObservableReactComponent'; -import { DocumentView, DocumentViewProps } from '../nodes/DocumentView'; +import { DocumentView } from '../nodes/DocumentView'; import './FlashcardPracticeUI.scss'; +import { StyleProp } from '../StyleProp'; +import { FieldViewProps } from '../nodes/FieldView'; +import { DocumentViewProps } from '../nodes/DocumentContentsView'; export enum practiceMode { PRACTICE = 'practice', @@ -60,8 +63,8 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp @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 - btnHeight = () => NumCast(this.filterDoc?.height) * Math.min(1, this._props.ScreenToLocalBoxXf().Scale); - btnWidth = () => (!this.filterDoc ? 1 : (this.btnHeight() * NumCast(this.filterDoc._width)) / NumCast(this.filterDoc._height)); + btnHeight = () => NumCast(this.filterDoc?.height); + btnWidth = () => (!this.filterDoc ? 1 : NumCast(this.filterDoc._width)); /** * Sets the practice mode answer style for flashcards @@ -131,7 +134,7 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp <div className="FlashcardPracticeUI-practiceModes" style={{ - transform: this._props.ScreenToLocalBoxXf().Scale >= 1 ? undefined : `translateY(${this.btnHeight() / this._props.ScreenToLocalBoxXf().Scale - this.btnHeight()}px)`, + background: SnappingManager.userVariantColor, }}> <MultiToggle tooltip="Practice flashcards one at a time" @@ -139,7 +142,6 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp color={SnappingManager.userColor} background={SnappingManager.userVariantColor} multiSelect={false} - isToggle={false} toggleStatus={!!this.practiceMode} label="Practice" items={[ @@ -159,7 +161,6 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp color={SnappingManager.userColor} background={SnappingManager.userVariantColor} multiSelect={false} - isToggle={false} toggleStatus={!!this.practiceMode} label={StrCast(this._props.layoutDoc.revealOp, flashcardRevealOp.FLIP)} items={[ @@ -179,6 +180,12 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp </div> ); } + childStyleProvider = (doc: Doc | undefined, props: Opt<FieldViewProps>, property: string) => { + if (doc instanceof Doc && property === StyleProp.BackgroundColor) { + return SnappingManager.userVariantColor; + } + return this._props.docViewProps().styleProvider?.(doc, props, property); + }; tryFilterOut = (doc: Doc) => (this.practiceMode && doc?._layout_flashcardType && doc[this.practiceField] === practiceVal.CORRECT ? true : false); // show only cards that aren't marked as correct render() { return ( @@ -186,24 +193,27 @@ export class FlashcardPracticeUI extends ObservableReactComponent<PracticeUIProp {this.emptyMessage} {this.practiceButtons} {this._props.layoutDoc._chromeHidden ? null : ( - <div className="FlashcardPracticeUI-menu" style={{ height: this.btnHeight(), width: this.btnHeight(), transform: `scale(${this._props.uiBtnScaling})` }}> + <div className="FlashcardPracticeUI-menu" style={{ height: this.btnHeight(), width: this.btnWidth(), transform: `scale(${this._props.uiBtnScaling})` }}> {!this.filterDoc ? null : ( - <DocumentView - {...this._props.docViewProps()} - Document={this.filterDoc} - TemplateDataDocument={undefined} - PanelWidth={this.btnWidth} - PanelHeight={this.btnHeight} - NativeWidth={returnZero} - NativeHeight={returnZero} - hideDecorations={BoolCast(this._props.layoutDoc.layout_hideDecorations)} - hideCaptions={true} - hideFilterStatus={true} - renderDepth={this._props.renderDepth + 1} - fitWidth={undefined} - showTags={false} - setContentViewBox={undefined} - /> + <div style={{ background: SnappingManager.userVariantColor }}> + <DocumentView + {...this._props.docViewProps()} + Document={this.filterDoc} + TemplateDataDocument={undefined} + PanelWidth={this.btnWidth} + PanelHeight={this.btnHeight} + NativeWidth={returnZero} + NativeHeight={returnZero} + hideDecorations={BoolCast(this._props.layoutDoc.layout_hideDecorations)} + hideCaptions={true} + hideFilterStatus={true} + renderDepth={this._props.renderDepth + 1} + styleProvider={this.childStyleProvider} + fitWidth={undefined} + showTags={false} + setContentViewBox={undefined} + /> + </div> )} {this.practiceModesMenu} </div> diff --git a/src/client/views/collections/TabDocView.scss b/src/client/views/collections/TabDocView.scss index 397e35ca9..931cdac2b 100644 --- a/src/client/views/collections/TabDocView.scss +++ b/src/client/views/collections/TabDocView.scss @@ -4,7 +4,6 @@ height: 100%; width: 100%; } - input.lm_title:focus, input.lm_title { max-width: unset !important; @@ -22,7 +21,6 @@ input.lm_title { .lm_iconWrap { display: flex; color: black; - width: 15px; height: 15px; align-items: center; align-self: center; @@ -30,6 +28,16 @@ input.lm_title { margin: 3px; border-radius: 20%; + width: auto; + svg:nth-of-type(2) { + display: none; + } + &:hover { + svg:nth-of-type(2) { + display: block; + } + } + .moreInfoDot { background-color: white; border-radius: 100%; diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index cc56a8ff9..4348bc7dc 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -10,12 +10,12 @@ import ResizeObserver from 'resize-observer-polyfill'; import { ClientUtils, DashColor, lightOrDark, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, Opt, returnEmptyDoclist } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; +import { DocData, DocLayout } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { FieldId } from '../../../fields/RefField'; import { ComputedField } from '../../../fields/ScriptField'; -import { Cast, NumCast, StrCast, toList } from '../../../fields/Types'; +import { Cast, DocCast, NumCast, StrCast, toList } from '../../../fields/Types'; import { DocServer } from '../../DocServer'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { Docs } from '../../documents/Documents'; @@ -41,9 +41,10 @@ import { CollectionDockingView } from './CollectionDockingView'; import { CollectionView } from './CollectionView'; import './TabDocView.scss'; import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; +import { Tooltip } from '@mui/material'; interface TabMinimapViewProps { - document: Doc; + doc: Doc; tabView: () => DocumentView | undefined; addDocTab: (doc: Doc | Doc[], where: OpenWhere) => boolean; PanelWidth: () => number; @@ -100,15 +101,15 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps return bounds === undefined ? bounds : { l: bounds.x + width / 2 - dim / 2, t: bounds.y + height / 2 - dim / 2, cx, cy, dim }; } @computed get xPadding() { - return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l)); + return !this.renderBounds ? 0 : Math.max(0, this._props.PanelWidth() / NumCast(this._props.doc._freeform_scale, 1) - 2 * (this.renderBounds.cx - this.renderBounds.l)); } @computed get yPadding() { - return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l)); + return !this.renderBounds ? 0 : Math.max(0, this._props.PanelHeight() / NumCast(this._props.doc._freeform_scale, 1) - 2 * (this.renderBounds.cy - this.renderBounds.l)); } - childLayoutTemplate = () => Cast(this._props.document.childLayoutTemplate, Doc, null); - returnMiniSize = () => NumCast(this._props.document._miniMapSize, 150); + childLayoutTemplate = () => Cast(this._props.doc.childLayoutTemplate, Doc, null); + returnMiniSize = () => NumCast(this._props.doc._miniMapSize, 150); miniDown = (e: React.PointerEvent) => { - const doc = this._props.document; + const doc = this._props.doc; const miniSize = this.returnMiniSize(); doc && setupMoveUpEvents( @@ -127,15 +128,15 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps popup = () => { const { renderBounds } = this; if (!renderBounds) return <div />; - const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100; - const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.document._freeform_scale, 1) / renderBounds.dim) * 100; - const miniLeft = () => 50 + ((NumCast(this._props.document._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2; - const miniTop = () => 50 + ((NumCast(this._props.document._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2; + const miniWidth = () => (this._props.PanelWidth() / NumCast(this._props.doc._freeform_scale, 1) / renderBounds.dim) * 100; + const miniHeight = () => (this._props.PanelHeight() / NumCast(this._props.doc._freeform_scale, 1) / renderBounds.dim) * 100; + const miniLeft = () => 50 + ((NumCast(this._props.doc._freeform_panX) - renderBounds.cx) / renderBounds.dim) * 100 - miniWidth() / 2; + const miniTop = () => 50 + ((NumCast(this._props.doc._freeform_panY) - renderBounds.cy) / renderBounds.dim) * 100 - miniHeight() / 2; const miniSize = this.returnMiniSize(); return ( <div className="miniMap" style={{ width: miniSize, height: miniSize, background: this._props.background() }}> <CollectionFreeFormView - Document={this._props.document} + Document={this._props.doc} docViewPath={returnEmptyDocViewList} childLayoutTemplate={this.childLayoutTemplate} // bcz: Ugh .. should probably be rendering a CollectionView or the minimap should be part of the collectionFreeFormView to avoid having to set stuff like this. noOverlay // don't render overlay Docs since they won't scale @@ -144,7 +145,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps select={emptyFunction} isSelected={returnFalse} dontRegisterView - fieldKey={Doc.LayoutFieldKey(this._props.document)} + fieldKey={Doc.LayoutDataKey(this._props.doc)} addDocument={returnFalse} moveDocument={returnFalse} removeDocument={returnFalse} @@ -172,7 +173,7 @@ export class TabMinimapView extends ObservableReactComponent<TabMinimapViewProps ); }; render() { - return this._props.document.layout !== CollectionView.LayoutString(Doc.LayoutFieldKey(this._props.document)) || this._props.document?._type_collection !== CollectionViewType.Freeform ? null : ( + return this._props.doc.layout !== CollectionView.LayoutString(Doc.LayoutDataKey(this._props.doc)) || this._props.doc?._type_collection !== CollectionViewType.Freeform ? null : ( <div className="miniMap-hidden"> <Popup icon={<FontAwesomeIcon icon="globe-asia" size="lg" />} color={SnappingManager.userVariantColor} type={Type.TERT} onPointerDown={e => e.stopPropagation()} placement="top-end" popup={this.popup} /> </div> @@ -206,10 +207,10 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { const docs = toList(docIn); const batch = UndoManager.StartBatch('Pin doc to pres trail'); - const curPres = Doc.ActivePresentation ?? Doc.MakeCopy(Doc.UserDoc().emptyTrail as Doc, true); + const curPres = Doc.ActivePresentation ?? (DocCast(Doc.UserDoc().emptyTrail) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyTrail)!, true) : Docs.Create.PresDocument({})); if (!Doc.ActivePresentation) { - Doc.AddDocToList(Doc.MyTrails, 'data', curPres); + Doc.MyTrails && Doc.AddDocToList(Doc.MyTrails, 'data', curPres); Doc.ActivePresentation = curPres; } @@ -235,7 +236,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { pinDoc.treeView_FieldKey = 'data'; // tree view will treat the 'data' field as the field where the hierarchical children are located instead of using the document's layout string field pinDoc.treeView_ExpandedView = 'data'; // in case the data doc has an expandedView set, this will mask that field and use the 'data' field when expanding the tree view pinDoc.treeView_HideHeaderIfTemplate = true; // this will force the document to render itself as the tree view header - const duration = NumCast(doc[`${Doc.LayoutFieldKey(pinDoc)}_duration`], null); + const duration = NumCast(doc[`${Doc.LayoutDataKey(pinDoc)}_duration`], null); if (pinProps.pinViewport) PinDocView(pinDoc, pinProps, anchorDoc ?? doc); if (!pinProps?.audioRange && duration !== undefined) { @@ -311,7 +312,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { @observable _view: DocumentView | undefined = undefined; @observable _forceInvalidateScreenToLocal = 0; // screentolocal is computed outside of react using a dom resize ovbserver. this hack allows the resize observer to trigger a react update - @computed get layoutDoc() { return this._document && Doc.Layout(this._document); } // prettier-ignore + @computed get layoutDoc() { return this._document?.[DocLayout]; } // prettier-ignore @computed get isUserActivated() { return TabDocView.IsSelected(this._document) || this._isAnyChildContentActive; } // prettier-ignore @computed get isContentActive() { return this.isUserActivated || this._hovering; } // prettier-ignore @@ -347,13 +348,12 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { undoable(() => { const target = e.currentTarget as unknown as { value: string }; titleEle.size = target?.value.length + 3; - doc[DocData].title = target?.value ?? ''; + doc.$title = target?.value ?? ''; }, 'edit tab title')(); }; if (tab.element[0].children[1].children.length === 1) { iconWrap.className = 'lm_iconWrap lm_moreInfo'; - iconWrap.title = 'click for menu, drag to embed in document'; const dragBtnDown = (e: React.PointerEvent) => { setupMoveUpEvents( this, @@ -377,7 +377,26 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { ); }; - const docIcon = <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} />; + const docIcon = ( + <> + <Tooltip title="click for menu, drag to embed in document"> + <FontAwesomeIcon onPointerDown={dragBtnDown} icon={iconType} /> + </Tooltip> + <Tooltip title="click to open in lightbox"> + <FontAwesomeIcon + onPointerDown={dragBtnDown} + icon="external-link-alt" + onClick={() => { + if (doc.layout_fieldKey === 'layout_icon') { + const odoc = Doc.GetEmbeddings(doc).find(embedding => !embedding.embedContainer) ?? Doc.MakeEmbedding(doc); + Doc.deiconifyView(odoc); + } + this.addDocTab(doc, OpenWhere.lightboxAlways); + }} + /> + </Tooltip> + </> + ); const closeIcon = <FontAwesomeIcon icon="eye" />; ReactDOM.createRoot(iconWrap).render(docIcon); ReactDOM.createRoot(closeWrap).render(closeIcon); @@ -547,7 +566,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { return false; }; - getCurrentFrame = () => NumCast(Cast(PresBox.Instance.activeItem.presentation_targetDoc, Doc, null)._currentFrame); + getCurrentFrame = () => NumCast(DocCast(PresBox.Instance.activeItem?.presentation_targetDoc)?._currentFrame); focusFunc = () => { if (!this.tab.header.parent._activeContentItem || this.tab.header.parent._activeContentItem !== this.tab.contentItem) { this.tab.header.parent.setActiveContentItem(this.tab.contentItem); // glr: Panning does not work when this is set - (this line is for trying to make a tab that is not topmost become topmost) @@ -631,7 +650,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { }}> {!this._activated || !this._document ? null : this.renderDocView(this._document)} {this.disableMinimap() || !this._document ? null : ( - <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} document={this._document} tabView={this.tabView} /> + <TabMinimapView key="minimap" addDocTab={this.addDocTab} PanelHeight={this.PanelHeight} PanelWidth={this.PanelWidth} background={this.miniMapColor} doc={this._document} tabView={this.tabView} /> )} </div> ); diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index 6208905fe..4beb75074 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -7,7 +7,7 @@ import * as React from 'react'; import { ClientUtils, lightOrDark, return18, returnEmptyFilter, returnEmptyString, returnFalse, returnTrue, returnZero, setupMoveUpEvents, simulateMouseClick } from '../../../ClientUtils'; import { emptyFunction } from '../../../Utils'; import { Doc, DocListCast, Field, FieldType, Opt, StrListCast, returnEmptyDoclist } from '../../../fields/Doc'; -import { DocData } from '../../../fields/DocSymbols'; +import { DocData, DocLayout } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { List } from '../../../fields/List'; import { RichTextField } from '../../../fields/RichTextField'; @@ -156,10 +156,10 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { return this.Document[DocData]; } @computed get layoutDoc() { - return Doc.Layout(this.Document); + return this.Document[DocLayout]; } @computed get fieldKey() { - return StrCast(this.Document._treeView_FieldKey, Doc.LayoutFieldKey(this.Document)); + return StrCast(this.Document._treeView_FieldKey, Doc.LayoutDataKey(this.Document)); } @computed get childDocs() { return this.childDocList(this.fieldKey); @@ -186,7 +186,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { moving: boolean = false; @undoBatch move = (doc: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => { if (this.Document !== target && addDoc !== returnFalse) { - const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.WithoutComputed(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField); + const canAdd1 = (this._props.parentTreeView as TreeView).dropping || !(ComputedField.DisableCompute(() => FieldValue(this._props.parentTreeView?.Document.data)) instanceof ComputedField); // bcz: this should all be running in a Temp undo batch instead of hackily testing for returnFalse if (canAdd1 && this._props.removeDoc?.(doc) === true) { @@ -372,9 +372,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { _width: 1000, _height: 10, }); - const bulletData = bullet[DocData]; - bulletData.title = ComputedField.MakeFunction('this.text?.Text'); - bulletData.data = new List<Doc>([]); + bullet.$title = ComputedField.MakeFunction('this.text?.Text'); + bullet.$data = new List<Doc>([]); DocumentView.addViewRenderedCb(bullet, dv => dv.ComponentView?.setFocus?.()); return bullet; @@ -432,9 +431,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { localAdd = (docs: Doc | Doc[]): boolean => { const innerAdd = (doc: Doc): boolean => { - const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; + const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[this.fieldKey])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, this.fieldKey, doc); - dataIsComputed && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)); + dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(doc, DocCast(this.Document.embedContainer)!); return added; }; return toList(docs).reduce((flg, doc) => flg && innerAdd(doc), true as boolean); @@ -512,9 +511,9 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { const moveDoc = (docs: Doc | Doc[], target: Doc | undefined, addDoc: (doc: Doc | Doc[]) => boolean) => this.move(docs, target, addDoc); const addDoc = (docs: Doc | Doc[], addBefore?: Doc, before?: boolean) => { const innerAdd = (iDoc: Doc) => { - const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; + const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, iDoc, addBefore, before, false, true); - dataIsComputed && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer)); + dataIsComputed && DocCast(this.Document.embedContainer) && Doc.SetContainer(iDoc, DocCast(this.Document.embedContainer)!); return added; }; return toList(docs).reduce((flg, iDoc) => flg && innerAdd(iDoc), true as boolean); @@ -595,7 +594,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { const key = match[1]; const assign = match[2]; const val = match[3]; - Doc.SetField(doc, key, assign + val, false); + Doc.SetField(doc, key, assign + val); return true; } return false; @@ -634,7 +633,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { d.zIndex = i; }); } - const dataIsComputed = ComputedField.WithoutComputed(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; + const dataIsComputed = ComputedField.DisableCompute(() => FieldValue(this.dataDoc[key])) instanceof ComputedField; const added = (!dataIsComputed || (this.dropping && this.moving)) && Doc.AddDocToList(this.dataDoc, key, doc, addBefore, before, false); !dataIsComputed && added && Doc.SetContainer(doc, this.Document); @@ -835,7 +834,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { @observable headerEleWidth = 0; @computed get titleButtons() { - const customHeaderButtons = this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.Decorations); + const customHeaderButtons = this._props.styleProvider?.(this.Document, this.treeView._props, StyleProp.Decorations) as JSX.Element; const color = SettingsManager.userColor; return this._props.treeViewHideHeaderFields() || this.Document.treeView_HideHeaderFields ? null : ( <> @@ -884,8 +883,8 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { ]; }; childContextMenuItems = () => { - const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), []); - const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), []); + const customScripts = Cast(this.Document.childContextMenuScripts, listSpec(ScriptField), [])!; + const customFilters = Cast(this.Document.childContextMenuFilters, listSpec(ScriptField), [])!; const icons = StrListCast(this.Document.childContextMenuIcons); return StrListCast(this.Document.childContextMenuLabels).map((label, i) => ({ script: customScripts[i], filter: customFilters[i], icon: icons[i], label })); }; @@ -940,7 +939,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { if (property.startsWith(StyleProp.Decorations)) return null; return this._props?.treeView?._props.styleProvider?.(doc, props, property); // properties are inherited from the CollectionTreeView, not the hierarchical parent in the treeView }; - onKeyDown = (e: React.KeyboardEvent) => { + onKey = (e: KeyboardEvent) => { if (this.Document.treeView_HideHeader || (this.Document.treeView_HideHeaderIfTemplate && this.treeView._props.childLayoutTemplate?.()) || this.treeView.outlineMode) { switch (e.key) { case 'Tab': @@ -1139,7 +1138,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { ScreenToLocalTransform={this.docTransform} renderDepth={this._props.renderDepth + 1} onClickScript={this.onChildClick} - onKey={this.onKeyDown} + onKey={this.onKey} containerViewPath={this.treeView.childContainerViewPath} childFilters={returnEmptyFilter} childFiltersByRanges={returnEmptyFilter} @@ -1297,7 +1296,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { const dentDoc = (editTitle: boolean, newParent: Doc, addAfter: Doc | undefined, parent: TreeView | CollectionTreeView | undefined) => { if (parent instanceof TreeView && parent._props.treeView.fileSysMode && !newParent.isFolder) return; - const fieldKey = Doc.LayoutFieldKey(newParent); + const fieldKey = Doc.LayoutDataKey(newParent); if (remove && fieldKey && Cast(newParent[fieldKey], listSpec(Doc)) !== undefined) { remove(child); DocumentView.SetSelectOnLoad(child); @@ -1310,7 +1309,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { const indent = i === 0 ? undefined : (editTitle: boolean) => dentDoc(editTitle, docs[i - 1], undefined, treeViewRefs.get(docs[i - 1])); const outdent = !parentCollectionDoc ? undefined : (editTitle: boolean) => dentDoc(editTitle, parentCollectionDoc, containerPrevSibling, parentTreeView instanceof TreeView ? parentTreeView._props.parentTreeView : undefined); const addDocument = (doc: Doc | Doc[], annotationKey?: string, relativeTo?: Doc, before?: boolean) => add(doc, relativeTo ?? docs[i], before !== undefined ? before : false); - const childLayout = Doc.Layout(pair.layout); + const childLayout = pair.layout[DocLayout]; const rowHeight = () => { const aspect = Doc.NativeAspect(childLayout); return aspect ? Math.min(NumCast(childLayout._width), rowWidth()) / aspect : NumCast(childLayout._height); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts index 3838852dd..903d92c90 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormClusters.ts @@ -31,16 +31,14 @@ export class CollectionFreeFormClusters { get selectDocuments() { return this._view.selectDocuments; } // prettier-ignore static overlapping(doc1: Doc, doc2: Doc, clusterDistance: number) { - const doc2Layout = Doc.Layout(doc2); - const doc1Layout = Doc.Layout(doc1); const x2 = NumCast(doc2.x) - clusterDistance; const y2 = NumCast(doc2.y) - clusterDistance; - const w2 = NumCast(doc2Layout._width) + clusterDistance; - const h2 = NumCast(doc2Layout._height) + clusterDistance; + const w2 = NumCast(doc2._width) + clusterDistance; + const h2 = NumCast(doc2._height) + clusterDistance; const x = NumCast(doc1.x) - clusterDistance; const y = NumCast(doc1.y) - clusterDistance; - const w = NumCast(doc1Layout._width) + clusterDistance; - const h = NumCast(doc1Layout._height) + clusterDistance; + const w = NumCast(doc1._width) + clusterDistance; + const h = NumCast(doc1._height) + clusterDistance; return doc1.z === doc2.z && intersectRect({ left: x, top: y, width: w, height: h }, { left: x2, top: y2, width: w2, height: h2 }); } handlePointerDown(probe: number[]) { @@ -49,12 +47,11 @@ export class CollectionFreeFormClusters { .reduce((cluster, cd) => { const grouping = this.Document._freeform_useClusters ? NumCast(cd.layout_cluster, -1) : NumCast(cd.group, -1); if (grouping !== -1) { - const layoutDoc = Doc.Layout(cd); const cx = NumCast(cd.x) - this._clusterDistance / 2; const cy = NumCast(cd.y) - this._clusterDistance / 2; - const cw = NumCast(layoutDoc._width) + this._clusterDistance; - const ch = NumCast(layoutDoc._height) + this._clusterDistance; - return !layoutDoc.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster; + const cw = NumCast(cd._width) + this._clusterDistance; + const ch = NumCast(cd._height) + this._clusterDistance; + return !cd.z && intersectRect({ left: cx, top: cy, width: cw, height: ch }, { left: probe[0], top: probe[1], width: 1, height: 1 }) ? grouping : cluster; } return cluster; }, -1); diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 8b9a3e0ec..89d2bf2c3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -1,20 +1,19 @@ import { makeObservable, observable, runInAction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; -import { Doc, DocListCast, FieldType, FieldResult } from '../../../../fields/Doc'; +import { Doc, DocListCast, FieldResult, FieldType } from '../../../../fields/Doc'; import { InkTool } from '../../../../fields/InkField'; import { StrCast } from '../../../../fields/Types'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton'; import { TopBar } from '../../topbar/TopBar'; import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState'; -import './CollectionFreeFormView.scss'; -import { DocData } from '../../../../fields/DocSymbols'; import { CollectionFreeFormView } from './CollectionFreeFormView'; +import './CollectionFreeFormView.scss'; export interface CollectionFreeFormInfoUIProps { - Document: Doc; - LayoutDoc: Doc; + Doc: Doc; + layoutDoc: Doc; childDocs: () => Doc[]; close: () => void; } @@ -24,7 +23,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio public static Init() { CollectionFreeFormView.SetInfoUICreator((doc: Doc, layout: Doc, childDocs: () => Doc[], close: () => void) => ( // - <CollectionFreeFormInfoUI Document={doc} LayoutDoc={layout} childDocs={childDocs} close={close} /> + <CollectionFreeFormInfoUI Doc={doc} layoutDoc={layout} childDocs={childDocs} close={close} /> )); } _firstDocPos = { x: 0, y: 0 }; @@ -41,7 +40,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio set currState(val) { runInAction(() => {this._currState = val;}); } // prettier-ignore componentWillUnmount(): void { - this._props.Document[DocData].backgroundColor = this._originalbackground; + this._props.Doc.$backgroundColor = this._originalbackground; } setCurrState = (state: infoState) => { @@ -52,10 +51,10 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio }; setupStates = () => { - this._originalbackground = StrCast(this._props.Document[DocData].backgroundColor); + this._originalbackground = StrCast(this._props.Doc.$backgroundColor); // state entry functions - // const setBackground = (colour: string) => () => {this._props.Document[DocData].backgroundColor = colour;} // prettier-ignore - // const setOpacity = (opacity: number) => () => {this._props.LayoutDoc.opacity = opacity;} // prettier-ignore + // const setBackground = (colour: string) => () => {this._props.Doc.$backgroundColor = colour;} // prettier-ignore + // const setOpacity = (opacity: number) => () => {this._props.layoutDoc.opacity = opacity;} // prettier-ignore // arc transition trigger conditions const firstDoc = () => (this._props.childDocs().length ? this._props.childDocs()[0] : undefined); const numDocs = () => this._props.childDocs().length; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index 241a56a88..158bac7ba 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -1,5 +1,6 @@ /* eslint-disable no-use-before-define */ import { Doc, Field, FieldType, FieldResult } from '../../../../fields/Doc'; +import { DocLayout } from '../../../../fields/DocSymbols'; import { Id, ToString } from '../../../../fields/FieldSymbols'; import { ObjectField } from '../../../../fields/ObjectField'; import { RefField } from '../../../../fields/RefField'; @@ -107,7 +108,7 @@ export function computePassLayout(poolData: Map<string, PoolData>, pivotDoc: Doc } function toNumber(val: FieldResult<FieldType>) { - return val === undefined ? undefined : DateCast(val) ? DateCast(val).date.getMilliseconds() : NumCast(val, Number(StrCast(val))); + return val === undefined ? undefined : DateCast(val) ? DateCast(val).date.getTime() : NumCast(val, Number(StrCast(val))); } export function computeStarburstLayout(poolData: Map<string, PoolData>, pivotDoc: Doc, childPairs: { layout: Doc; data?: Doc }[], panelDim: number[], viewDefsToJSX: (views: ViewDefBounds[]) => ViewDefResult[] /* , engineProps: any */) { @@ -237,7 +238,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do payload: val, }); val?.docs.forEach((doc, i) => { - const layoutDoc = Doc.Layout(doc); + const layoutDoc = doc[DocLayout]; let wid = pivotAxisWidth; let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1); if (hgt > pivotAxisWidth) { @@ -249,7 +250,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do y: -y + (pivotAxisWidth - hgt) / 2, width: wid, height: hgt, - backgroundColor: StrCast(layoutDoc.backgroundColor, 'white'), + backgroundColor: StrCast(doc.backgroundColor, 'white'), pair: { layout: doc }, replica: val.replicas[i], }); @@ -362,7 +363,7 @@ export function computeTimelineLayout(poolData: Map<string, PoolData>, pivotDoc: function layoutDocsAtTime(keyDocs: Doc[], key: number) { keyDocs.forEach(doc => { const stack = findStack(x, stacking); - const layoutDoc = Doc.Layout(doc); + const layoutDoc = doc[DocLayout]; let wid = pivotAxisWidth; let hgt = pivotAxisWidth / (Doc.NativeAspect(layoutDoc) || 1); if (hgt > pivotAxisWidth) { diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx index bc9dd022c..2683d9439 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormPannableContents.tsx @@ -7,7 +7,7 @@ import { ObservableReactComponent } from '../../ObservableReactComponent'; import './CollectionFreeFormView.scss'; export interface CollectionFreeFormPannableContentsProps { - Document: Doc; + Doc: Doc; viewDefDivClick?: ScriptField; children?: React.ReactNode | undefined; transition: () => string; @@ -33,7 +33,7 @@ export class CollectionFreeFormPannableContents extends ObservableReactComponent makeObservable(this); } @computed get presPaths() { - return this._props.showPresPaths() ? CollectionFreeFormPannableContents._overlayPlugin?.(this._props.Document) : null; + return this._props.showPresPaths() ? CollectionFreeFormPannableContents._overlayPlugin?.(this._props.Doc) : null; } // rectangle highlight used when following trail/link to a region of a collection that isn't a document showViewport = (viewport: { panX: number; panY: number; width: number; height: number } | undefined) => diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx index f64c6715b..86310dca3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormRemoteCursors.tsx @@ -13,7 +13,7 @@ import './CollectionFreeFormView.scss'; @observer export class CollectionFreeFormRemoteCursors extends React.Component<CollectionViewProps> { @computed protected get cursors(): CursorField[] { - const { Document } = this.props; + const { Document: Document } = this.props; const cursors = Cast(Document.cursors, listSpec(CursorField)); if (!cursors) { return []; diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index cce0ff684..6c47a71b0 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -41,7 +41,7 @@ transition: background-color 1s ease 0s; } .collectionfreeformview-mask { - mix-blend-mode: multiply; + mix-blend-mode: hard-light; background-color: rgba(0, 0, 0, 0.8); } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 89aa53c35..842293358 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,14 +1,17 @@ -import { Bezier } from 'bezier-js'; import { Button, Colors, Type } from '@dash/components'; +import { Slider } from '@mui/material'; +import { Bezier } from 'bezier-js'; import { Property } from 'csstype'; import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; 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 { DateField } from '../../../../fields/DateField'; import { Doc, DocListCast, Field, FieldType, Opt, StrListCast } from '../../../../fields/Doc'; -import { DocData, Height, Width } from '../../../../fields/DocSymbols'; +import { DocData, DocLayout, Height, Width } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkEraserTool, InkField, InkInkTool, InkTool, Segment } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; @@ -28,6 +31,7 @@ import { DragManager } from '../../../util/DragManager'; import { dropActionType } from '../../../util/DropActionTypes'; import { CompileScript } from '../../../util/Scripting'; import { ScriptingGlobals } from '../../../util/ScriptingGlobals'; +import { SettingsManager } from '../../../util/SettingsManager'; import { freeformScrollMode, SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoable, UndoManager } from '../../../util/UndoManager'; @@ -37,26 +41,26 @@ import { InkingStroke } from '../../InkingStroke'; import { CollectionFreeFormDocumentView } from '../../nodes/CollectionFreeFormDocumentView'; import { SchemaCSVPopUp } from '../../nodes/DataVizBox/SchemaCSVPopUp'; import { + ActiveEraserWidth, ActiveInkArrowEnd, ActiveInkArrowStart, - ActiveInkDash, - ActiveEraserWidth, - ActiveInkFillColor, ActiveInkBezierApprox, ActiveInkColor, + ActiveInkDash, + ActiveInkFillColor, ActiveInkWidth, ActiveIsInkMask, DocumentView, SetActiveInkColor, SetActiveInkWidth, } from '../../nodes/DocumentView'; -import { FieldViewProps } from '../../nodes/FieldView'; import { FocusViewOptions } from '../../nodes/FocusViewOptions'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { OpenWhere } from '../../nodes/OpenWhere'; import { PinDocView, PinProps } from '../../PinFuncs'; -import { StickerPalette } from '../../smartdraw/StickerPalette'; +import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler'; import { DrawingOptions, SmartDrawHandler } from '../../smartdraw/SmartDrawHandler'; +import { StickerPalette } from '../../smartdraw/StickerPalette'; import { StyleProp } from '../../StyleProp'; import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView'; import { TreeViewType } from '../CollectionTreeViewType'; @@ -67,11 +71,6 @@ import { CollectionFreeFormPannableContents } from './CollectionFreeFormPannable import { CollectionFreeFormRemoteCursors } from './CollectionFreeFormRemoteCursors'; import './CollectionFreeFormView.scss'; import { MarqueeView } from './MarqueeView'; -import ReactLoading from 'react-loading'; -import { SettingsManager } from '../../../util/SettingsManager'; -import { Slider } from '@mui/material'; -import { AiOutlineSend } from 'react-icons/ai'; -import { DrawingFillHandler } from '../../smartdraw/DrawingFillHandler'; @observer class CollectionFreeFormOverlayView extends React.Component<{ elements: () => ViewDefResult[] }> { @@ -180,10 +179,10 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return (this._props.fitContentsToBox?.() || this.Document._freeform_fitContentsToBox) && !this.isAnnotationOverlay; } @computed get nativeWidth() { - return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); + return this._props.NativeWidth?.() || Doc.NativeWidth(this.Document); } @computed get nativeHeight() { - return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document, Cast(this.Document.resolvedDataDoc, Doc, null)); + return this._props.NativeHeight?.() || Doc.NativeHeight(this.Document); } @computed get centeringShiftX(): number { return this._props.isAnnotationOverlay || this._props.originTopLeft ? 0 : this._props.PanelWidth() / 2 / this.nativeDimScaling; // shift so pan position is at center of window for non-overlay collections @@ -276,7 +275,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection onChildDoubleClickHandler = () => this._props.childDoubleClickScript || ScriptCast(this.Document.onChildDoubleClick); elementFunc = () => this._layoutElements; viewTransition = () => (this._panZoomTransition ? '' + this._panZoomTransition : undefined); - panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))); + panZoomTransition = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms` : (Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null) ?? null) ?? '')); fitContentOnce = () => { const { cx, cy, scale } = this.contentBounds(); // prettier-ignore this.layoutDoc._freeform_panX = cx; @@ -285,9 +284,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; // freeform_panx, freeform_pany, freeform_scale all attempt to get values first from the layout controller, then from the layout/dataDoc (or template layout doc), and finally from the resolved template data document. // this search order, for example, allows icons of cropped images to find the panx/pany/zoom on the cropped image's data doc instead of the usual layout doc because the zoom/panX/panY define the cropped image - panX = () => this.fitContentBounds?.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1)); - panY = () => this.fitContentBounds?.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1)); - zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1)); + panX = () => this.fitContentBounds?.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(this.Document.freeform_panX, 1)); + panY = () => this.fitContentBounds?.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(this.Document.freeform_panY, 1)); + zoomScaling = () => this.fitContentBounds?.scale ?? NumCast(this.Document[this.scaleFieldKey], 1); // , NumCast(DocCast(this.Document.rootDocument)?.[this.scaleFieldKey], 1)); PanZoomCenterXf = () => (this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.centeringShiftX}px, ${this.centeringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`); ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy(); getActiveDocuments = () => this.childLayoutPairs.filter(pair => this.isCurrent(pair.layout)).map(pair => pair.layout); @@ -389,7 +388,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection if (options.easeFunc) this.setPresEaseFunc(options.easeFunc); if (this._lightboxDoc) return undefined; if (options.pointFocus) return this.focusOnPoint(options); - const anchorInCollection = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor); + const anchorInCollection = DocListCast(this.Document[this.fieldKey ?? Doc.LayoutDataKey(this.Document)]).includes(anchor); const anchorInChildViews = this.childLayoutPairs.map(pair => pair.layout).includes(anchor); if (!anchorInCollection && !anchorInChildViews) { return undefined; @@ -441,7 +440,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const dropPos = this.Document._currentFrame !== undefined ? [NumCast(dvals.x), NumCast(dvals.y)] : [NumCast(refDoc.x), NumCast(refDoc.y)]; docDragData.droppedDocuments.forEach((d, i) => { - const layoutDoc = Doc.Layout(d); + const layoutDoc = d[DocLayout]; const delta = Utils.rotPt(x - dropPos[0], y - dropPos[1], fromScreenXf.Rotate); if (this.Document._currentFrame !== undefined) { CollectionFreeFormDocumentView.setupKeyframes([d], NumCast(this.Document._currentFrame), false); @@ -561,7 +560,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const { points } = ge; const B = this.screenToFreeformContentsXf.transformBounds(ge.bounds.left, ge.bounds.top, ge.bounds.width, ge.bounds.height); const inkDoc = this.createInkDoc(points, B); - if (Doc.ActiveInk === InkInkTool.Highlight) inkDoc[DocData].backgroundColor = 'transparent'; + if (Doc.ActiveInk === InkInkTool.Highlight) inkDoc.$backgroundColor = 'transparent'; if (Doc.ActiveInk === InkInkTool.Write) { this.unprocessedDocs.push(inkDoc); CollectionFreeFormView.collectionsWithUnprocessedInk.add(this); @@ -639,7 +638,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const B = this.screenToFreeformContentsXf.transformBounds(bounds.left, bounds.top, bounds.width, bounds.height); const inkDoc = this.createInkDoc(points, B); ['color', 'fillColor', 'stroke_width', 'stroke_dash', 'stroke_bezier'].forEach(field => { - inkDoc[DocData][field] = stroke.dataDoc[field]; + inkDoc['$' + field] = stroke.dataDoc[field]; }); this.addDocument(inkDoc); }); @@ -1260,15 +1259,14 @@ 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) => { - const docData = doc[DocData]; - docData.title = opts.text; - docData._width = opts.size; - docData.ai_drawing_input = opts.text; - docData.ai_drawing_complexity = opts.complexity; - docData.ai_drawing_colored = opts.autoColor; - docData.ai_drawing_size = opts.size; - docData.ai_drawing_data = gptRes; - docData.ai = 'gpt'; + 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'; this._drawingContainer = doc; if (x !== undefined && y !== undefined) { [doc.x, doc.y] = this.screenToFreeformContentsXf.transformPoint(x, y); @@ -1310,7 +1308,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection SnappingManager.TriggerUserPanned(); if (this.layoutDoc._Transform || this.Document.treeView_OutlineMode === TreeViewType.outline) return; e.stopPropagation(); - const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight); + const docHeight = NumCast(this.Document[Doc.LayoutDataKey(this.Document) + '_nativeHeight'], this.nativeHeight); const scrollable = this.isAnnotationOverlay && NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling + 1e-4; switch ( !e.ctrlKey && !e.shiftKey && !e.metaKey && !e.altKey ?// @@ -1442,9 +1440,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } calculatePanIntoView = (doc: Doc, xf: Transform, scale?: number) => { - const layoutdoc = Doc.Layout(doc); const pt = xf.transformPoint(NumCast(doc.x), NumCast(doc.y)); - const pt2 = xf.transformPoint(NumCast(doc.x) + NumCast(layoutdoc._width), NumCast(doc.y) + NumCast(layoutdoc._height)); + const pt2 = xf.transformPoint(NumCast(doc.x) + NumCast(doc._width), NumCast(doc.y) + NumCast(doc._height)); const bounds = { left: pt[0], right: pt2[0], top: pt[1], bot: pt2[1], width: pt2[0] - pt[0], height: pt2[1] - pt[1] }; if (scale !== undefined) { @@ -1490,20 +1487,23 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection * @param below whether to place the new text Doc below or to the right of the one being typed into. * @returns whether the new text doc was created and added successfully */ - createTextDocCopy = undoable((fieldProps: FieldViewProps, below: boolean) => { - const textDoc = DocCast(fieldProps.Document.rootDocument, fieldProps.Document); - const newDoc = Doc.MakeCopy(textDoc, true); - newDoc[DocData][Doc.LayoutFieldKey(newDoc, fieldProps.LayoutTemplateString)] = undefined; // the copy should not copy the text contents of it source, just the render style - newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10); - newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0); - DocumentView.SetSelectOnLoad(newDoc); - return this.addDocument?.(newDoc); + createTextDocCopy = undoable((textBox: FormattedTextBox, below: boolean) => { + const textDoc = DocCast(textBox.Document); + if (textDoc) { + const newDoc = Doc.MakeCopy(textDoc, true); + newDoc['$' + Doc.LayoutDataKey(newDoc)] = undefined; // the copy should not copy the text contents of it source, just the render style + newDoc.x = NumCast(textDoc.x) + (below ? 0 : NumCast(textDoc._width) + 10); + newDoc.y = NumCast(textDoc.y) + (below ? NumCast(textDoc._height) + 10 : 0); + DocumentView.SetSelectOnLoad(newDoc); + return this.addDocument?.(newDoc); + } + return false; }, 'copied text note'); - onKeyDown = (e: React.KeyboardEvent, fieldProps: FieldViewProps) => { - if ((e.metaKey || e.ctrlKey || e.altKey || fieldProps.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) { + onKey = (e: KeyboardEvent, textBox: FormattedTextBox) => { + if ((e.metaKey || e.ctrlKey || e.altKey || textBox.Document._createDocOnCR) && ['Tab', 'Enter'].includes(e.key)) { e.stopPropagation?.(); - return this.createTextDocCopy(fieldProps, !e.altKey && e.key !== 'Tab'); + return this.createTextDocCopy(textBox, !e.altKey && e.key !== 'Tab'); } return undefined; }; @@ -1543,7 +1543,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection rootSelected={childData ? this.rootSelected : returnFalse} waitForDoubleClickToClick={this._props.waitForDoubleClickToClick} onClickScript={this.onChildClickHandler} - onKey={this.onKeyDown} + onKey={this.onKey} onDoubleClickScript={this.onChildDoubleClickHandler} bringToFront={this.bringToFront} ScreenToLocalTransform={childLayout.z ? this.ScreenToLocalBoxXf : this.ScreenToContentsXf} @@ -1590,7 +1590,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection } case undefined: case OpenWhere.lightbox: - if (this.layoutDoc._isLightbox) { + if (this.dataDoc.$isLightbox) { this._lightboxDoc = docs[0]; return true; } @@ -1602,14 +1602,14 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection getCalculatedPositions(pair: { layout: Doc; data?: Doc }): PoolData { const random = (min: number, max: number, x: number, y: number) => /* min should not be equal to max */ min + (((Math.abs(x * y) * 9301 + 49297) % 233280) / 233280) * (max - min); const childDoc = pair.layout; - const childDocLayout = Doc.Layout(childDoc); const layoutFrameNumber = Cast(this.Document._currentFrame, 'number'); // frame number that container is at which determines layout frame values - const contentFrameNumber = Cast(childDocLayout._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed - const { z, zIndex } = childDoc; + const contentFrameNumber = Cast(childDoc._currentFrame, 'number', layoutFrameNumber ?? null); // frame number that content is at which determines what content is displayed + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const { z, zIndex, stroke_isInkMask } = childDoc; const { backgroundColor, color } = contentFrameNumber === undefined ? { backgroundColor: undefined, color: undefined } : CollectionFreeFormDocumentView.getStringValues(childDoc, contentFrameNumber); const { x, y, autoDim, _width, _height, opacity, _rotation } = layoutFrameNumber === undefined // -1 for width/height means width/height should be PanelWidth/PanelHeight (prevents collectionfreeformdocumentview width/height from getting out of synch with panelWIdth/Height which causes detailView to re-render and lose focus because HTMLtag scaling gets set to a bad intermediate value) - ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDocLayout._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() } + ? { autoDim: 1, _width: Cast(childDoc._width, 'number'), _height: Cast(childDoc._height, 'number'), _rotation: Cast(childDoc._rotation, 'number'), x: childDoc.x, y: childDoc.y, opacity: this._props.childOpacity?.() } : CollectionFreeFormDocumentView.getValues(childDoc, layoutFrameNumber); // prettier-ignore const rotation = Cast(_rotation,'number', @@ -1627,8 +1627,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection zIndex: Cast(zIndex, 'number'), width: _width, height: _height, - transition: StrCast(childDocLayout.dataTransition), - showTags: BoolCast(childDocLayout.showTags) || BoolCast(this.Document.showChildTags) || BoolCast(this.Document._layout_showTags), + transition: StrCast(childDoc.dataTransition), + showTags: BoolCast(childDoc.showTags) || BoolCast(this.Document.showChildTags) || BoolCast(this.Document._layout_showTags), pointerEvents: Cast(childDoc.pointerEvents, 'string', null), pair, replica: '', @@ -1721,7 +1721,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection elements.push({ ele: this.getChildDocView(entry[1]), bounds: entry[1].opacity === 0 ? { payload: undefined, type: '', ...entry[1], width: 0, height: 0 } : { payload: undefined, type: '', ...entry[1] }, - inkMask: BoolCast(entry[1].pair.layout.stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, + inkMask: BoolCast(entry[1].pair.layout?.$stroke_isInkMask) ? NumCast(entry[1].pair.layout.opacity, 1) : -1, }) ); @@ -1744,7 +1744,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection 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); + Cast(this.dataDoc[this._props.fieldKey + '_annotations'], listSpec(Doc), [])?.push(anchor); } else { this.dataDoc[this._props.fieldKey + '_annotations'] = new List<Doc>([anchor]); } @@ -1841,15 +1841,15 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection Object.values(this._disposers).forEach(disposer => disposer?.()); } - updateIcon = (usePanelDimensions?: boolean) => { + updateIcon = (/*usePanelDimensions?: boolean*/) => { const contentDiv = this._mainCont; return !contentDiv ? new Promise<void>(res => res()) : UpdateIcon( this.layoutDoc[Id] + '_icon_' + new Date().getTime(), contentDiv, - usePanelDimensions || true ? this._props.PanelWidth() : NumCast(this.layoutDoc._width), - usePanelDimensions || true ? this._props.PanelHeight() : NumCast(this.layoutDoc._height), + this._props.PanelWidth(), // usePanelDimensions ? this._props.PanelWidth() : NumCast(this.layoutDoc._width), + this._props.PanelHeight(), // usePanelDimensions ? this._props.PanelHeight() : NumCast(this.layoutDoc._height), this._props.PanelWidth(), this._props.PanelHeight(), 0, @@ -1988,7 +1988,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection optionItems.push({ description: 'Use Background Color as Default', event: () => { - Cast(Doc.UserDoc().emptyCollection, Doc, null).backgroundColor = StrCast(this.layoutDoc.backgroundColor); + DocCast(Doc.UserDoc().emptyCollection) && (DocCast(Doc.UserDoc().emptyCollection)!.backgroundColor = StrCast(this.layoutDoc.backgroundColor)); }, icon: 'palette', }); @@ -2088,7 +2088,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection lightboxPanelHeight = () => Math.max(0, this._props.PanelHeight() - 30); lightboxScreenToLocal = () => this.ScreenToLocalBoxXf().translate(-15, -15); onPassiveWheel = (e: WheelEvent) => { - const docHeight = NumCast(this.Document[Doc.LayoutFieldKey(this.Document) + '_nativeHeight'], this.nativeHeight); + const docHeight = NumCast(this.Document[Doc.LayoutDataKey(this.Document) + '_nativeHeight'], this.nativeHeight); const scrollable = NumCast(this.layoutDoc[this.scaleFieldKey], 1) === 1 && docHeight > this._props.PanelHeight() / this.nativeDimScaling; this._props.isSelected() && !scrollable && e.preventDefault(); }; @@ -2111,12 +2111,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection </div> ); } - transitionFunc = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms ${this._presEaseFunc}` : Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null))); + transitionFunc = () => (this._panZoomTransition ? `transform ${this._panZoomTransition}ms ${this._presEaseFunc}` : (Cast(this.layoutDoc._viewTransition, 'string', Cast(this.Document._viewTransition, 'string', null) ?? null) ?? '')); get pannableContents() { this.incrementalRender(); // needs to happen synchronously or freshly typed text documents will flash and miss their first characters return ( <CollectionFreeFormPannableContents - Document={this.Document} + Doc={this.Document} brushedView={this.brushedView} isAnnotationOverlay={this.isAnnotationOverlay} transform={this.PanZoomCenterXf} @@ -2134,6 +2134,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection <MarqueeView {...this._props} ref={this._marqueeViewRef} + Doc={this.Document} ungroup={this.Document.isGroup ? this.promoteCollection : undefined} nudge={this.isAnnotationOverlay || this._props.renderDepth > 0 ? undefined : this.nudge} addDocTab={this.addDocTab} @@ -2150,7 +2151,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection isAnnotationOverlay={this.isAnnotationOverlay}> {this.layoutDoc._freeform_backgroundGrid ? this.backgroundGrid : null} {this.pannableContents} - {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} /> : null} + {this._showAnimTimeline ? <Timeline ref={this._timelineRef} {...this._props} Doc={this._props.Document} /> : null} </MarqueeView> ); } @@ -2315,7 +2316,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection NativeWidth={returnZero} NativeHeight={returnZero} onClickScript={this.onChildClickHandler} - onKey={this.onKeyDown} + onKey={this.onKey} onDoubleClickScript={this.onChildDoubleClickHandler} childFilters={this.childDocFilters} childFiltersByRanges={this.childDocRangeFilters} @@ -2387,7 +2388,7 @@ ScriptingGlobals.add(function datavizFromSchema() { const keys = Cast(view.layoutDoc.schema_columnKeys, listSpec('string'))?.filter(key => key !== 'text'); if (!keys) return; - const children = DocListCast(view.Document[Doc.LayoutFieldKey(view.Document)]); + const children = DocListCast(view.Document[Doc.LayoutDataKey(view.Document)]); const csvRows = []; csvRows.push(keys.join(',')); for (let i = 0; i < children.length; i++) { diff --git a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx index b9f8b13a7..72485aa86 100644 --- a/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx +++ b/src/client/views/collections/collectionFreeForm/FaceCollectionBox.tsx @@ -11,7 +11,7 @@ import { emptyFunction } from '../../../../Utils'; import { Doc, DocListCast, Opt } from '../../../../fields/Doc'; import { DocData } from '../../../../fields/DocSymbols'; import { List } from '../../../../fields/List'; -import { DocCast, ImageCast, NumCast, StrCast } from '../../../../fields/Types'; +import { DocCast, ImageCastToNameType, NumCast, StrCast } from '../../../../fields/Types'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; import { DragManager } from '../../../util/DragManager'; @@ -108,7 +108,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { if (faceAnno) { faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(faceAnno, DocCast(faceAnno.face)); FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document); - faceAnno[DocData].face = this.Document[DocData]; + faceAnno.$face = this.Document[DocData]; } } }); @@ -118,7 +118,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { const imgDoc = faceAnno; faceAnno.face && FaceRecognitionHandler.UniqueFaceRemoveFaceImage(imgDoc, DocCast(faceAnno.face)); FaceRecognitionHandler.UniqueFaceAddFaceImage(faceAnno, this.Document); - faceAnno[DocData].face = this.Document[DocData]; + faceAnno.$face = this.Document[DocData]; }); e.stopPropagation(); return true; @@ -187,7 +187,7 @@ export class UniqueFaceBox extends ViewBoxBaseComponent<FieldViewProps>() { ele?.addEventListener('wheel', this.onPassiveWheel, { passive: false }); })}> {FaceRecognitionHandler.UniqueFaceImages(this.Document).map((doc, i) => { - const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)])?.url.href.split('.') ?? ['-missing-', '.png']; + const [name, type] = ImageCastToNameType(doc[Doc.LayoutDataKey(doc)]) ?? ['-missing-', '.png']; return ( <div className="image-wrapper" diff --git a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx index a3d9641da..ff9fb14e7 100644 --- a/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx +++ b/src/client/views/collections/collectionFreeForm/ImageLabelBox.tsx @@ -9,9 +9,8 @@ import React from 'react'; import { imageUrlToBase64 } from '../../../../ClientUtils'; import { Utils, numberRange } from '../../../../Utils'; import { Doc, NumListCast, Opt } from '../../../../fields/Doc'; -import { DocData } from '../../../../fields/DocSymbols'; import { List } from '../../../../fields/List'; -import { ImageCast } from '../../../../fields/Types'; +import { ImageCastToNameType, ImageCastWithSuffix } from '../../../../fields/Types'; import { gptGetEmbedding, gptImageLabel } from '../../../apis/gpt/GPT'; import { DocumentType } from '../../../documents/DocumentTypes'; import { Docs } from '../../../documents/Documents'; @@ -165,9 +164,9 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { // Converts the images into a Base64 format, afterwhich the information is sent to GPT to label them. const imageInfos = this._selectedImages.map(async doc => { - if (!doc[DocData].tags_chat) { - const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.'); - return imageUrlToBase64(`${name}_o.${type}`).then(hrefBase64 => + 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 => ({ doc, labels }))) ; // prettier-ignore @@ -176,7 +175,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { (await Promise.all(imageInfos)).forEach(imageInfo => { if (imageInfo) { - imageInfo.doc[DocData].tags_chat = (imageInfo.doc[DocData].tags_chat as List<string>) ?? new List<string>(); + imageInfo.doc.$tags_chat = (imageInfo.doc.$tags_chat as List<string>) ?? new List<string>(); const labels = imageInfo.labels.split('\n'); labels.forEach(label => { @@ -186,7 +185,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { .replace(/^\d+\.\s*|-|f\*/, '') .replace(/^#/, '') .trim(); - (imageInfo.doc[DocData].tags_chat as List<string>).push(hashLabel); + (imageInfo.doc.$tags_chat as List<string>).push(hashLabel); }); } }); @@ -200,13 +199,11 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { groupImagesInBox = action(async () => { this.startLoading(); - for (const doc of this._selectedImages) { - for (let index = 0; index < (doc[DocData].tags_chat as List<string>).length; index++) { - const label = (doc[DocData].tags_chat as List<string>)[index]; - const embedding = await gptGetEmbedding(label); - doc[DocData][`tags_embedding_${index + 1}`] = new List<number>(embedding); - } - } + await Promise.all( + this._selectedImages + .map(doc => ({ doc, labels: doc.$tags_chat as List<string> })) + .map(({ doc, labels }) => labels.map((label, index) => gptGetEmbedding(label).then(embedding => (doc[`$tags_embedding_${index + 1}`] = new List<number>(embedding))))) + ); const labelToEmbedding = new Map<string, number[]>(); // Create embeddings for the labels. @@ -215,13 +212,13 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { // For each image, loop through the labels, and calculate similarity. Associate it with the // most similar one. this._selectedImages.forEach(doc => { - const embedLists = numberRange((doc[DocData].tags_chat as List<string>).length).map(n => Array.from(NumListCast(doc[DocData][`tags_embedding_${n + 1}`]))); + const embedLists = numberRange((doc.$tags_chat as List<string>).length).map(n => Array.from(NumListCast(doc[`$tags_embedding_${n + 1}`]))); const bestEmbedScore = (embedding: Opt<number[]>) => Math.max(...embedLists.map(l => (embedding && similarity(Array.from(embedding), l)!) || 0)); const {label: mostSimilarLabelCollect} = this._labelGroups.map(label => ({ label, similarityScore: bestEmbedScore(labelToEmbedding.get(label)) })) .reduce((prev, cur) => cur.similarityScore < 0.3 || cur.similarityScore <= prev.similarityScore ? prev: cur, { label: '', similarityScore: 0, }); // prettier-ignore - doc[DocData].data_label = mostSimilarLabelCollect; // The label most similar to the image's contents. + doc.$data_label = mostSimilarLabelCollect; // The label most similar to the image's contents. }); this.endLoading(); @@ -313,7 +310,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { {this._displayImageInformation ? ( <div className="image-information-list"> {this._selectedImages.map(doc => { - const [name, type] = ImageCast(doc[Doc.LayoutFieldKey(doc)]).url.href.split('.'); + const [name, type] = ImageCastToNameType(doc[Doc.LayoutDataKey(doc)]); return ( <div className="image-information" style={{ borderColor: SettingsManager.userColor }} key={Utils.GenerateGuid()}> <img @@ -322,7 +319,7 @@ export class ImageLabelBox extends ViewBoxBaseComponent<FieldViewProps>() { await DocumentView.showDocument(doc, { willZoomCentered: true }); }}></img> <div className="image-information-labels" onClick={() => this._props.addDocTab(doc, OpenWhere.addRightKeyvalue)}> - {(doc[DocData].tags_chat as List<string>).map(label => { + {(doc.$tags_chat as List<string>).map(label => { return ( <div key={Utils.GenerateGuid()} className="image-label" style={{ backgroundColor: SettingsManager.userVariantColor, borderColor: SettingsManager.userColor }}> {label} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index 00d7ea451..3cc7c0f2d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -8,7 +8,7 @@ import { AclAdmin, AclAugment, AclEdit, DocData } from '../../../../fields/DocSy import { Id } from '../../../../fields/FieldSymbols'; import { InkData, InkTool } from '../../../../fields/InkField'; import { List } from '../../../../fields/List'; -import { Cast, NumCast, StrCast } from '../../../../fields/Types'; +import { Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { ImageField } from '../../../../fields/URLField'; import { GetEffectiveAcl } from '../../../../fields/util'; import { DocUtils } from '../../../documents/DocUtils'; @@ -18,7 +18,6 @@ import { SnappingManager, freeformScrollMode } from '../../../util/SnappingManag import { Transform } from '../../../util/Transform'; import { UndoManager, undoBatch } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; -import { MainView } from '../../MainView'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { MarqueeViewBounds } from '../../PinFuncs'; import { PreviewCursor } from '../../PreviewCursor'; @@ -32,6 +31,7 @@ import { MarqueeOptionsMenu } from './MarqueeOptionsMenu'; import './MarqueeView.scss'; interface MarqueeViewProps { + Doc: Doc; getContainerTransform: () => Transform; getTransform: () => Transform; activeDocuments: () => Doc[]; @@ -113,6 +113,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps // tslint:disable-next-line:prefer-const const cm = ContextMenu.Instance; const [x, y] = this.Transform.transformPoint(this._downX, this._downY); + if (e.key === '?') { cm.setDefaultItem('?', (str: string) => this._props.addDocTab(Docs.Create.WebDocument(`https://wikipedia.org/wiki/${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: `wiki:${str}`, data_useCors: true }), OpenWhere.addRight) @@ -164,7 +165,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps error && console.log(error); data && ClientUtils.convertDataUri(data, this._props.Document[Id] + '_icon_' + new Date().getTime()).then(returnedfilename => { - this._props.Document[DocData].icon = new ImageField(returnedfilename); + this._props.Document.$icon = new ImageField(returnedfilename); }); }) ); @@ -370,13 +371,12 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps const newCollection = creator ? creator(selected, { title: 'nested stack' }) : ((doc: Doc) => { - const docData = doc[DocData]; - docData.data = new List<Doc>(selected); - docData.isGroup = makeGroup; - docData.title = makeGroup ? 'grouping' : 'nested freeform'; + doc.$data = new List<Doc>(selected); + doc.$isGroup = makeGroup; + doc.$title = makeGroup ? 'grouping' : 'nested freeform'; doc._freeform_panX = doc._freeform_panY = 0; return doc; - })(Doc.MakeCopy(Doc.UserDoc().emptyCollection as Doc, true)); + })(DocCast(Doc.UserDoc().emptyCollection) ? Doc.MakeCopy(DocCast(Doc.UserDoc().emptyCollection)!, true) : Docs.Create.FreeformDocument([], {})); newCollection.isSystem = undefined; newCollection._width = bounds.width || 1; // if width/height are unset/0, then groups won't autoexpand to contain their children newCollection._height = bounds.height || 1; @@ -437,11 +437,11 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps */ @undoBatch classifyImages = async () => { - const groupButton = DocListCast(Doc.MyLeftSidebarMenu.data).find(d => d.target === Doc.MyImageGrouper); + const groupButton = DocListCast(Doc.MyLeftSidebarMenu?.data).find(d => d.target === Doc.MyImageGrouper); if (groupButton) { this._selectedDocs = this.marqueeSelect(false, DocumentType.IMG); ImageLabelBoxData.Instance.setData(this._selectedDocs); - MainView.Instance.expandFlyout(groupButton); + ScriptCast(groupButton.onClick)?.script.run({ this: groupButton }); } }; @@ -461,9 +461,9 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps const newColDim = 900; for (const label of labelGroups) { const newCollection = MarqueeView.getCollection([], undefined, false, this.Bounds); - newCollection[DocData].title = label + ' Collection'; - newCollection._x = this.Bounds.left + x_offset; - newCollection._y = this.Bounds.top + y_offset; + newCollection.$title = label + ' Collection'; + newCollection.x = this.Bounds.left + x_offset; + newCollection.y = this.Bounds.top + y_offset; newCollection._width = newColDim; newCollection._height = newColDim; newCollection._freeform_panX = this.Bounds.left + this.Bounds.width / 2; @@ -480,8 +480,8 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps } for (const doc of selectedImages) { - if (doc[DocData].data_label) { - Doc.AddDocToList(labelToCollection.get(doc[DocData].data_label as string)!, undefined, doc); + if (doc.$data_label) { + Doc.AddDocToList(labelToCollection.get(doc.$data_label as string)!, undefined, doc); this._props.removeDocument?.(doc); } } @@ -590,8 +590,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps marqueeSelect = (selectBackgrounds: boolean = false, docType: DocumentType | undefined = undefined) => { const selection: Doc[] = []; const selectFunc = (doc: Doc) => { - const layoutDoc = Doc.Layout(doc); - const bounds = { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(layoutDoc._width), height: NumCast(layoutDoc._height) }; + const bounds = { left: NumCast(doc.x), top: NumCast(doc.y), width: NumCast(doc._width), height: NumCast(doc._height) }; if (!this._lassoFreehand) { intersectRect(bounds, this.Bounds) && selection.push(doc); } else { diff --git a/src/client/views/collections/collectionGrid/CollectionGridView.tsx b/src/client/views/collections/collectionGrid/CollectionGridView.tsx index e7f1de7f1..a7603a45b 100644 --- a/src/client/views/collections/collectionGrid/CollectionGridView.tsx +++ b/src/client/views/collections/collectionGrid/CollectionGridView.tsx @@ -182,17 +182,17 @@ export class CollectionGridView extends CollectionSubView() { isChildContentActive = () => (this._props.isDocumentActive?.() && (this._props.childDocumentsActive?.() || BoolCast(this.Document.childDocumentsActive)) ? true : undefined); /** * - * @param layout + * @param childLayout * @param dxf the x- and y-translations of the decorations box as a transform i.e. this.lookupIndividualTransform * @param width * @param height * @returns the `ContentFittingDocumentView` of the node */ - getDisplayDoc = (layout: Doc, dxf: () => Transform, width: () => number, height: () => number) => ( + getDisplayDoc = (childLayout: Doc, dxf: () => Transform, width: () => number, height: () => number) => ( <DocumentView {...this._props} - Document={layout} - TemplateDataDocument={layout.resolvedDataDoc as Doc} + Document={childLayout} + TemplateDataDocument={childLayout.isTemplateDoc || childLayout.isTemplateForField ? this._props.TemplateDataDocument : undefined} NativeWidth={returnZero} NativeHeight={returnZero} fitWidth={this._props.childLayoutFitWidth} diff --git a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx index 80116dd2f..8d3947653 100644 --- a/src/client/views/collections/collectionLinear/CollectionLinearView.tsx +++ b/src/client/views/collections/collectionLinear/CollectionLinearView.tsx @@ -102,7 +102,7 @@ export class CollectionLinearView extends CollectionSubView() { getLinkUI = () => !DocumentLinksButton.StartLink ? null : ( - <span className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}> + <span key="-link-ui-" className="bottomPopup-background" style={{ pointerEvents: 'all' }} onPointerDown={e => e.stopPropagation()}> <span className="bottomPopup-text"> Creating link from:{' '} <b> @@ -126,7 +126,7 @@ export class CollectionLinearView extends CollectionSubView() { ); getCurrentlyPlayingUI = () => !DocumentView.CurrentlyPlaying?.length ? null : ( - <span className="bottomPopup-background"> + <span key="-currently-playing-" className="bottomPopup-background"> <span className="bottomPopup-text"> Currently playing: {DocumentView.CurrentlyPlaying.map((clip, i) => ( diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx index 8aae24df0..adaceb053 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMulticolumnView.tsx @@ -1,14 +1,17 @@ +import { Button, IconButton } from '@dash/components'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; -import { Button, IconButton } from '@dash/components'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; +import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { FaChevronRight } from 'react-icons/fa'; import { Doc, DocListCast } from '../../../../fields/Doc'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; import { DragManager } from '../../../util/DragManager'; +import { dropActionType } from '../../../util/DropActionTypes'; import { SettingsManager } from '../../../util/SettingsManager'; +import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoable } from '../../../util/UndoManager'; import { DocumentView } from '../../nodes/DocumentView'; @@ -16,8 +19,6 @@ import { CollectionSubView, SubCollectionViewProps } from '../CollectionSubView' import './CollectionMulticolumnView.scss'; import ResizeBar from './MulticolumnResizer'; import WidthLabel from './MulticolumnWidthLabel'; -import { dropActionType } from '../../../util/DropActionTypes'; -import { SnappingManager } from '../../../util/SnappingManager'; interface WidthSpecifier { magnitude: number; @@ -197,16 +198,14 @@ export class CollectionMulticolumnView extends CollectionSubView() { * documents before the target. */ private lookupIndividualTransform = (layout: Doc) => { - if (this.columnUnitLength === undefined) { - return Transform.Identity(); // we're still waiting on promises to resolve - } - let offset = 0; - // eslint-disable-next-line no-restricted-syntax - for (const { layout: candidate } of this.childLayoutPairs) { - if (candidate === layout) { - return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0); + if (this.columnUnitLength !== undefined) { + let offset = 0; + for (const { layout: candidate } of this.childLayoutPairs) { + if (candidate === layout) { + return this.ScreenToLocalBoxXf().translate(-offset / (this._props.NativeDimScaling?.() || 1), 0); + } + offset += this.lookupPixels(candidate) + resizerWidth; } - offset += this.lookupPixels(candidate) + resizerWidth; } return Transform.Identity(); }; @@ -262,50 +261,51 @@ export class CollectionMulticolumnView extends CollectionSubView() { ? true : undefined; }; - getDisplayDoc = (childLayout: Doc) => { - const width = () => this.lookupPixels(childLayout); - const height = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); - const dxf = () => - this.lookupIndividualTransform(childLayout) + childHeight = () => this._props.PanelHeight() - 2 * NumCast(this.layoutDoc._yMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); + childWidth = computedFn((childDoc: Doc) => () => this.lookupPixels(childDoc)); + childXf = computedFn( + (childDoc: Doc) => () => + this.lookupIndividualTransform(childDoc) .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin)) - .scale(this._props.NativeDimScaling?.() || 1); - return ( - <DocumentView - Document={childLayout} - TemplateDataDocument={childLayout.resolvedDataDoc as Doc} - styleProvider={this._props.styleProvider} - containerViewPath={this.childContainerViewPath} - LayoutTemplate={this._props.childLayoutTemplate} - LayoutTemplateString={this._props.childLayoutString} - renderDepth={this._props.renderDepth + 1} - PanelWidth={width} - PanelHeight={height} - rootSelected={this.rootSelected} - dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType} - onClickScript={this.onChildClickHandler} - onDoubleClickScript={this.onChildDoubleClickHandler} - suppressSetHeight - ScreenToLocalTransform={dxf} - isContentActive={this.isChildContentActive} - isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} - hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)} - hideDecorationTitle={this._props.childHideDecorationTitle} - fitContentsToBox={this._props.fitContentsToBox} - focus={this._props.focus} - childFilters={this.childDocFilters} - childFiltersByRanges={this.childDocRangeFilters} - searchFilterDocs={this.searchFilterDocs} - dontRegisterView={this._props.dontRegisterView} - addDocument={this._props.addDocument} - moveDocument={this._props.moveDocument} - removeDocument={this._props.removeDocument} - whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} - addDocTab={this._props.addDocTab} - pinToPres={this._props.pinToPres} - dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'} - /> - ); - }; + .scale(this._props.NativeDimScaling?.() || 1) + ); + getDisplayDoc = (childLayout: Doc) => ( + <DocumentView + Document={childLayout} + TemplateDataDocument={childLayout.isTemplateDoc || childLayout.isTemplateForField ? this._props.TemplateDataDocument : undefined} + styleProvider={this._props.styleProvider} + containerViewPath={this.childContainerViewPath} + LayoutTemplate={this._props.childLayoutTemplate} + LayoutTemplateString={this._props.childLayoutString} + renderDepth={this._props.renderDepth + 1} + PanelWidth={this.childWidth(childLayout)} + PanelHeight={this.childHeight} + rootSelected={this.rootSelected} + dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType} + onClickScript={this.onChildClickHandler} + onDoubleClickScript={this.onChildDoubleClickHandler} + suppressSetHeight + ScreenToLocalTransform={this.childXf(childLayout)} + isContentActive={this.isChildContentActive} + isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} + hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)} + hideDecorationTitle={this._props.childHideDecorationTitle} + fitContentsToBox={this._props.fitContentsToBox} + focus={this._props.focus} + childFilters={this.childDocFilters} + childFiltersByRanges={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + dontRegisterView={this._props.dontRegisterView} + addDocument={this._props.addDocument} + moveDocument={this._props.moveDocument} + removeDocument={this._props.removeDocument} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} + addDocTab={this._props.addDocTab} + pinToPres={this._props.pinToPres} + dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'x' | 'y' | 'xy'} + /> + ); + /** * @returns the resolved list of rendered child documents, displayed * at their resolved pixel widths, each separated by a resizer. @@ -315,7 +315,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { const collector: JSX.Element[] = []; this.childLayouts.forEach((layout, i) => { collector.push( - // eslint-disable-next-line react/no-array-index-key <Tooltip title={'Doc: ' + StrCast(layout.title)} key={'wrapper' + i}> <div className="document-wrapper" style={{ flexDirection: 'column', width: this.lookupPixels(layout) }}> {this.getDisplayDoc(layout)} @@ -327,7 +326,6 @@ export class CollectionMulticolumnView extends CollectionSubView() { </Tooltip>, <ResizeBar width={resizerWidth} - // eslint-disable-next-line react/no-array-index-key key={'resizer' + i} styleProvider={this._props.styleProvider} isContentActive={this._props.isContentActive} diff --git a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx index bda8e91ac..27c41583c 100644 --- a/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx +++ b/src/client/views/collections/collectionMulticolumn/CollectionMultirowView.tsx @@ -1,5 +1,6 @@ import { action, computed, makeObservable } from 'mobx'; import { observer } from 'mobx-react'; +import { computedFn } from 'mobx-utils'; import * as React from 'react'; import { Doc, DocListCast } from '../../../../fields/Doc'; import { BoolCast, NumCast, ScriptCast, StrCast } from '../../../../fields/Types'; @@ -178,16 +179,14 @@ export class CollectionMultirowView extends CollectionSubView() { * documents before the target. */ private lookupIndividualTransform = (layout: Doc) => { - if (this.rowUnitLength === undefined) { - return Transform.Identity(); // we're still waiting on promises to resolve - } - let offset = 0; - // eslint-disable-next-line no-restricted-syntax - for (const { layout: candidate } of this.childLayoutPairs) { - if (candidate === layout) { - return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1)); + if (this.rowUnitLength !== undefined) { + let offset = 0; + for (const { layout: candidate } of this.childLayoutPairs) { + if (candidate === layout) { + return this.ScreenToLocalBoxXf().translate(0, -offset / (this._props.NativeDimScaling?.() || 1)); + } + offset += this.lookupPixels(candidate) + resizerHeight; } - offset += this.lookupPixels(candidate) + resizerHeight; } return Transform.Identity(); // type coersion, this case should never be hit }; @@ -243,49 +242,51 @@ export class CollectionMultirowView extends CollectionSubView() { ? true : undefined; }; - getDisplayDoc = (layout: Doc) => { - const height = () => this.lookupPixels(layout); - const width = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); - const dxf = () => - this.lookupIndividualTransform(layout) + childHeight = computedFn((childDoc: Doc) => () => this.lookupPixels(childDoc)); + childWidth = () => this._props.PanelWidth() - 2 * NumCast(this.layoutDoc._xMargin) - (BoolCast(this.layoutDoc.showWidthLabels) ? 20 : 0); + childXf = computedFn( + (childDoc: Doc) => () => + this.lookupIndividualTransform(childDoc) .translate(-NumCast(this.layoutDoc._xMargin), -NumCast(this.layoutDoc._yMargin)) - .scale(this._props.NativeDimScaling?.() || 1); - return ( - <DocumentView - Document={layout} - TemplateDataDocument={layout.resolvedDataDoc as Doc} - styleProvider={this._props.styleProvider} - containerViewPath={this.childContainerViewPath} - LayoutTemplate={this._props.childLayoutTemplate} - LayoutTemplateString={this._props.childLayoutString} - renderDepth={this._props.renderDepth + 1} - PanelWidth={width} - PanelHeight={height} - rootSelected={this.rootSelected} - dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType} - onClickScript={this.onChildClickHandler} - onDoubleClickScript={this.onChildDoubleClickHandler} - ScreenToLocalTransform={dxf} - isContentActive={this.isChildContentActive} - isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} - hideResizeHandles={!!(layout.layout_fitWidth || this._props.childHideResizeHandles)} - hideDecorationTitle={this._props.childHideDecorationTitle} - fitContentsToBox={this._props.fitContentsToBox} - focus={this._props.focus} - childFilters={this.childDocFilters} - childFiltersByRanges={this.childDocRangeFilters} - searchFilterDocs={this.searchFilterDocs} - dontRegisterView={this._props.dontRegisterView} - addDocument={this._props.addDocument} - moveDocument={this._props.moveDocument} - removeDocument={this._props.removeDocument} - whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} - addDocTab={this._props.addDocTab} - pinToPres={this._props.pinToPres} - dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'y' | 'x' | 'xy'} - /> - ); - }; + .scale(this._props.NativeDimScaling?.() || 1) + ); + + getDisplayDoc = (childLayout: Doc) => ( + <DocumentView + Document={childLayout} + TemplateDataDocument={childLayout.isTemplateDoc || childLayout.isTemplateForField ? this._props.TemplateDataDocument : undefined} + styleProvider={this._props.styleProvider} + containerViewPath={this.childContainerViewPath} + LayoutTemplate={this._props.childLayoutTemplate} + LayoutTemplateString={this._props.childLayoutString} + renderDepth={this._props.renderDepth + 1} + PanelWidth={this.childWidth} + PanelHeight={this.childHeight(childLayout)} + rootSelected={this.rootSelected} + dragAction={StrCast(this.Document.childDragAction, this._props.childDragAction) as dropActionType} + onClickScript={this.onChildClickHandler} + onDoubleClickScript={this.onChildDoubleClickHandler} + ScreenToLocalTransform={this.childXf(childLayout)} + isContentActive={this.isChildContentActive} + isDocumentActive={this._props.childDocumentsActive?.() || this.Document._childDocumentsActive ? this._props.isDocumentActive : this.isContentActive} + hideResizeHandles={!!(childLayout.layout_fitWidth || this._props.childHideResizeHandles)} + hideDecorationTitle={this._props.childHideDecorationTitle} + fitContentsToBox={this._props.fitContentsToBox} + focus={this._props.focus} + childFilters={this.childDocFilters} + childFiltersByRanges={this.childDocRangeFilters} + searchFilterDocs={this.searchFilterDocs} + dontRegisterView={this._props.dontRegisterView} + addDocument={this._props.addDocument} + moveDocument={this._props.moveDocument} + removeDocument={this._props.removeDocument} + whenChildContentsActiveChanged={this._props.whenChildContentsActiveChanged} + addDocTab={this._props.addDocTab} + pinToPres={this._props.pinToPres} + dontCenter={StrCast(this.layoutDoc.layout_dontCenter) as 'y' | 'x' | 'xy'} + /> + ); + /** * @returns the resolved list of rendered child documents, displayed * at their resolved pixel widths, each separated by a resizer. diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index 0bf78f57c..53c0823ea 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -224,6 +224,7 @@ display: none; } +.schema-table-cell-selected, .schema-table-cell, .row-menu { border: 1px solid global.$medium-gray; @@ -232,6 +233,14 @@ display: inline-flex; padding: 0; align-items: center; + input[type='text'] { + border: unset; + } +} +.schema-table-cell-selected { + input[type='text'] { + background: lightgray; + } } .schemaRTFCell { @@ -310,4 +319,7 @@ .schemaField-editing { outline: none; height: 100%; + cursor: text; + outline: none; + overflow: auto; } diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 8e9e8e1cc..82ca96839 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -200,7 +200,7 @@ export class CollectionSchemaView extends CollectionSubView() { this._props.setContentViewBox?.(this); document.addEventListener('keydown', this.onKeyDown); - Object.entries(this._documentOptions).forEach((pair: [string, FInfo]) => this.fieldInfos.set(pair[0], pair[1])); + Object.entries(this._documentOptions).forEach(pair => this.fieldInfos.set(pair[0], pair[1] as FInfo)); this._keysDisposer = observe( this.dataDoc[this.fieldKey ?? 'data'] as List<Doc>, change => { @@ -252,7 +252,8 @@ export class CollectionSchemaView extends CollectionSubView() { @action onKeyDown = (e: KeyboardEvent) => { if (this._selectedDocs.length > 0) { - switch (e.key) { + switch (e.key + (e.shiftKey ? 'Shift' : '')) { + case 'Enter': case 'ArrowDown': { const lastDoc = this._selectedDocs.lastElement(); @@ -272,6 +273,7 @@ export class CollectionSchemaView extends CollectionSubView() { e.preventDefault(); } break; + case 'EnterShift': case 'ArrowUp': { const firstDoc = this._selectedDocs.lastElement(); @@ -291,6 +293,7 @@ export class CollectionSchemaView extends CollectionSubView() { e.preventDefault(); } break; + case 'Tab': case 'ArrowRight': if (this._selectedCells) { this._selectedCol = Math.min(this._colEles.length - 1, this._selectedCol + 1); @@ -298,6 +301,7 @@ export class CollectionSchemaView extends CollectionSubView() { this.selectCell(this._selectedDocs[0], 0, false, false); } break; + case 'TabShift': case 'ArrowLeft': if (this._selectedCells) { this._selectedCol = Math.max(0, this._selectedCol - 1); @@ -1189,7 +1193,7 @@ export class CollectionSchemaView extends CollectionSubView() { let notFiltered = d.z || Doc.IsSystem(d) || DocUtils.FilterDocs([d], this.unrecursiveDocFilters(), childFiltersByRanges, this.Document).length > 0; if (notFiltered) { notFiltered = (!searchDocs.length || searchDocs.includes(d)) && DocUtils.FilterDocs([d], childDocFilters, childFiltersByRanges, this.Document).length > 0; - const fieldKey = Doc.LayoutFieldKey(d); + const fieldKey = Doc.LayoutDataKey(d); const isAnnotatableDoc = d[fieldKey] instanceof List && !(d[fieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc)); const docChildDocs = d[isAnnotatableDoc ? fieldKey + '_annotations' : fieldKey]; const sidebarDocs = isAnnotatableDoc && d[fieldKey + '_sidebar']; @@ -1202,7 +1206,7 @@ export class CollectionSchemaView extends CollectionSubView() { newarray = []; // eslint-disable-next-line no-loop-func subDocs.forEach(t => { - const docFieldKey = Doc.LayoutFieldKey(t); + const docFieldKey = Doc.LayoutDataKey(t); const isSubDocAnnotatable = t[docFieldKey] instanceof List && !(t[docFieldKey] as List<Doc>)?.some(ele => !(ele instanceof Doc)); notFiltered = notFiltered || ((!searchDocs.length || searchDocs.includes(t)) && ((!childDocFilters.length && !childFiltersByRanges.length) || DocUtils.FilterDocs([t], childDocFilters, childFiltersByRanges, d).length)); DocListCast(t[isSubDocAnnotatable ? docFieldKey + '_annotations' : docFieldKey]).forEach(newdoc => newarray.push(newdoc)); diff --git a/src/client/views/collections/collectionSchema/SchemaCellField.tsx b/src/client/views/collections/collectionSchema/SchemaCellField.tsx index e6acff061..9ad94cb31 100644 --- a/src/client/views/collections/collectionSchema/SchemaCellField.tsx +++ b/src/client/views/collections/collectionSchema/SchemaCellField.tsx @@ -21,11 +21,11 @@ import DOMPurify from 'dompurify'; */ export interface SchemaCellFieldProps { + Doc: Doc; contents: FieldType | undefined; fieldContents?: FieldViewProps; editing?: boolean; oneLine?: boolean; - Document: Doc; fieldKey: string; // eslint-disable-next-line no-use-before-define refSelectModeInfo: { enabled: boolean; currEditing: SchemaCellField | undefined }; @@ -55,7 +55,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro }); //must be moved to end of batch or else other docs aren't loaded, so render as d-1 in function } - get docIndex(){return DocumentView.getDocViewIndex(this._props.Document);} // prettier-ignore + get docIndex(){return DocumentView.getDocViewIndex(this._props.Doc);} // prettier-ignore get selfRefPattern() { return `d${this.docIndex}.${this._props.fieldKey}`; @@ -85,16 +85,8 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro () => this._editing, editing => { if (editing) { + this.setContent((this._unrenderedContent = this._props.GetValue() ?? '')); this.setupRefSelect(this.refSelectConditionMet); - setTimeout(() => { - if (this._inputref?.innerText.startsWith('=') || this._inputref?.innerText.startsWith(':=')) { - this._overlayDisposer?.(); - this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 }); - this._props.highlightCells?.(this._unrenderedContent); - this.setContent(this._unrenderedContent); - setTimeout(() => this.setCursorPosition(this._unrenderedContent.length)); - } - }); } else { this._overlayDisposer?.(); this._overlayDisposer = undefined; @@ -108,7 +100,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro () => this._props.GetValue(), fieldVal => { this._unrenderedContent = fieldVal ?? ''; - this.finalizeEdit(false, false, false); + this._editing && this.finalizeEdit(false, false, false); } ); } @@ -192,6 +184,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro setIsFocused = (value: boolean) => { const wasFocused = this._editing; this._editing = value; + this._editing && setTimeout(() => this._inputref?.focus()); return wasFocused !== this._editing; }; @@ -272,8 +265,6 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro @action onKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => { - if (e.nativeEvent.defaultPrevented) return; // hack .. DashFieldView grabs native events, but react ignores stoppedPropagation and preventDefault, so we need to check it here - switch (e.key) { case 'Tab': e.stopPropagation(); @@ -284,9 +275,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro break; case 'Enter': e.stopPropagation(); - if (!e.ctrlKey) { - this.finalizeEdit(e.shiftKey, false, true); - } + !e.ctrlKey && this.finalizeEdit(e.shiftKey, false, true); break; case 'Escape': e.stopPropagation(); @@ -297,7 +286,7 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro case 'ArrowLeft': case 'ArrowRight': // prettier-ignore e.stopPropagation(); - setTimeout(() => this.setupRefSelect(this.refSelectConditionMet), 0); + setTimeout(() => this.setupRefSelect(this.refSelectConditionMet)); break; case ' ': { @@ -306,18 +295,14 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro setTimeout(() => { this.setContent(this._unrenderedContent); setTimeout(() => this.setCursorPosition(cursorPos)); - }, 0); + }); } break; - case 'u': // for some reason 'u' otherwise exits the editor - e.stopPropagation(); - break; case 'Shift': case 'Alt': case 'Meta': case 'Control': - case ':': // prettier-ignore - break; + case ':': default: break; } @@ -361,12 +346,9 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro <div contentEditable className="schemaField-editing" - ref={r => { - this._inputref = r; - }} - style={{ cursor: 'text', outline: 'none', overflow: 'auto', minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20 }} + ref={r => (this._inputref = r)} + style={{ minHeight: `min(100%, ${(this._props.GetValue()?.split('\n').length || 1) * 15})`, minWidth: 20 }} onBlur={() => (this._props.refSelectModeInfo.enabled ? setTimeout(() => this.setIsFocused(true), 1000) : this.finalizeEdit(false, true, false))} - autoFocus onInput={this.onChange} onKeyDown={this.onKeyDown} onPointerDown={e => { @@ -383,14 +365,37 @@ export class SchemaCellField extends ObservableReactComponent<SchemaCellFieldPro ); }; + onFocus = () => { + if (this._inputref?.innerText.startsWith('=') || this._inputref?.innerText.startsWith(':=')) { + this._overlayDisposer?.(); + this._overlayDisposer = OverlayView.Instance.addElement(<DocumentIconContainer />, { x: 0, y: 0 }); + this._props.highlightCells?.(this._unrenderedContent); + this.setContent(this._unrenderedContent); + setTimeout(() => this.setCursorPosition(this._unrenderedContent.length)); + } + }; + + onBlur = action(() => { + this._editing = false; + }); + render() { const gval = this._props.GetValue()?.replace(/\n/g, '\\r\\n'); if (this._editing && gval !== undefined) { - return <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`}>{this.renderEditor()}</div>; + return ( + <div + className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`} // + onFocus={this.onFocus} + onBlur={this.onBlur}> + {this.renderEditor()} + </div> + ); } else return this._props.contents instanceof ObjectField ? null : ( <div className={`editableView-container-editing${this._props.oneLine ? '-oneLine' : ''}`} + onFocus={this.onFocus} + onBlur={this.onBlur} style={{ minHeight: '10px', whiteSpace: this._props.oneLine ? 'nowrap' : 'pre-line', diff --git a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx index da203abfa..c9853fab0 100644 --- a/src/client/views/collections/collectionSchema/SchemaRowBox.tsx +++ b/src/client/views/collections/collectionSchema/SchemaRowBox.tsx @@ -171,7 +171,7 @@ export class SchemaRowBox extends ViewBoxBaseComponent<SchemaRowBoxProps>() { isolatedSelection={this.isolatedSelection} key={key} rowSelected={this._props.isSelected} - Document={this.Document} + Doc={this.Document} col={index} fieldKey={key} allowCRs={false} // to enter text with new lines, must use \n diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index d404378eb..8e1edc1ee 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -32,6 +32,7 @@ import { FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaCellField } from './SchemaCellField'; +import { DocLayout } from '../../../../fields/DocSymbols'; /** * SchemaTableCells make up the majority of the visual representation of the SchemaView. @@ -40,7 +41,7 @@ import { SchemaCellField } from './SchemaCellField'; */ export interface SchemaTableCellProps { - Document: Doc; + Doc: Doc; col: number; deselectCell: () => void; selectCell: (doc: Doc, col: number, shift: boolean, ctrl: boolean) => void; @@ -71,7 +72,7 @@ export interface SchemaTableCellProps { } function selectedCell(props: SchemaTableCellProps) { - return props.isRowActive() && props.selectedCol() === props.col && props.selectedCells()?.filter(d => d === props.Document)?.length; + return props.isRowActive() && props.selectedCol() === props.col && props.selectedCells()?.filter(d => d === props.Doc)?.length; } @observer @@ -84,11 +85,11 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro makeObservable(this); } - get docIndex(){return DocumentView.getDocViewIndex(this._props.Document);} // prettier-ignore + get docIndex(){return DocumentView.getDocViewIndex(this._props.Doc);} // prettier-ignore get isDefault(){return SchemaColumnHeader.isDefaultField(this._props.fieldKey);} // prettier-ignore - get lockedInteraction(){return (this.isDefault || this._props.Document._lockedSchemaEditing);} // prettier-ignore + get lockedInteraction(){return (this.isDefault || this._props.Doc._lockedSchemaEditing);} // prettier-ignore get backgroundColor() { if (this.lockedInteraction) { @@ -102,15 +103,16 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro return true; }; public static renderProps(props: SchemaTableCellProps) { - const { Document, fieldKey, getFinfo, columnWidth, isRowActive } = props; + const { Doc: Document, fieldKey, /* getFinfo,*/ columnWidth, isRowActive } = props; let protoCount = 0; - let doc: Doc | undefined = Document; + const layoutDoc = fieldKey.startsWith('_') ? Document[DocLayout] : Document; + let doc = Document; while (doc) { if (Object.keys(doc).includes(fieldKey.replace(/^_/, ''))) break; protoCount++; doc = DocCast(doc.proto); } - const color = protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue'; // color of text in cells + const color = layoutDoc !== Document ? 'red' : protoCount === 0 || (fieldKey.startsWith('_') && Document[fieldKey] === undefined) ? 'black' : 'blue'; // color of text in cells const textDecoration = ''; const fieldProps: FieldViewProps = { childFilters: returnEmptyFilter, @@ -130,7 +132,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro focus: emptyFunction, addDocTab: SchemaTableCell.addFieldDoc, pinToPres: returnZero, - Document: DocCast(Document.rootDocument, Document), + Document: Document, fieldKey: fieldKey, PanelWidth: columnWidth, PanelHeight: props.rowHeight, @@ -175,7 +177,8 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro const inQuotes = (strField: string) => { return (strField.startsWith('`') && strField.endsWith('`')) || (strField.startsWith("'") && strField.endsWith("'")) || (strField.startsWith('"') && strField.endsWith('"')); }; - if (!inQuotes(this._submittedValue) && inQuotes(modField)) modField = modField.substring(1, modField.length - 1); + const submittedValue = this._submittedValue.startsWith(eqSymbol) ? this._submittedValue.slice(eqSymbol.length) : this._submittedValue; + if (!inQuotes(submittedValue) && inQuotes(modField)) modField = modField.substring(1, modField.length - 1); return eqSymbol + modField; }; @@ -186,7 +189,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro return ( <div className="schemacell-edit-wrapper" - // onContextMenu={} + tabIndex={1} style={{ color, textDecoration, @@ -196,7 +199,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro <SchemaCellField fieldKey={this._props.fieldKey} refSelectModeInfo={this._props.refSelectModeInfo} - Document={this._props.Document} + Doc={this._props.Doc} highlightCells={(text: string) => this._props.highlightCells(this.adjustSelfReference(text))} getCells={(text: string) => this._props.eqHighlightFunc(this.adjustSelfReference(text))} ref={r => selectedCell(this._props) && this._props.autoFocus && r?.setIsFocused(true)} @@ -211,8 +214,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro this._props.finishEdit?.(); return true; } - const hasNoLayout = Doc.IsDataProto(fieldProps.Document) ? true : undefined; // the "delegate" is a a data document so never write to it's proto - const ret = Doc.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, hasNoLayout); + const ret = Doc.SetField(fieldProps.Document, this._props.fieldKey, value); this._submittedValue = value; this._props.finishEdit?.(); return ret; @@ -224,7 +226,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro get getCellType() { const columnTypeStr = this._props.getFinfo(this._props.fieldKey)?.fieldType; - const cellValue = this._props.Document[this._props.fieldKey]; + const cellValue = this._props.Doc[this._props.fieldKey]; if (cellValue instanceof ImageField) return ColumnType.Image; if (cellValue instanceof DateField) return ColumnType.Date; @@ -252,15 +254,15 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro const sides: Array<string | undefined> = []; sides[0] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // left sides[1] = selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // right - sides[2] = !this._props.isolatedSelection(this._props.Document)[0] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // top - sides[3] = !this._props.isolatedSelection(this._props.Document)[1] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // bottom + sides[2] = !this._props.isolatedSelection(this._props.Doc)[0] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // top + sides[3] = !this._props.isolatedSelection(this._props.Doc)[1] && selectedCell(this._props) ? `solid 2px ${Colors.MEDIUM_BLUE}` : undefined; // bottom return sides; } render() { return ( <div - className="schema-table-cell" + className={`schema-table-cell${selectedCell(this._props) ? '-selected' : ''}`} onContextMenu={e => StopEvent(e)} onPointerDown={action(e => { if (this.lockedInteraction) { @@ -272,7 +274,7 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro if (this._props.refSelectModeInfo.enabled && !selectedCell(this._props)) { e.stopPropagation(); e.preventDefault(); - this._props.selectReference(this._props.Document, this._props.col); + this._props.selectReference(this._props.Doc, this._props.col); return; } @@ -280,9 +282,9 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro const ctrl: boolean = e.ctrlKey; if (this._props.isRowActive?.()) { if (selectedCell(this._props) && ctrl) { - this._props.selectCell(this._props.Document, this._props.col, shift, ctrl); + this._props.selectCell(this._props.Doc, this._props.col, shift, ctrl); e.stopPropagation(); - } else !selectedCell(this._props) && this._props.selectCell(this._props.Document, this._props.col, shift, ctrl); + } else !selectedCell(this._props) && this._props.selectCell(this._props.Doc, this._props.col, shift, ctrl); } })} style={{ @@ -321,10 +323,10 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro } get url() { - const field = Cast(this._props.Document[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc - const alts = DocListCast(this._props.Document[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images + const field = Cast(this._props.Doc[this._props.fieldKey], ImageField, null); // retrieve the primary image URL that is being rendered from the data doc + const alts = DocListCast(this._props.Doc[this._props.fieldKey + '_alternates']); // retrieve alternate documents that may be rendered as alternate images const altpaths = alts - .map(doc => Cast(doc[Doc.LayoutFieldKey(doc)], ImageField, null)?.url) + .map(doc => Cast(doc[Doc.LayoutDataKey(doc)], ImageField, null)?.url) .filter(url => url) .map(url => this.choosePath(url)); // access the primary layout data of the alternate documents const paths = field ? [this.choosePath(field.url), ...altpaths] : altpaths; @@ -359,7 +361,7 @@ export class SchemaImageCell extends ObservableReactComponent<SchemaTableCellPro }; render() { - const aspect = Doc.NativeAspect(this._props.Document); // aspect ratio + const aspect = Doc.NativeAspect(this._props.Doc); // aspect ratio // let width = Math.max(75, this._props.columnWidth); // get a with that is no smaller than 75px // const height = Math.max(75, width / aspect); // get a height either proportional to that or 75 px const height = this._props.rowHeight() ? this._props.rowHeight() - (this._props.padding || 6) * 2 : undefined; @@ -379,7 +381,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp @observable _pickingDate: boolean = false; @computed get date(): DateField { // if the cell is a date field, cast then contents to a date. Otherrwwise, make the contents undefined. - return DateCast(this._props.Document[this._props.fieldKey]); + return DateCast(this._props.Doc[this._props.fieldKey]); } handleChange = undoable((date: Date | null) => { @@ -388,7 +390,7 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp // this.applyToDoc(this._document, this._props.row, this._props.col, script.run); // } else { // ^ DateCast is always undefined for some reason, but that is what the field should be set to - date && (this._props.Document[this._props.fieldKey] = new DateField(date)); + date && (this._props.Doc[this._props.fieldKey] = new DateField(date)); // } }, 'date change'); @@ -396,10 +398,10 @@ export class SchemaDateCell extends ObservableReactComponent<SchemaTableCellProp const { pointerEvents } = SchemaTableCell.renderProps(this._props); return ( <> - <div style={{ pointerEvents: 'none' }}> + <div style={{ pointerEvents: 'none' }} tabIndex={1}> <DatePicker dateFormat="Pp" selected={this.date?.date ?? Date.now()} onChange={emptyFunction} /> </div> - {pointerEvents === 'none' ? null : ( + {pointerEvents === 'none' || !selectedCell(this._props) ? null : ( <Popup icon={<FontAwesomeIcon size="xs" icon="caret-down" />} size={Size.XSMALL} @@ -451,11 +453,11 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp onPointerDown={e => e.stopPropagation()} style={{ marginRight: 4 }} type="checkbox" - checked={BoolCast(this._props.Document[this._props.fieldKey])} + checked={BoolCast(this._props.Doc[this._props.fieldKey])} onChange={undoable((value: React.ChangeEvent<HTMLInputElement> | undefined) => { if ((value?.nativeEvent as MouseEvent | PointerEvent).shiftKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); - } else Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); + } else Doc.SetField(this._props.Doc, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + (value?.target?.checked.toString() ?? '')); }, 'set bool cell')} /> @@ -463,14 +465,14 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp contents="" fieldContents={fieldProps} editing={selectedCell(this._props) ? undefined : false} - GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)} + GetValue={() => Field.toKeyValueString(this._props.Doc, this._props.fieldKey)} SetValue={undoable((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value); this._props.finishEdit?.(); return true; } - const set = Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined); + const set = Doc.SetField(this._props.Doc, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Doc) ? true : undefined); this._props.finishEdit?.(); return set; }, 'set bool cell')} @@ -494,14 +496,22 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC <div style={{ width: '100%' }}> <Select styles={{ + dropdownIndicator: base => ({ + ...base, + display: selectedCell(this._props) ? 'unset' : 'none', + }), container: base => ({ ...base, height: 20, + border: 'unset !important', + pointerEvents: selectedCell(this._props) ? 'all' : 'none', }), control: base => ({ ...base, height: 20, minHeight: 20, + border: 'unset !important', + background: selectedCell(this._props) ? 'lightgray' : undefined, }), placeholder: base => ({ ...base, @@ -517,6 +527,7 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC ...base, height: 20, transform: 'scale(0.75)', + border: 'unset !important', }), menuPortal: base => ({ ...base, @@ -529,10 +540,10 @@ export class SchemaEnumerationCell extends ObservableReactComponent<SchemaTableC }} menuPortalTarget={this._props.menuTarget} menuPosition="absolute" - placeholder={StrCast(this._props.Document[this._props.fieldKey], 'select...')} + placeholder={StrCast(this._props.Doc[this._props.fieldKey], 'select...')} options={options} isMulti={false} - onChange={val => Doc.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} + onChange={val => Doc.SetField(this._props.Doc, this._props.fieldKey.replace(/^_/, ''), `"${val?.value ?? ''}"`)} /> </div> </div> |
