diff options
Diffstat (limited to 'src/client/views/collections')
25 files changed, 330 insertions, 229 deletions
diff --git a/src/client/views/collections/CollectionCarousel3DView.tsx b/src/client/views/collections/CollectionCarousel3DView.tsx index 7e484f3df..5454c6f9f 100644 --- a/src/client/views/collections/CollectionCarousel3DView.tsx +++ b/src/client/views/collections/CollectionCarousel3DView.tsx @@ -38,6 +38,10 @@ export class CollectionCarousel3DView extends CollectionSubView() { } }; + @computed get carouselItems() { + return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK); + } + centerScale = Number(CAROUSEL3D_CENTER_SCALE); panelWidth = () => this._props.PanelWidth() / 3; panelHeight = () => this._props.PanelHeight() * Number(CAROUSEL3D_SIDE_SCALE); @@ -85,7 +89,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { ); }; - return this.childLayoutPairs.map((childPair, index) => { + return this.carouselItems.map((childPair, index) => { return ( <div key={childPair.layout[Id]} className={`collectionCarousel3DView-item${index === currentIndex ? '-active' : ''} ${index}`} style={{ width: this.panelWidth() }}> {displayDoc(childPair)} @@ -96,7 +100,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { changeSlide = (direction: number) => { SelectionManager.DeselectAll(); - this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.childLayoutPairs.length) % this.childLayoutPairs.length; + this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + direction + this.carouselItems.length) % this.carouselItems.length; }; onArrowClick = (direction: number) => { @@ -154,7 +158,7 @@ export class CollectionCarousel3DView extends CollectionSubView() { } @computed get dots() { - return this.childLayoutPairs.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />); + return this.carouselItems.map((_child, index) => <div key={Utils.GenerateGuid()} className={`dot${index === NumCast(this.layoutDoc._carousel_index) ? '-active' : ''}`} onClick={() => (this.layoutDoc._carousel_index = index)} />); } @computed get translateX() { const index = NumCast(this.layoutDoc._carousel_index); diff --git a/src/client/views/collections/CollectionCarouselView.tsx b/src/client/views/collections/CollectionCarouselView.tsx index dae16bafb..9c370bfbb 100644 --- a/src/client/views/collections/CollectionCarouselView.tsx +++ b/src/client/views/collections/CollectionCarouselView.tsx @@ -12,6 +12,7 @@ import { FieldViewProps } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import './CollectionCarouselView.scss'; import { CollectionSubView } from './CollectionSubView'; +import { DocumentType } from '../../documents/DocumentTypes'; @observer export class CollectionCarouselView extends CollectionSubView() { @@ -33,13 +34,17 @@ export class CollectionCarouselView extends CollectionSubView() { } }; + @computed get carouselItems() { + return this.childLayoutPairs.filter(pair => pair.layout.type !== DocumentType.LINK); + } + advance = (e: React.MouseEvent) => { e.stopPropagation(); - this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.childLayoutPairs.length; + this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) + 1) % this.carouselItems.length; }; goback = (e: React.MouseEvent) => { e.stopPropagation(); - this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.childLayoutPairs.length) % this.childLayoutPairs.length; + this.layoutDoc._carousel_index = (NumCast(this.layoutDoc._carousel_index) - 1 + this.carouselItems.length) % this.carouselItems.length; }; captionStyleProvider = (doc: Doc | undefined, captionProps: Opt<FieldViewProps>, property: string): any => { // first look for properties on the document in the carousel, then fallback to properties on the container @@ -55,7 +60,7 @@ export class CollectionCarouselView extends CollectionSubView() { captionWidth = () => this._props.PanelWidth() - 2 * this.marginX; @computed get content() { const index = NumCast(this.layoutDoc._carousel_index); - const curDoc = this.childLayoutPairs?.[index]; + const curDoc = this.carouselItems?.[index]; const captionProps = { ...this._props, NativeScaling: returnOne, PanelWidth: this.captionWidth, fieldKey: 'caption', setHeight: undefined, setContentView: undefined }; const carouselShowsCaptions = StrCast(this.layoutDoc._layout_showCaption); return !(curDoc?.layout instanceof Doc) ? null : ( diff --git a/src/client/views/collections/CollectionDockingView.tsx b/src/client/views/collections/CollectionDockingView.tsx index 8f1633122..b2897a9b7 100644 --- a/src/client/views/collections/CollectionDockingView.tsx +++ b/src/client/views/collections/CollectionDockingView.tsx @@ -3,14 +3,14 @@ import { observer } from 'mobx-react'; import * as React from 'react'; import * as ReactDOM from 'react-dom/client'; import * as GoldenLayout from '../../../client/goldenLayout'; -import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { Doc, DocListCast, Field, Opt } from '../../../fields/Doc'; import { AclAdmin, AclEdit, DocData } from '../../../fields/DocSymbols'; import { Id } from '../../../fields/FieldSymbols'; import { InkTool } from '../../../fields/InkField'; import { List } from '../../../fields/List'; -import { ImageCast, NumCast, StrCast } from '../../../fields/Types'; +import { FieldValue, ImageCast, NumCast, StrCast } from '../../../fields/Types'; import { ImageField } from '../../../fields/URLField'; -import { GetEffectiveAcl, inheritParentAcls } from '../../../fields/util'; +import { GetEffectiveAcl, inheritParentAcls, SetPropSetterCb } from '../../../fields/util'; import { addStyleSheet, addStyleSheetRule, clearStyleSheetRules, DivHeight, DivWidth, emptyFunction, incrementTitleCopy } from '../../../Utils'; import { DocServer } from '../../DocServer'; import { Docs } from '../../documents/Documents'; @@ -32,6 +32,7 @@ import './CollectionDockingView.scss'; import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import { TabDocView } from './TabDocView'; +import { ComputedField } from '../../../fields/ScriptField'; const _global = (window /* browser */ || global) /* node */ as any; @observer @@ -313,8 +314,26 @@ export class CollectionDockingView extends CollectionSubView() { } }; + /** + * This publishes Docs having titles starting with '@' to Doc.myPublishedDocs + * Once published, any text that uses the 'title' in its body will automatically + * be linked to this published document. + * @param target + * @param title + */ + titleChanged = (target: any, value: any) => { + const title = Field.toString(value); + if (title.startsWith('@') && !title.substring(1).match(/[\(\)\[\]@]/) && title.length > 1) { + const embedding = DocListCast(target.proto_embeddings).lastElement(); + embedding && Doc.AddToMyPublished(embedding); + } else if (!title.startsWith('@')) { + DocListCast(target.proto_embeddings).forEach(doc => Doc.RemFromMyPublished(doc)); + } + }; + componentDidMount: () => void = async () => { this._unmounting = false; + SetPropSetterCb('title', this.titleChanged); // this overrides any previously assigned callback for the property if (this._containerRef.current) { this._lightboxReactionDisposer = reaction( () => LightboxView.LightboxDoc, @@ -468,10 +487,15 @@ export class CollectionDockingView extends CollectionSubView() { json = json.replace(origtab[Id], newtab[Id]); return newtab; }); - const copy = Docs.Create.DockDocument(newtabs, json, { title: incrementTitleCopy(StrCast(doc.title)) }); - DashboardView.SetupDashboardTrails(copy); - DashboardView.SetupDashboardCalendars(copy); // Zaul TODO: needed? - return DashboardView.openDashboard(copy); + 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>(); + + DashboardView.SetupDashboardTrails(dashboardDoc); + DashboardView.SetupDashboardCalendars(dashboardDoc); // Zaul TODO: needed? + return DashboardView.openDashboard(dashboardDoc); } @action @@ -512,13 +536,13 @@ export class CollectionDockingView extends CollectionSubView() { stack.header?.element.on('mousedown', (e: any) => { const dashboard = Doc.ActiveDashboard; if (dashboard && e.target === stack.header?.element[0] && e.button === 2) { - dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1; + dashboard.pane_count = NumCast(dashboard.pane_count) + 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.pane_count)}`, }); Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); inheritParentAcls(this.Document, docToAdd, false); @@ -530,13 +554,13 @@ export class CollectionDockingView extends CollectionSubView() { action(() => { const dashboard = Doc.ActiveDashboard; if (dashboard) { - dashboard['pane-count'] = NumCast(dashboard['pane-count']) + 1; + dashboard.pane_count = NumCast(dashboard.pane_count) + 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.pane_count)}`, }); Doc.AddDocToList(Doc.MyHeaderBar, 'data', docToAdd, undefined, undefined, true); inheritParentAcls(this.dataDoc, docToAdd, false); diff --git a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx index 763d2e3a6..7dcfd32bd 100644 --- a/src/client/views/collections/CollectionMasonryViewFieldRow.tsx +++ b/src/client/views/collections/CollectionMasonryViewFieldRow.tsx @@ -100,9 +100,8 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF if (de.complete.docDragData) { const key = this._props.pivotField; const castedValue = this.getValue(this.heading); - const onLayoutDoc = this.onLayoutDoc(key); if (this._props.parent.onInternalDrop(e, de)) { - de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !onLayoutDoc)); + key && de.complete.docDragData.droppedDocuments.forEach(d => Doc.SetInPlace(d, key, castedValue, !this.onLayoutDoc(key))); } return true; } @@ -128,7 +127,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF return false; } } - this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true)); + key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, castedValue, true)); this._heading = castedValue.toString(); return true; } @@ -155,10 +154,9 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF this._createEmbeddingSelected = false; const key = this._props.pivotField; const newDoc = Docs.Create.TextDocument('', { _layout_autoHeight: true, _width: 200, _layout_fitWidth: true, title: value }); - const onLayoutDoc = this.onLayoutDoc(key); FormattedTextBox.SetSelectOnLoad(newDoc); FormattedTextBox.SelectOnLoadChar = value; - (onLayoutDoc ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading); + key && ((this.onLayoutDoc(key) ? newDoc : newDoc[DocData])[key] = this.getValue(this._props.heading)); const docs = this._props.parent.childDocList; return docs ? (docs.splice(0, 0, newDoc) ? true : false) : this._props.parent._props.addDocument?.(newDoc) || false; // should really extend addDocument to specify insertion point (at beginning of list) }; @@ -167,7 +165,7 @@ export class CollectionMasonryViewFieldRow extends ObservableReactComponent<CMVF action(() => { this._createEmbeddingSelected = false; const key = this._props.pivotField; - this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true)); + key && this._props.docList.forEach(d => Doc.SetInPlace(d, key, undefined, true)); if (this._props.parent.colHeaderData && this._props.headingObject) { const index = this._props.parent.colHeaderData.indexOf(this._props.headingObject); this._props.parent.colHeaderData.splice(index, 1); diff --git a/src/client/views/collections/CollectionMenu.tsx b/src/client/views/collections/CollectionMenu.tsx index 8729ef549..4f25f69ef 100644 --- a/src/client/views/collections/CollectionMenu.tsx +++ b/src/client/views/collections/CollectionMenu.tsx @@ -1,15 +1,16 @@ -import * as React from 'react'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Tooltip } from '@mui/material'; import { Toggle, ToggleType, Type } from 'browndash-components'; -import { action, computed, Lambda, makeObservable, observable, reaction, runInAction } from 'mobx'; +import { Lambda, action, computed, makeObservable, observable, reaction, runInAction } from 'mobx'; import { observer } from 'mobx-react'; +import * as React from 'react'; +import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents } from '../../../Utils'; import { Doc, DocListCast, Opt } from '../../../fields/Doc'; +import { DocData } from '../../../fields/DocSymbols'; import { List } from '../../../fields/List'; import { ObjectField } from '../../../fields/ObjectField'; import { RichTextField } from '../../../fields/RichTextField'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnTrue, setupMoveUpEvents, Utils } from '../../../Utils'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; @@ -19,11 +20,10 @@ import { undoBatch } from '../../util/UndoManager'; import { AntimodeMenu } from '../AntimodeMenu'; import { EditableView } from '../EditableView'; import { MainView } from '../MainView'; -import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView'; import { DefaultStyleProvider } from '../StyleProvider'; -import { CollectionLinearView } from './collectionLinear'; +import { DocumentView, DocumentViewInternal, returnEmptyDocViewList } from '../nodes/DocumentView'; import './CollectionMenu.scss'; -import { DocData } from '../../../fields/DocSymbols'; +import { CollectionLinearView } from './collectionLinear'; interface CollectionMenuProps { panelHeight: () => number; diff --git a/src/client/views/collections/CollectionNoteTakingView.tsx b/src/client/views/collections/CollectionNoteTakingView.tsx index 6318620e0..5b91216cb 100644 --- a/src/client/views/collections/CollectionNoteTakingView.tsx +++ b/src/client/views/collections/CollectionNoteTakingView.tsx @@ -1,4 +1,4 @@ -import { action, computed, IReactionDisposer, makeObservable, observable, observe, reaction } from 'mobx'; +import { action, computed, IReactionDisposer, makeObservable, observable, reaction } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; import { Doc, Field, Opt } from '../../../fields/Doc'; @@ -9,7 +9,7 @@ import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { BoolCast, Cast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DivHeight, emptyFunction, returnFalse, returnZero, smoothScroll, Utils } from '../../../Utils'; +import { DivHeight, emptyFunction, returnZero, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; import { SnappingManager } from '../../util/SnappingManager'; @@ -19,14 +19,13 @@ import { ContextMenu } from '../ContextMenu'; import { ContextMenuProps } from '../ContextMenuItem'; import { LightboxView } from '../LightboxView'; import { DocumentView } from '../nodes/DocumentView'; -import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView'; +import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import './CollectionNoteTakingView.scss'; import { CollectionNoteTakingViewColumn } from './CollectionNoteTakingViewColumn'; import { CollectionNoteTakingViewDivider } from './CollectionNoteTakingViewDivider'; import { CollectionSubView } from './CollectionSubView'; -import { JsxElement } from 'typescript'; const _global = (window /* browser */ || global) /* node */ as any; /** diff --git a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx index 5e4bce19d..50a97b978 100644 --- a/src/client/views/collections/CollectionNoteTakingViewDivider.tsx +++ b/src/client/views/collections/CollectionNoteTakingViewDivider.tsx @@ -59,6 +59,18 @@ export class CollectionNoteTakingViewDivider extends ObservableReactComponent<Di width: 12, borderRight: '4px solid #282828', borderLeft: '4px solid #282828', + position: 'fixed', + pointerEvents: 'none', + }} + /> + <div + className="columnResizer-handler" + onPointerDown={e => this.registerResizing(e)} + style={{ + height: '95%', + width: 12, + borderRight: '4px solid #282828', + borderLeft: '4px solid #282828', }} /> </div> diff --git a/src/client/views/collections/CollectionPileView.tsx b/src/client/views/collections/CollectionPileView.tsx index 7d7f0bb61..9d68c621b 100644 --- a/src/client/views/collections/CollectionPileView.tsx +++ b/src/client/views/collections/CollectionPileView.tsx @@ -6,6 +6,7 @@ import { ScriptField } from '../../../fields/ScriptField'; import { NumCast, StrCast } from '../../../fields/Types'; import { emptyFunction, returnFalse, setupMoveUpEvents } from '../../../Utils'; import { DocUtils } from '../../documents/Documents'; +import { dropActionType } from '../../util/DragManager'; import { SelectionManager } from '../../util/SelectionManager'; import { undoBatch, UndoManager } from '../../util/UndoManager'; import { OpenWhere } from '../nodes/DocumentView'; @@ -13,7 +14,6 @@ import { computePassLayout, computeStarburstLayout } from './collectionFreeForm' import { CollectionFreeFormView } from './collectionFreeForm/CollectionFreeFormView'; import './CollectionPileView.scss'; import { CollectionSubView } from './CollectionSubView'; -import { dropActionType } from '../../util/DragManager'; @observer export class CollectionPileView extends CollectionSubView() { diff --git a/src/client/views/collections/CollectionStackedTimeline.tsx b/src/client/views/collections/CollectionStackedTimeline.tsx index 5c47d4b9e..656f850b3 100644 --- a/src/client/views/collections/CollectionStackedTimeline.tsx +++ b/src/client/views/collections/CollectionStackedTimeline.tsx @@ -594,6 +594,7 @@ export class CollectionStackedTimeline extends CollectionSubView<CollectionStack <StackedTimelineAnchor {...this._props} mark={d.anchor} + containerViewPath={this._props.containerViewPath} rangeClickScript={this.rangeClickScript} rangePlayScript={this.rangePlayScript} left={left - this._scroll} @@ -701,6 +702,7 @@ interface StackedTimelineAnchorProps { layoutDoc: Doc; isDocumentActive?: () => boolean | undefined; ScreenToLocalTransform: () => Transform; + containerViewPath?: () => DocumentView[]; _timeline: HTMLDivElement | null; focus: FocusFuncType; currentTimecode: () => number; @@ -796,12 +798,15 @@ 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}"]` + ); + }; // context menu contextMenuItems = () => { const resetTitle = { - script: ScriptField.MakeFunction( - `this.title = this["${this._props.endTag}"] ? "#" + formatToTime(this["${this._props.startTag}"]) + "-" + formatToTime(this["${this._props.endTag}"]) : "#" + formatToTime(this["${this._props.startTag}"])` - )!, + method: this.resetTitle, icon: 'folder-plus', label: 'Reset Title', }; @@ -826,7 +831,7 @@ class StackedTimelineAnchor extends ObservableReactComponent<StackedTimelineAnch ref={action((r: DocumentView | null) => (anchor.view = r))} Document={mark} TemplateDataDocument={undefined} - containerViewPath={returnEmptyDoclist} + containerViewPath={this._props.containerViewPath} pointerEvents={this.noEvents ? returnNone : undefined} styleProvider={this._props.styleProvider} renderDepth={this._props.renderDepth + 1} diff --git a/src/client/views/collections/CollectionStackingView.tsx b/src/client/views/collections/CollectionStackingView.tsx index ea1caf58f..3a62a53d7 100644 --- a/src/client/views/collections/CollectionStackingView.tsx +++ b/src/client/views/collections/CollectionStackingView.tsx @@ -11,7 +11,7 @@ import { listSpec } from '../../../fields/Schema'; import { SchemaHeaderField } from '../../../fields/SchemaHeaderField'; import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../fields/Types'; import { TraceMobx } from '../../../fields/util'; -import { DivHeight, emptyFunction, returnEmptyDoclist, returnFalse, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; +import { DivHeight, emptyFunction, returnEmptyDoclist, returnNone, returnZero, setupMoveUpEvents, smoothScroll, Utils } from '../../../Utils'; import { Docs, DocUtils } from '../../documents/Documents'; import { CollectionViewType } from '../../documents/DocumentTypes'; import { DragManager, dropActionType } from '../../util/DragManager'; @@ -24,7 +24,7 @@ import { EditableView } from '../EditableView'; import { LightboxView } from '../LightboxView'; import { CollectionFreeFormDocumentView } from '../nodes/CollectionFreeFormDocumentView'; import { DocumentView } from '../nodes/DocumentView'; -import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView'; +import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { StyleProp } from '../StyleProvider'; import { CollectionMasonryViewFieldRow } from './CollectionMasonryViewFieldRow'; @@ -225,6 +225,7 @@ export class CollectionStackingView extends CollectionSubView<Partial<collection componentWillUnmount() { super.componentWillUnmount(); + this.observer.disconnect(); Object.keys(this._disposers).forEach(key => this._disposers[key]()); } diff --git a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx index 6a3cb759e..641e01b81 100644 --- a/src/client/views/collections/CollectionStackingViewFieldColumn.tsx +++ b/src/client/views/collections/CollectionStackingViewFieldColumn.tsx @@ -112,7 +112,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< if (this._props.colHeaderData?.map(i => i.heading).indexOf(castedValue.toString()) !== -1) { return false; } - this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue)); + this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = castedValue)); if (this._props.headingObject) { this._props.headingObject.setHeading(castedValue.toString()); this._heading = this._props.headingObject.heading; @@ -137,7 +137,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< if (!value && !forceEmptyNote) return false; const key = this._props.pivotField; const newDoc = Docs.Create.TextDocument(value, { _height: 18, _width: 200, _layout_fitWidth: true, title: value, _layout_autoHeight: true }); - newDoc[key] = this.getValue(this._props.heading); + key && (newDoc[key] = this.getValue(this._props.heading)); const maxHeading = this._props.docList.reduce((maxHeading, doc) => (NumCast(doc.heading) > maxHeading ? NumCast(doc.heading) : maxHeading), 0); const heading = maxHeading === 0 || this._props.docList.length === 0 ? 1 : maxHeading === 1 ? 2 : 3; newDoc.heading = heading; @@ -148,7 +148,7 @@ export class CollectionStackingViewFieldColumn extends ObservableReactComponent< @action deleteColumn = () => { - this._props.docList.forEach(d => (d[this._props.pivotField] = undefined)); + this._props.pivotField && this._props.docList.forEach(d => (d[this._props.pivotField] = undefined)); if (this._props.colHeaderData && this._props.headingObject) { const index = this._props.colHeaderData.indexOf(this._props.headingObject); this._props.colHeaderData.splice(index, 1); diff --git a/src/client/views/collections/CollectionSubView.tsx b/src/client/views/collections/CollectionSubView.tsx index 59a695a3d..ecc2aea31 100644 --- a/src/client/views/collections/CollectionSubView.tsx +++ b/src/client/views/collections/CollectionSubView.tsx @@ -15,13 +15,13 @@ import { GestureUtils } from '../../../pen-gestures/GestureUtils'; import { DocServer } from '../../DocServer'; import { Networking } from '../../Network'; import { CollectionViewType, DocumentType } from '../../documents/DocumentTypes'; +import { ViewBoxBaseComponent } from '../DocComponent'; import { DocUtils, Docs, DocumentOptions } from '../../documents/Documents'; import { DragManager, dropActionType } from '../../util/DragManager'; import { ImageUtils } from '../../util/Import & Export/ImageUtils'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { UndoManager, undoBatch } from '../../util/UndoManager'; -import { ViewBoxBaseComponent } from '../DocComponent'; import { LoadingBox } from '../nodes/LoadingBox'; import { FormattedTextBox } from '../nodes/formattedText/FormattedTextBox'; import { CollectionView, CollectionViewProps } from './CollectionView'; diff --git a/src/client/views/collections/CollectionTimeView.tsx b/src/client/views/collections/CollectionTimeView.tsx index b92edd165..37452ddfb 100644 --- a/src/client/views/collections/CollectionTimeView.tsx +++ b/src/client/views/collections/CollectionTimeView.tsx @@ -49,7 +49,7 @@ export class CollectionTimeView extends CollectionSubView() { } getAnchor = (addAsAnnotation: boolean) => { - const anchor = Docs.Create.HTMLMarkerDocument([], { + const anchor = Docs.Create.ConfigDocument({ title: ComputedField.MakeFunction(`"${this.pivotField}"])`) as any, annotationOn: this.Document, }); diff --git a/src/client/views/collections/CollectionTreeView.tsx b/src/client/views/collections/CollectionTreeView.tsx index 3b37bdcfa..293c79119 100644 --- a/src/client/views/collections/CollectionTreeView.tsx +++ b/src/client/views/collections/CollectionTreeView.tsx @@ -12,6 +12,7 @@ import { DivHeight, emptyFunction, returnAll, returnEmptyDoclist, returnEmptyFil import { Docs, DocUtils } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; +import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SelectionManager } from '../../util/SelectionManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -27,7 +28,6 @@ import { CollectionFreeFormView } from './collectionFreeForm'; import { CollectionSubView } from './CollectionSubView'; import './CollectionTreeView.scss'; import { TreeView } from './TreeView'; -import { ScriptingGlobals } from '../../util/ScriptingGlobals'; const _global = (window /* browser */ || global) /* node */ as any; export type collectionTreeViewProps = { diff --git a/src/client/views/collections/TabDocView.tsx b/src/client/views/collections/TabDocView.tsx index 699ec3b95..5cb7b149b 100644 --- a/src/client/views/collections/TabDocView.tsx +++ b/src/client/views/collections/TabDocView.tsx @@ -30,7 +30,7 @@ import { ObservableReactComponent } from '../ObservableReactComponent'; import { DefaultStyleProvider, StyleProp } from '../StyleProvider'; import { Colors } from '../global/globalEnums'; import { DocumentView, OpenWhere, OpenWhereMod, returnEmptyDocViewList } from '../nodes/DocumentView'; -import { FocusViewOptions, FieldViewProps } from '../nodes/FieldView'; +import { FieldViewProps, FocusViewOptions } from '../nodes/FieldView'; import { KeyValueBox } from '../nodes/KeyValueBox'; import { DashFieldView } from '../nodes/formattedText/DashFieldView'; import { PinProps, PresBox, PresMovement } from '../nodes/trails'; @@ -465,6 +465,7 @@ export class TabDocView extends ObservableReactComponent<TabDocViewProps> { addDocument={undefined} removeDocument={this.remDocTab} addDocTab={this.addDocTab} + suppressSetHeight={this._document._layout_fitWidth ? true : false} ScreenToLocalTransform={this.ScreenToLocalTransform} dontCenter={'y'} whenChildContentsActiveChanged={this.whenChildContentActiveChanges} diff --git a/src/client/views/collections/TreeView.tsx b/src/client/views/collections/TreeView.tsx index c6bbcb0a5..5eac6cb09 100644 --- a/src/client/views/collections/TreeView.tsx +++ b/src/client/views/collections/TreeView.tsx @@ -19,7 +19,6 @@ import { DocUtils, Docs } from '../../documents/Documents'; import { DocumentManager } from '../../util/DocumentManager'; import { DragManager, dropActionType } from '../../util/DragManager'; import { LinkManager } from '../../util/LinkManager'; -import { ScriptingGlobals } from '../../util/ScriptingGlobals'; import { SettingsManager } from '../../util/SettingsManager'; import { SnappingManager } from '../../util/SnappingManager'; import { Transform } from '../../util/Transform'; @@ -574,11 +573,21 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { <div style={{ display: 'flex', overflow: 'auto' }} key={'newKeyValue'}> <EditableView key="editableView" - contents={'+key:value'} + contents={'+key=value'} height={13} fontSize={12} GetValue={returnEmptyString} - SetValue={value => value.indexOf(':') !== -1 && KeyValueBox.SetField(doc, value.substring(0, value.indexOf(':')), value.substring(value.indexOf(':') + 1, value.length), true)} + SetValue={input => { + const match = input.match(/([a-zA-Z0-9_-]+)(=|=:=)([a-zA-Z,_@\?\+\-\*\/\ 0-9\(\)]+)/); + if (match) { + const key = match[1]; + const assign = match[2]; + const val = match[3]; + KeyValueBox.SetField(doc, key, assign + val, false); + return true; + } + return false; + }} /> </div> ); @@ -1055,7 +1064,7 @@ export class TreeView extends ObservableReactComponent<TreeViewProps> { this, e, () => { - this._dref?.startDragging(e.clientX, e.clientY, '' as any); + (this._dref ?? this._docRef)?.startDragging(e.clientX, e.clientY, '' as any); return true; }, returnFalse, diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx index 58f6b1593..73dd7fea3 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoState.tsx @@ -5,7 +5,7 @@ import * as React from 'react'; import { SettingsManager } from '../../../util/SettingsManager'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import './CollectionFreeFormView.scss'; -// import assets from './assets/link.png'; +import { Doc } from '../../../../fields/Doc'; /** * An Fsa Arc. The first array element is a test condition function that will be observed. @@ -15,18 +15,18 @@ import './CollectionFreeFormView.scss'; export type infoArc = [() => any, (res?: any) => infoState]; export const StateMessage = Symbol('StateMessage'); -export const StateEntryFunc = Symbol('StateEntryFunc'); export const StateMessageGIF = Symbol('StateMessageGIF'); +export const StateEntryFunc = Symbol('StateEntryFunc'); export class infoState { [StateMessage]: string = ''; - [StateEntryFunc]?: () => any; [StateMessageGIF]?: string = ''; + [StateEntryFunc]?: () => any; [key: string]: infoArc; - constructor(message: string, arcs: { [key: string]: infoArc }, entryFunc?: () => any, messageGif?: string) { + constructor(message: string, arcs: { [key: string]: infoArc }, messageGif?: string, entryFunc?: () => any) { this[StateMessage] = message; Object.assign(this, arcs); - this[StateEntryFunc] = entryFunc; this[StateMessageGIF] = messageGif; + this[StateEntryFunc] = entryFunc; } } @@ -36,16 +36,17 @@ export class infoState { * @param arcs an object with fields containing @infoArcs (an object with field names indicating the arc transition and * field values being a tuple of an arc transition trigger function (that returns a truthy value when the arc should fire), * and an arc transition action function (that sets the next state) + * @param gif the gif displayed when in this state * @param entryFunc a function to call when entering the state * @returns an FSA state */ export function InfoState( msg: string, // arcs: { [key: string]: infoArc }, - entryFunc?: () => any, - gif?: string + gif?: string, + entryFunc?: () => any ) { - return new infoState(msg, arcs, entryFunc, gif); + return new infoState(msg, arcs, gif, entryFunc); } export interface CollectionFreeFormInfoStateProps { @@ -57,7 +58,7 @@ export interface CollectionFreeFormInfoStateProps { @observer export class CollectionFreeFormInfoState extends ObservableReactComponent<CollectionFreeFormInfoStateProps> { _disposers: IReactionDisposer[] = []; - @observable _hide = false; + @observable _expanded = false; constructor(props: any) { super(props); @@ -72,22 +73,16 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec } clearState = () => this._disposers.map(disposer => disposer()); - initState = () => - (this._disposers = this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map(arc => { - return reaction( - // + initState = () => (this._disposers = + this.Arcs.map(arc => ({ test: arc[0], act: arc[1] })).map( + arc => reaction( arc.test, - res => { - if (res) { - const next = arc.act(res); - this._props.next(next); - } - }, + res => res && this._props.next(arc.act(res)), { fireImmediately: true } - ); - })); + ) + )); // prettier-ignore - componentDidMount(): void { + componentDidMount() { this.initState(); } componentDidUpdate(prevProps: Readonly<CollectionFreeFormInfoStateProps>) { @@ -95,17 +90,24 @@ export class CollectionFreeFormInfoState extends ObservableReactComponent<Collec this.clearState(); this.initState(); } - componentWillUnmount(): void { + componentWillUnmount() { this.clearState(); } + render() { + const gif = this.State?.[StateMessageGIF]; return ( - <div className="collectionFreeform-infoUI" style={{ display: this._hide ? 'none' : undefined }}> - <div className="msg">{this.State?.[StateMessage]}</div> - <div className="gif-container" style={{ display: this.State?.[StateMessageGIF] ? undefined : 'none' }}> - <img className="gif" src={this.State?.[StateMessageGIF]} alt="state message gif"></img> + <div className={'collectionFreeform-infoUI'}> + <p className="collectionFreeform-infoUI-msg"> + {this.State?.[StateMessage]} + <button className={'collectionFreeform-' + (!gif ? 'hidden' : 'infoUI-button')} onClick={action(() => (this._expanded = !this._expanded))}> + {this._expanded ? 'Less...' : 'More...'} + </button> + </p> + <div className={'collectionFreeform-' + (!this._expanded || !gif ? 'hidden' : 'infoUI-gif-container')}> + <img src={`/assets/${gif}`} alt="state message gif" /> </div> - <div style={{ position: 'absolute', top: -10, left: -10 }}> + <div className="collectionFreeform-infoUI-close"> <IconButton icon="x" color={SettingsManager.userColor} size={Size.XSMALL} type={Type.TERT} background={SettingsManager.userBackgroundColor} onClick={action(e => this.props.close())} /> </div> </div> diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx index 43b877705..cc729decc 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormInfoUI.tsx @@ -8,6 +8,7 @@ import { DocumentManager } from '../../../util/DocumentManager'; import { LinkManager } from '../../../util/LinkManager'; import { ObservableReactComponent } from '../../ObservableReactComponent'; import { DocButtonState, DocumentLinksButton } from '../../nodes/DocumentLinksButton'; +import { TopBar } from '../../topbar/TopBar'; import { CollectionFreeFormInfoState, InfoState, StateEntryFunc, infoState } from './CollectionFreeFormInfoState'; import { CollectionFreeFormView } from './CollectionFreeFormView'; import './CollectionFreeFormView.scss'; @@ -25,12 +26,12 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio constructor(props: any) { super(props); makeObservable(this); - this.currState = this.setupStates(); + this._currState = this.setupStates(); } _originalbackground: string | undefined; @observable _currState: infoState | undefined = undefined; - get currState() { return this._currState!; } // prettier-ignore + get currState() { return this._currState; } // prettier-ignore set currState(val) { runInAction(() => (this._currState = val)); } // prettier-ignore componentWillUnmount(): void { @@ -60,6 +61,7 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio const docNewY = () => firstDoc()?.y; const linkStart = () => DocumentLinksButton.StartLink; + const linkUnstart = () => !DocumentLinksButton.StartLink; const numDocLinks = () => LinkManager.Instance.getAllDirectLinks(firstDoc())?.length; const linkMenuOpen = () => DocButtonState.Instance.LinkEditorDocView; @@ -74,81 +76,85 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio const presentationMode = () => Doc.ActivePresentation?.presentation_status; // set of states - const start = InfoState('Click anywhere and begin typing to create your first text document.', { - docCreated: [() => numDocs(), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return oneDoc; - }], - }, setBackground("blue")); // prettier-ignore - - const oneDoc = InfoState('Hello world! You can drag and drop to move your document around.', { - // docCreated: [() => numDocs() > 1, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc1; - }], - }, setBackground("red")); // prettier-ignore - - const movedDoc1 = InfoState('Great moves. Try creating a second document.', { - docCreated: [() => numDocs() == 2, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc2; - }], - }, setBackground("yellow")); // prettier-ignore - - const movedDoc2 = InfoState('Slick moves. Try creating a second document.', { - docCreated: [() => numDocs() == 2, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc3; - }], - }, setBackground("pink")); // prettier-ignore - - const movedDoc3 = InfoState('Groovy moves. Try creating a second document.', { - docCreated: [() => numDocs() == 2, () => multipleDocs], - docDeleted: [() => numDocs() < 1, () => start], - docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { - docX = firstDoc()?.x; - docY = firstDoc()?.y; - return movedDoc1; - }], - }, setBackground("green")); // prettier-ignore - - const multipleDocs = InfoState('Let\'s create a new link. Click the link icon on one of your documents.', { - linkStarted: [() => linkStart(), () => startedLink], - docRemoved: [() => numDocs() < 2, () => oneDoc], - }, setBackground("purple")); // prettier-ignore - - const startedLink = InfoState('Now click the highlighted link icon on your other document.', { - linkCreated: [() => numDocLinks(), () => madeLink], - docRemoved: [() => numDocs() < 2, () => oneDoc], - }, setBackground("orange")); // prettier-ignore - - const madeLink = InfoState('You made your first link! You can view your links by selecting the blue dot.', { - linkCreated: [() => !numDocLinks(), () => multipleDocs], - linkViewed: [() => linkMenuOpen(), () => { - alert(numDocLinks() + " cheer for " + numDocLinks() + " link!"); - return viewedLink; - }], - }, setBackground("blue")); // prettier-ignore - - const viewedLink = InfoState('Great work. You are now ready to create your own hypermedia world.', { - linkDeleted: [() => !numDocLinks(), () => multipleDocs], - docRemoved: [() => numDocs() < 2, () => oneDoc], - docCreated: [() => numDocs() == 3, () => { - trail = pin().length; - return presentDocs; - }], - activePen: [() => activeTool() === InkTool.Pen, () => penMode], - }, setBackground("black")); // prettier-ignore + const start = InfoState( + 'Click anywhere and begin typing to create your first text document.', + { + docCreated: [() => numDocs(), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return oneDoc; + }], + } + ); // prettier-ignore + + const oneDoc = InfoState( + 'Hello world! You can drag and drop to move your document around.', + { + // docCreated: [() => numDocs() > 1, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + docMoved: [() => (docX && docX != docNewX()) || (docY && docY != docNewY()), () => { + docX = firstDoc()?.x; + docY = firstDoc()?.y; + return movedDoc; + }], + } + ); // prettier-ignore + + const movedDoc = InfoState( + 'Great moves. Try creating a second document. You can see the list of supported document types by typing a colon (\":\")', + { + docCreated: [() => numDocs() == 2, () => multipleDocs], + docDeleted: [() => numDocs() < 1, () => start], + }, + 'dash-colon-menu.gif', + () => TopBar.Instance.FlipDocumentationIcon() + ); // prettier-ignore + + const multipleDocs = InfoState( + 'Let\'s create a new link. Click the link icon on one of your documents.', + { + linkStarted: [() => linkStart(), () => startedLink], + docRemoved: [() => numDocs() < 2, () => oneDoc], + }, + 'dash-create-link-board.gif' + ); // prettier-ignore + + const startedLink = InfoState( + 'Now click the highlighted link icon on your other document.', + { + linkUnstart: [() => linkUnstart(), () => multipleDocs], + linkCreated: [() => numDocLinks(), () => madeLink], + docRemoved: [() => numDocs() < 2, () => oneDoc], + }, + 'dash-create-link-board.gif' + ); // prettier-ignore + + const madeLink = InfoState( + 'You made your first link! You can view your links by selecting the blue dot.', + { + linkCreated: [() => !numDocLinks(), () => multipleDocs], + linkViewed: [() => linkMenuOpen(), () => { + alert(numDocLinks() + " cheer for " + numDocLinks() + " link!"); + return viewedLink; + }], + }, + 'dash-following-link.gif' + ); // prettier-ignore + + const viewedLink = InfoState( + 'Great work. You are now ready to create your own hypermedia world. Click the ? icon in the top right corner to learn more.', + { + linkDeleted: [() => !numDocLinks(), () => multipleDocs], + docRemoved: [() => numDocs() < 2, () => oneDoc], + docCreated: [() => numDocs() == 3, () => { + trail = pin().length; + return presentDocs; + }], + activePen: [() => activeTool() === InkTool.Pen, () => penMode], + }, + 'documentation.png', + () => TopBar.Instance.FlipDocumentationIcon() + ); // prettier-ignore const presentDocs = InfoState( 'Another document! You could make a presentation. Click the pin icon in the top left corner.', @@ -162,7 +168,6 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio ], docRemoved: [() => numDocs() < 3, () => viewedLink], }, - setBackground('black'), '/assets/dash-pin-with-view.gif' ); @@ -242,14 +247,17 @@ export class CollectionFreeFormInfoUI extends ObservableReactComponent<Collectio docCreated: [() => numDocs() == 4, () => completed], }); - const completed = InfoState('Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', { - docRemoved: [() => numDocs() == 1, () => oneDoc], - }, setBackground("white")); // prettier-ignore + const completed = InfoState( + 'Eager to learn more? Click the ? icon in the top right corner to read our full documentation.', + { docRemoved: [() => numDocs() == 1, () => oneDoc] }, + 'documentation.png', + () => TopBar.Instance.FlipDocumentationIcon() + ); // prettier-ignore return start; }; render() { - return <CollectionFreeFormInfoState next={this.setCurrState} close={this._props.close} infoState={this.currState} />; + return !this.currState ? null : <CollectionFreeFormInfoState next={this.setCurrState} close={this._props.close} infoState={this.currState} />; } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx index b8c0967c1..c83c26509 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormLayoutEngines.tsx @@ -5,7 +5,6 @@ import { RefField } from '../../../../fields/RefField'; import { listSpec } from '../../../../fields/Schema'; import { Cast, NumCast, StrCast } from '../../../../fields/Types'; import { aggregateBounds } from '../../../../Utils'; -import * as React from 'react'; export interface ViewDefBounds { type: string; @@ -240,6 +239,7 @@ export function computePivotLayout(poolData: Map<string, PoolData>, pivotDoc: Do y: -y + (pivotAxisWidth - hgt) / 2, width: wid, height: hgt, + backgroundColor: StrCast(layoutDoc.backgroundColor), pair: { layout: doc }, replica: val.replicas[i], }); @@ -388,7 +388,7 @@ function normalizeResults( minWidth: number, extras: ViewDefBounds[] ): ViewDefResult[] { - const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height } as ViewDefBounds)); + const grpEles = groupNames.map(gn => ({ x: gn.x, y: gn.y, width: gn.width, height: gn.height }) as ViewDefBounds); const docEles = Array.from(docMap.entries()).map(ele => ele[1]); const aggBounds = aggregateBounds( extras.concat(grpEles.concat(docEles.map(de => ({ ...de, type: 'doc', payload: '' })))).filter(e => e.zIndex !== -99), diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss index 9e7d364ea..2c94446fb 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.scss @@ -273,33 +273,34 @@ margin: 15px; padding: 10px; + .collectionFreeform-infoUI-close { + position: absolute; + top: -10; + left: -10; + } - .msg { + .collectionFreeform-infoUI-msg { + position: relative; + max-width: 500; + margin: 10; + } + .collectionFreeform-infoUI-button { + border-radius: 50px; + font-size: 12px; + padding: 6; position: relative; - // display: block; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -o-user-select: none; - user-select: none; } - .gif-container { + .collectionFreeform-infoUI-gif-container { position: relative; margin-top: 5px; - // display: block; + pointer-events: none; - justify-content: center; - align-items: center; - -webkit-user-select: none; - -khtml-user-select: none; - -moz-user-select: none; - -o-user-select: none; - user-select: none; - - .gif { - background-color: transparent; + > img { height: 300px; } } + .collectionFreeform-hidden { + display: none; + } } diff --git a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx index 9500b918a..791124f50 100644 --- a/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx +++ b/src/client/views/collections/collectionFreeForm/CollectionFreeFormView.tsx @@ -1,6 +1,6 @@ import { Bezier } from 'bezier-js'; import { Colors } from 'browndash-components'; -import { action, computed, IReactionDisposer, makeObservable, observable, reaction, runInAction, trace } from 'mobx'; +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'; @@ -17,7 +17,7 @@ import { BoolCast, Cast, DocCast, NumCast, ScriptCast, StrCast } from '../../../ import { ImageField } from '../../../../fields/URLField'; import { TraceMobx } from '../../../../fields/util'; import { GestureUtils } from '../../../../pen-gestures/GestureUtils'; -import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, numberValue, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; +import { aggregateBounds, DashColor, emptyFunction, intersectRect, lightOrDark, OmitKeys, returnFalse, returnZero, setupMoveUpEvents, Utils } from '../../../../Utils'; import { CognitiveServices } from '../../../cognitive_services/CognitiveServices'; import { Docs, DocUtils } from '../../../documents/Documents'; import { CollectionViewType, DocumentType } from '../../../documents/DocumentTypes'; @@ -76,7 +76,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection @observable _paintedId = 'id' + Utils.GenerateGuid().replace(/-/g, ''); @computed get paintFunc() { - const field = this.layoutDoc[this.fieldKey]; + const field = this.dataDoc[this.fieldKey]; const paintFunc = StrCast(Field.toJavascriptString(Cast(field, RichTextField, null)?.Text as Field)).trim(); return !paintFunc ? '' @@ -249,7 +249,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // 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.freeformData()?.bounds.cx ?? NumCast(this.Document[this.panXFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panX, 1)); panY = () => this.freeformData()?.bounds.cy ?? NumCast(this.Document[this.panYFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.freeform_panY, 1)); - zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], NumCast(Cast(this.Document.resolvedDataDoc, Doc, null)?.[this.scaleFieldKey], 1)); + zoomScaling = () => this.freeformData()?.scale ?? NumCast(Doc.Layout(this.Document)[this.scaleFieldKey], 1); //, NumCast(DocCast(this.Document.resolvedDataDoc)?.[this.scaleFieldKey], 1)); PanZoomCenterXf = () => this._props.isAnnotationOverlay && this.zoomScaling() === 1 ? `` : `translate(${this.cachedCenteringShiftX}px, ${this.cachedCenteringShiftY}px) scale(${this.zoomScaling()}) translate(${-this.panX()}px, ${-this.panY()}px)`; ScreenToContentsXf = () => this.screenToFreeformContentsXf.copy(); @@ -314,7 +314,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection // options.didMove = true; // } } - if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor)) return; + if (anchor.type !== DocumentType.CONFIG && !DocListCast(this.Document[this.fieldKey ?? Doc.LayoutFieldKey(this.Document)]).includes(anchor) && !this.childLayoutPairs.map(pair => pair.layout).includes(anchor)) return; const xfToCollection = options?.docTransform ?? Transform.Identity(); const savedState = { panX: NumCast(this.Document[this.panXFieldKey]), panY: NumCast(this.Document[this.panYFieldKey]), scale: options?.willZoomCentered ? this.Document[this.scaleFieldKey] : undefined }; const cantTransform = this.fitContentsToBox || ((this.Document.isGroup || this.layoutDoc._lockedTransform) && !LightboxView.LightboxDoc); @@ -335,6 +335,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection getView = async (doc: Doc, options: FocusViewOptions): Promise<Opt<DocumentView>> => new Promise<Opt<DocumentView>>(res => { if (doc.hidden && this._lightboxDoc !== doc) options.didMove = !(doc.hidden = false); + if (doc === this.Document) return res(this.DocumentView?.()); const findDoc = (finish: (dv: DocumentView) => void) => DocumentManager.Instance.AddViewRenderedCb(doc, dv => finish(dv)); findDoc(dv => res(dv)); }); @@ -564,7 +565,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection clusterStyleProvider = (doc: Opt<Doc>, props: Opt<FieldViewProps>, property: string) => { let styleProp = this._props.styleProvider?.(doc, props, property); // bcz: check 'props' used to be renderDepth + 1 if (doc && this.childDocList?.includes(doc)) - switch (property) { + switch (property.split(':')[0]) { case StyleProp.BackgroundColor: const cluster = NumCast(doc?.layout_cluster); if (this.Document._freeform_useClusters && doc?.type !== DocumentType.IMG) { @@ -589,7 +590,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection }; trySelectCluster = (addToSel: boolean) => { - if (this._hitCluster !== -1) { + if (addToSel && this._hitCluster !== -1) { !addToSel && SelectionManager.DeselectAll(); const eles = this.childLayoutPairs.map(pair => pair.layout).filter(cd => (this.Document._freeform_useClusters ? NumCast(cd.layout_cluster) : NumCast(cd.group, -1)) === this._hitCluster); this.selectDocuments(eles); @@ -647,7 +648,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection x: B.x - inkWidth / 2, y: B.y - inkWidth / 2, _width: B.width + inkWidth, - _height: B.height + inkWidth }, // prettier-ignore + _height: B.height + inkWidth, + stroke_showLabel: BoolCast(Doc.UserDoc().activeInkHideTextLabels)}, // prettier-ignore inkWidth ); if (Doc.ActiveTool === InkTool.Write) { @@ -1261,7 +1263,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection this._lightboxDoc = doc; return true; } - if (this.childDocList?.includes(doc)) { + if (doc === this.Document || this.childDocList?.includes(doc) || this.childLayoutPairs.map(pair => pair.layout)?.includes(doc)) { if (doc.hidden) doc.hidden = false; return true; } @@ -1286,15 +1288,16 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection const rotation = Cast(_rotation,'number', !this.layoutDoc._rotation_jitter ? null : NumCast(this.layoutDoc._rotation_jitter) * random(-1, 1, NumCast(x), NumCast(y)) ); + const childProps = { ...this._props, fieldKey: '', styleProvider: this.clusterStyleProvider }; return { x: Number.isNaN(NumCast(x)) ? 0 : NumCast(x), y: Number.isNaN(NumCast(y)) ? 0 : NumCast(y), z: Cast(z, 'number'), autoDim, rotation, - color: Cast(color, 'string') ? StrCast(color) : this._props.styleProvider?.(childDoc, this._props, StyleProp.Color), - backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, this._props, StyleProp.BackgroundColor), - opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this._props.styleProvider?.(childDoc, this._props, StyleProp.Opacity), + color: Cast(color, 'string') ? StrCast(color) : this.clusterStyleProvider(childDoc, childProps, StyleProp.Color), + backgroundColor: Cast(backgroundColor, 'string') ? StrCast(backgroundColor) : this.clusterStyleProvider(childDoc, childProps, StyleProp.BackgroundColor), + opacity: !_width ? 0 : this._keyframeEditing ? 1 : Cast(opacity, 'number') ?? this.clusterStyleProvider?.(childDoc, childProps, StyleProp.Opacity), zIndex: Cast(zIndex, 'number'), width: _width, height: _height, @@ -1404,7 +1407,12 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection getAnchor = (addAsAnnotation: boolean, pinProps?: PinProps) => { // create an anchor that saves information about the current state of the freeform view (pan, zoom, view type) - const anchor = Docs.Create.ConfigDocument({ title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), layout_unrendered: true, presentation_transition: 500, annotationOn: this.Document }); + const anchor = Docs.Create.ConfigDocument({ + title: 'ViewSpec - ' + StrCast(this.layoutDoc._type_collection), + layout_unrendered: true, + presentation_transition: 500, + annotationOn: this.Document, + }); PresBox.pinDocView(anchor, { pinDocLayout: pinProps?.pinDocLayout, pinData: { ...(pinProps?.pinData ?? {}), pannable: !this.Document.isGroup, type_collection: true, filters: true } }, this.Document); if (addAsAnnotation) { @@ -1417,8 +1425,8 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection return anchor; }; - @action closeInfo = () => (this.Document._hideInfo = true); - infoUI = () => (this.Document._hideInfo || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />); + @action closeInfo = () => (Doc.IsInfoUIDisabled = true); + infoUI = () => (Doc.IsInfoUIDisabled || this.Document.annotationOn || this._props.renderDepth ? null : <CollectionFreeFormInfoUI Document={this.Document} Freeform={this} close={this.closeInfo} />); componentDidMount() { this._props.setContentViewBox?.(this); @@ -1713,7 +1721,7 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection horizLines.push(topLeftInScreen[1], topLeftInScreen[1] + docSize[1] / 2, topLeftInScreen[1] + docSize[1]); // horiz center line vertLines.push(topLeftInScreen[0], topLeftInScreen[0] + docSize[0] / 2, topLeftInScreen[0] + docSize[0]); // right line }); - SnappingManager.addSnapLines(horizLines, vertLines); + this.layoutDoc._freeform_snapLines && SnappingManager.addSnapLines(horizLines, vertLines); }; incrementalRendering = () => this.childDocs.filter(doc => !this._renderCutoffData.get(doc[Id])).length !== 0; @@ -1862,7 +1870,9 @@ export class CollectionFreeFormView extends CollectionSubView<Partial<collection width: `${100 / (this.nativeDimScaling || 1)}%`, height: this._props.getScrollHeight?.() ?? `${100 / (this.nativeDimScaling || 1)}%`, }}> - {this.paintFunc ? null : this._lightboxDoc ? ( + {this.paintFunc ? ( + <FormattedTextBox {...this.props} /> // need this so that any live dashfieldviews will update the underlying text that the code eval reads + ) : this._lightboxDoc ? ( <div style={{ padding: 15, width: '100%', height: '100%' }}> <DocumentView {...this._props} diff --git a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx index b913e05ad..6eca91e9d 100644 --- a/src/client/views/collections/collectionFreeForm/MarqueeView.tsx +++ b/src/client/views/collections/collectionFreeForm/MarqueeView.tsx @@ -101,13 +101,15 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps }; @action - onKeyPress = (e: KeyboardEvent) => { + onKeyDown = (e: KeyboardEvent) => { //make textbox and add it to this collection // 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://bing.com/search?q=${str}`, { _width: 400, x, y, _height: 512, _nativeWidth: 850, title: 'bing', data_useCors: true }), OpenWhere.addRight)); + 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) + ); cm.displayMenu(this._downX, this._downY, undefined, true); e.stopPropagation(); } else if (e.key === 'u' && this._props.ungroup) { @@ -309,7 +311,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps const effectiveAcl = GetEffectiveAcl(this._props.Document[DocData]); if ([AclAdmin, AclEdit, AclAugment].includes(effectiveAcl)) { PreviewCursor.Instance.Doc = doc; - PreviewCursor.Show(x, y, this.onKeyPress, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments); + PreviewCursor.Show(x, y, this.onKeyDown, this._props.addLiveTextDocument, this._props.getTransform, this._props.addDocument, this._props.nudge, this._props.slowLoadDocuments); } this.clearSelection(); } @@ -497,7 +499,7 @@ export class MarqueeView extends ObservableReactComponent<SubCollectionViewProps _layout_showSidebar: true, title: 'overview', }); - const portal = Docs.Create.FreeformDocument(selected, { x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' }); + const portal = Docs.Create.FreeformDocument(selected, { title: 'summarized documents', x: this.Bounds.left + 200, y: this.Bounds.top, isGroup: true, backgroundColor: 'transparent' }); DocUtils.MakeLink(summary, portal, { link_relationship: 'summary of:summarized by' }); portal.hidden = true; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss index ac0bd2378..9768877ff 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.scss +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.scss @@ -226,6 +226,15 @@ align-items: center; } +.schemaRTFCell { + display: flex; + width: 100%; + height: 100%; + position: relative; + min-width: 10px; + min-height: 10px; +} + .schema-row { cursor: grab; justify-content: flex-end; diff --git a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx index 12f0ad5e9..df023b00f 100644 --- a/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx +++ b/src/client/views/collections/collectionSchema/CollectionSchemaView.tsx @@ -1,32 +1,33 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { action, computed, makeObservable, observable, ObservableMap, observe, trace } from 'mobx'; +import { Popup, PopupTrigger, Type } from 'browndash-components'; +import { ObservableMap, action, computed, makeObservable, observable, observe } from 'mobx'; import { observer } from 'mobx-react'; import * as React from 'react'; +import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; import { Doc, DocListCast, Field, NumListCast, Opt, StrListCast } from '../../../../fields/Doc'; +import { DocData } from '../../../../fields/DocSymbols'; import { Id } from '../../../../fields/FieldSymbols'; import { List } from '../../../../fields/List'; import { listSpec } from '../../../../fields/Schema'; import { BoolCast, Cast, DocCast, NumCast, StrCast } from '../../../../fields/Types'; -import { emptyFunction, returnEmptyDoclist, returnEmptyString, returnFalse, returnIgnore, returnNever, returnTrue, setupMoveUpEvents, smoothScroll } from '../../../../Utils'; -import { Docs, DocumentOptions, DocUtils, FInfo } from '../../../documents/Documents'; +import { DocUtils, Docs, DocumentOptions, FInfo } from '../../../documents/Documents'; import { DocumentManager } from '../../../util/DocumentManager'; import { DragManager, dropActionType } from '../../../util/DragManager'; import { SelectionManager } from '../../../util/SelectionManager'; -import { undoable, undoBatch } from '../../../util/UndoManager'; +import { SettingsManager } from '../../../util/SettingsManager'; +import { undoBatch, undoable } from '../../../util/UndoManager'; import { ContextMenu } from '../../ContextMenu'; import { EditableView } from '../../EditableView'; +import { ObservableReactComponent } from '../../ObservableReactComponent'; +import { DefaultStyleProvider, StyleProp } from '../../StyleProvider'; import { Colors } from '../../global/globalEnums'; import { DocumentView } from '../../nodes/DocumentView'; -import { FocusViewOptions, FieldViewProps } from '../../nodes/FieldView'; +import { FieldViewProps, FocusViewOptions } from '../../nodes/FieldView'; import { KeyValueBox } from '../../nodes/KeyValueBox'; -import { ObservableReactComponent } from '../../ObservableReactComponent'; -import { DefaultStyleProvider, StyleProp } from '../../StyleProvider'; import { CollectionSubView } from '../CollectionSubView'; import './CollectionSchemaView.scss'; import { SchemaColumnHeader } from './SchemaColumnHeader'; import { SchemaRowBox } from './SchemaRowBox'; -import { Popup, PopupTrigger, Type } from 'browndash-components'; -import { SettingsManager } from '../../../util/SettingsManager'; const { default: { SCHEMA_NEW_NODE_HEIGHT } } = require('../../global/globalCssVariables.module.scss'); // prettier-ignore export enum ColumnType { @@ -237,7 +238,7 @@ export class CollectionSchemaView extends CollectionSubView() { } break; case 'Backspace': { - this.removeDocument(this._selectedDocs); + undoable(() => this.removeDocument(this._selectedDocs), 'delete schema row'); break; } case 'Escape': { @@ -284,7 +285,7 @@ export class CollectionSchemaView extends CollectionSubView() { }; @action - addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[key] = defaultVal)); + addNewKey = (key: string, defaultVal: any) => this.childDocs.forEach(doc => (doc[DocData][key] = defaultVal)); @undoBatch removeColumn = (index: number) => { diff --git a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx index ed1b519b4..bf36b2668 100644 --- a/src/client/views/collections/collectionSchema/SchemaTableCell.tsx +++ b/src/client/views/collections/collectionSchema/SchemaTableCell.tsx @@ -1,8 +1,11 @@ +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; +import { Popup, Size, Type } from 'browndash-components'; import { action, computed, makeObservable, observable } from 'mobx'; import { observer } from 'mobx-react'; import { extname } from 'path'; import * as React from 'react'; import DatePicker from 'react-datepicker'; +import 'react-datepicker/dist/react-datepicker.css'; import Select from 'react-select'; import { Utils, emptyFunction, returnEmptyDoclist, returnEmptyFilter, returnFalse, returnZero } from '../../../../Utils'; import { DateField } from '../../../../fields/DateField'; @@ -12,6 +15,9 @@ import { BoolCast, Cast, DateCast, DocCast, FieldValue, StrCast } from '../../.. import { ImageField } from '../../../../fields/URLField'; import { FInfo, FInfoFieldType } from '../../../documents/Documents'; import { DocFocusOrOpen } from '../../../util/DocumentManager'; +import { dropActionType } from '../../../util/DragManager'; +import { SettingsManager } from '../../../util/SettingsManager'; +import { SnappingManager } from '../../../util/SnappingManager'; import { Transform } from '../../../util/Transform'; import { undoBatch, undoable } from '../../../util/UndoManager'; import { EditableView } from '../../EditableView'; @@ -24,12 +30,6 @@ import { KeyValueBox } from '../../nodes/KeyValueBox'; import { FormattedTextBox } from '../../nodes/formattedText/FormattedTextBox'; import { ColumnType, FInfotoColType } from './CollectionSchemaView'; import './CollectionSchemaView.scss'; -import 'react-datepicker/dist/react-datepicker.css'; -import { Popup, Size, Type } from 'browndash-components'; -import { IconLookup, faCaretDown } from '@fortawesome/free-solid-svg-icons'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { SettingsManager } from '../../../util/SettingsManager'; -import { dropActionType } from '../../../util/DragManager'; export interface SchemaTableCellProps { Document: Doc; @@ -51,6 +51,8 @@ export interface SchemaTableCellProps { options?: string[]; menuTarget: HTMLDivElement | null; transform: () => Transform; + autoFocus?: boolean; // whether to set focus on creation, othwerise wait for a click + rootSelected?: () => boolean; } @observer @@ -89,16 +91,18 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro select: emptyFunction, dragAction: dropActionType.move, renderDepth: 1, + noSidebar: true, isContentActive: returnFalse, whenChildContentsActiveChanged: emptyFunction, ScreenToLocalTransform: Transform.Identity, focus: emptyFunction, addDocTab: SchemaTableCell.addFieldDoc, pinToPres: returnZero, - Document, + Document: DocCast(Document.rootDocument, Document), fieldKey: fieldKey, PanelWidth: columnWidth, PanelHeight: props.rowHeight, + rootSelected: props.rootSelected, }; const readOnly = getFinfo(fieldKey)?.readOnly ?? false; const cursor = !readOnly ? 'text' : 'default'; @@ -124,17 +128,20 @@ export class SchemaTableCell extends ObservableReactComponent<SchemaTableCellPro pointerEvents, }}> <EditableView + ref={r => this.selected && this._props.autoFocus && r?.setIsFocused(true)} oneLine={this._props.oneLine} allowCRs={this._props.allowCRs} contents={undefined} fieldContents={fieldProps} editing={this.selected ? undefined : false} - GetValue={() => Field.toKeyValueString(this._props.Document, this._props.fieldKey)} + GetValue={() => Field.toKeyValueString(fieldProps.Document, this._props.fieldKey, SnappingManager.MetaKey)} 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 ret = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined); + const ret = KeyValueBox.SetField(fieldProps.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(fieldProps.Document) ? true : undefined); this._props.finishEdit?.(); return ret; }, 'edit schema cell')} @@ -206,7 +213,7 @@ 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 alts = DocListCast(this._props.Document[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) .filter(url => url) @@ -312,13 +319,15 @@ export class SchemaRTFCell extends ObservableReactComponent<SchemaTableCellProps const selected: [Doc, number] | undefined = this._props.selectedCell(); return this._props.isRowActive() && selected?.[0] === this._props.Document && selected[1] === this._props.col; } + + // if the text box blurs and none of its contents are focused(), then the edit finishes selectedFunc = () => this.selected; render() { const { color, textDecoration, fieldProps, cursor, pointerEvents } = SchemaTableCell.renderProps(this._props); fieldProps.isContentActive = this.selectedFunc; return ( - <div className="schemaRTFCell" style={{ display: 'flex', fontStyle: this.selected ? undefined : 'italic', width: '100%', height: '100%', position: 'relative', color, textDecoration, cursor, pointerEvents }}> - {this.selected ? <FormattedTextBox {...fieldProps} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} + <div className="schemaRTFCell" style={{ fontStyle: this.selected ? undefined : 'italic', color, textDecoration, cursor, pointerEvents }}> + {this.selected ? <FormattedTextBox {...fieldProps} autoFocus={true} onBlur={() => this._props.finishEdit?.()} /> : (field => (field ? Field.toString(field) : ''))(FieldValue(fieldProps.Document[fieldProps.fieldKey]))} </div> ); } @@ -345,8 +354,7 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp onChange={undoBatch((value: React.ChangeEvent<HTMLInputElement> | undefined) => { if ((value?.nativeEvent as any).shiftKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); - } - KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); + } else KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), (color === 'black' ? '=' : '') + value?.target?.checked.toString()); })} /> <EditableView @@ -357,8 +365,10 @@ export class SchemaBoolCell extends ObservableReactComponent<SchemaTableCellProp SetValue={undoBatch((value: string, shiftDown?: boolean, enterKey?: boolean) => { if (shiftDown && enterKey) { this._props.setColumnValues(this._props.fieldKey.replace(/^_/, ''), value); + this._props.finishEdit?.(); + return true; } - const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value); + const set = KeyValueBox.SetField(this._props.Document, this._props.fieldKey.replace(/^_/, ''), value, Doc.IsDataProto(this._props.Document) ? true : undefined); this._props.finishEdit?.(); return set; })} |
